meadow-endpoints 3.0.7 → 4.0.3
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/Dockerfile_LUXURYCode +1 -1
- package/README.md +48 -14
- package/debug/Animal.json +62 -0
- package/debug/Harness-Configuration.json +31 -0
- package/debug/Harness.js +7 -108
- package/debug/KillHarness.sh +10 -0
- package/dist/meadowendpoints.js +4402 -0
- package/dist/meadowendpoints.min.js +92 -0
- package/dist/meadowendpoints.min.js.map +1 -0
- package/gulpfile.js +83 -0
- package/package.json +27 -15
- package/source/Controller/Meadow-Endpoints-Controller-Base.js +161 -0
- package/source/Controller/components/Meadow-Endpoints-Controller-BehaviorInjection.js +125 -0
- package/source/Controller/components/Meadow-Endpoints-Controller-Error-StatusCodes.txt +189 -0
- package/source/Controller/components/Meadow-Endpoints-Controller-Error.js +118 -0
- package/source/Controller/components/Meadow-Endpoints-Controller-Log.js +103 -0
- package/source/Controller/utility/Meadow-Endpoints-Filter-Parser.js +225 -0
- package/source/Controller/utility/Meadow-Endpoints-Session-Marshaler.js +48 -0
- package/source/Controller/utility/Meadow-Endpoints-Stream-RecordArray.js +66 -0
- package/source/Meadow-Endpoints-Browser-Shim.js +14 -0
- package/source/Meadow-Endpoints.js +176 -565
- package/source/endpoints/count/Meadow-Endpoint-Count.js +49 -0
- package/source/endpoints/count/Meadow-Endpoint-CountBy.js +40 -0
- package/source/endpoints/create/Meadow-Endpoint-BulkCreate.js +53 -0
- package/source/endpoints/create/Meadow-Endpoint-Create.js +58 -0
- package/source/endpoints/create/Meadow-Operation-Create.js +83 -0
- package/source/endpoints/delete/Meadow-Endpoint-Delete.js +93 -0
- package/source/endpoints/delete/Meadow-Endpoint-Undelete.js +108 -0
- package/source/endpoints/read/Meadow-Endpoint-Read.js +72 -0
- package/source/endpoints/read/Meadow-Endpoint-ReadDistinctList.js +92 -0
- package/source/endpoints/read/Meadow-Endpoint-ReadLiteList.js +85 -0
- package/source/endpoints/read/Meadow-Endpoint-ReadMax.js +55 -0
- package/source/endpoints/read/Meadow-Endpoint-ReadSelectList.js +89 -0
- package/source/endpoints/read/Meadow-Endpoint-Reads.js +75 -0
- package/source/endpoints/read/Meadow-Endpoint-ReadsBy.js +100 -0
- package/source/{crud → endpoints/read}/Meadow-Marshal-DistinctList.js +4 -13
- package/source/{crud → endpoints/read}/Meadow-Marshal-LiteList.js +5 -14
- package/source/endpoints/schema/Meadow-Endpoint-New.js +36 -0
- package/source/endpoints/schema/Meadow-Endpoint-Schema.js +36 -0
- package/source/endpoints/schema/Meadow-Endpoint-Validate.js +41 -0
- package/source/endpoints/update/Meadow-Endpoint-BulkUpdate.js +50 -0
- package/source/endpoints/update/Meadow-Endpoint-Update.js +58 -0
- package/source/endpoints/update/Meadow-Operation-Update.js +115 -0
- package/source/endpoints/upsert/Meadow-Endpoint-BulkUpsert.js +53 -0
- package/source/endpoints/upsert/Meadow-Endpoint-Upsert.js +58 -0
- package/source/endpoints/upsert/Meadow-Operation-Upsert.js +132 -0
- package/test/MeadowEndpoints_basic_tests.js +50 -2408
- package/test_support/bookstore-api-endpoint-exercises.paw +0 -0
- package/test_support/bookstore-configuration.json +28 -0
- package/test_support/bookstore-import-books-run.js +1 -0
- package/test_support/bookstore-import-books.js +215 -0
- package/test_support/bookstore-serve-meadow-endpoint-apis-IPC.js +138 -0
- package/test_support/bookstore-serve-meadow-endpoint-apis-run.js +6 -0
- package/test_support/bookstore-serve-meadow-endpoint-apis.js +129 -0
- package/test_support/data/books.csv +10001 -0
- package/test_support/model/ddl/BookStore.ddl +66 -0
- package/test_support/model/generated_diagram/README.md +1 -0
- package/test_support/model/generated_diagram/Stricture_Output.dot +13 -0
- package/test_support/model/generated_diagram/Stricture_Output.png +0 -0
- package/test_support/model/generated_documentation/Dictionary.md +18 -0
- package/test_support/model/generated_documentation/Model-Author.md +20 -0
- package/test_support/model/generated_documentation/Model-Book.md +26 -0
- package/test_support/model/generated_documentation/Model-BookAuthorJoin.md +14 -0
- package/test_support/model/generated_documentation/Model-BookPrice.md +25 -0
- package/test_support/model/generated_documentation/Model-Review.md +22 -0
- package/test_support/model/generated_documentation/ModelChangeTracking.md +17 -0
- package/test_support/model/generated_documentation/README.md +1 -0
- package/test_support/model/manual_scripts/DropTables.sql +5 -0
- package/test_support/model/manual_scripts/README.md +2 -0
- package/test_support/model/sql_create/BookStore-CreateDatabase.mysql.sql +116 -0
- package/test_support/model/sql_create/README.md +1 -0
- package/test_support/test_old/Tests.js +3243 -0
- package/test_support/test_old/untitled.js +88 -0
- package/source/Meadow-Authenticator.js +0 -31
- package/source/Meadow-Authorizers.js +0 -214
- package/source/Meadow-BehaviorModifications.js +0 -170
- package/source/Meadow-CommonServices.js +0 -206
- package/source/Meadow-MarshallSessionData.js +0 -64
- package/source/Restify-RouteParser.js +0 -114
- package/source/authorizers/Meadow-Authorizer-Allow.js +0 -17
- package/source/authorizers/Meadow-Authorizer-Deny.js +0 -17
- package/source/authorizers/Meadow-Authorizer-Mine.js +0 -47
- package/source/authorizers/Meadow-Authorizer-MyCustomer.js +0 -48
- package/source/crud/Meadow-Endpoint-BulkCreate.js +0 -67
- package/source/crud/Meadow-Endpoint-BulkUpdate.js +0 -74
- package/source/crud/Meadow-Endpoint-BulkUpsert.js +0 -76
- package/source/crud/Meadow-Endpoint-Count.js +0 -93
- package/source/crud/Meadow-Endpoint-CountBy.js +0 -101
- package/source/crud/Meadow-Endpoint-Create.js +0 -77
- package/source/crud/Meadow-Endpoint-Delete.js +0 -139
- package/source/crud/Meadow-Endpoint-Read.js +0 -109
- package/source/crud/Meadow-Endpoint-ReadDistinctList.js +0 -146
- package/source/crud/Meadow-Endpoint-ReadLiteList.js +0 -139
- package/source/crud/Meadow-Endpoint-ReadMax.js +0 -86
- package/source/crud/Meadow-Endpoint-ReadSelectList.js +0 -145
- package/source/crud/Meadow-Endpoint-Reads.js +0 -129
- package/source/crud/Meadow-Endpoint-ReadsBy.js +0 -155
- package/source/crud/Meadow-Endpoint-Undelete.js +0 -161
- package/source/crud/Meadow-Endpoint-Update.js +0 -80
- package/source/crud/Meadow-Endpoint-Upsert.js +0 -78
- package/source/crud/Meadow-Operation-Create.js +0 -105
- package/source/crud/Meadow-Operation-Update.js +0 -145
- package/source/crud/Meadow-Operation-Upsert.js +0 -106
- package/source/crud/Meadow-StreamRecordArray.js +0 -45
- package/source/schema/Meadow-Endpoint-New.js +0 -37
- package/source/schema/Meadow-Endpoint-Schema.js +0 -37
- package/source/schema/Meadow-Endpoint-Validate.js +0 -43
- package/test/Animal.json +0 -140
- package/test/MeadowEndpoints_disabledAuth_tests.js +0 -1325
- package/test/MeadowEndpoints_trustedSession_tests.js +0 -1731
- package/test/load/artillery-low.yml +0 -10
- package/test/load/cloud9setup.sh +0 -25
- package/test/load/package.json +0 -19
- package/test/load/test-schema-initializedatabase.sql +0 -29
- package/test/load/test-schema.json +0 -119
- package/test/load/test-server.js +0 -157
- package/test/scripts/InitializeDatabase-C9.sql +0 -7
- /package/{test/schemas → test_support/model}/json_schema/BookStore-Extended.json +0 -0
- /package/{test/schemas → test_support/model}/json_schema/BookStore-PICT.json +0 -0
- /package/{test/schemas → test_support/model}/json_schema/BookStore.json +0 -0
- /package/{test/schemas → test_support/model}/json_schema/README.md +0 -0
- /package/{test/schemas → test_support/model}/meadow_schema/BookStore-MeadowSchema-Author.json +0 -0
- /package/{test/schemas → test_support/model}/meadow_schema/BookStore-MeadowSchema-Book.json +0 -0
- /package/{test/schemas → test_support/model}/meadow_schema/BookStore-MeadowSchema-BookAuthorJoin.json +0 -0
- /package/{test/schemas → test_support/model}/meadow_schema/BookStore-MeadowSchema-BookPrice.json +0 -0
- /package/{test/schemas → test_support/model}/meadow_schema/BookStore-MeadowSchema-Review.json +0 -0
- /package/{test/schemas → test_support/model}/meadow_schema/README.md +0 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
class MeadowEndpointsControllerErrorBase
|
|
2
|
+
{
|
|
3
|
+
constructor(pController)
|
|
4
|
+
{
|
|
5
|
+
this._Controller = pController;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// Get the error object
|
|
9
|
+
getError(pMessage, pStatusCode, pSuppressSoftwareTrace)
|
|
10
|
+
{
|
|
11
|
+
let tmpError = new Error(pMessage);
|
|
12
|
+
|
|
13
|
+
// Default the error status code to 400 if none is passed
|
|
14
|
+
tmpError.StatusCode = (typeof(pStatusCode) == 'number') ? pStatusCode : 400;
|
|
15
|
+
// This suppresses the stack trace from being sent back or logged.
|
|
16
|
+
// And by default it does not send a stack trace, as we expect errors created this way to be protocol, schema or data related.
|
|
17
|
+
tmpError.SuppressSoftwareTrace = (typeof(pSuppressSoftwareTrace) != 'undefined') ? pSuppressSoftwareTrace : true;
|
|
18
|
+
|
|
19
|
+
return tmpError;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Handle an error if set -- some errors don't send the response back because they aren't fully errory errors.
|
|
23
|
+
handleErrorIfSet(pRequest, pRequestState, pResponse, pError, fCallback)
|
|
24
|
+
{
|
|
25
|
+
if (pError)
|
|
26
|
+
{
|
|
27
|
+
return this.sendError(pRequest, pRequestState, pResponse, pError, fCallback);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return fCallback();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Send an error object
|
|
34
|
+
sendError(pRequest, pRequestState, pResponse, pError, fCallback)
|
|
35
|
+
{
|
|
36
|
+
this._Controller.log.logRequestError(pRequest, pRequestState, pError);
|
|
37
|
+
|
|
38
|
+
// TODO: Detect if we've already sent headers?
|
|
39
|
+
if (!this._Controller.ControllerOptions.SendErrorStatusCodes)
|
|
40
|
+
{
|
|
41
|
+
let tmpStatusCode = (pError.hasOwnProperty('StatusCode')) ? pError.StatusCode : 500;
|
|
42
|
+
pResponse.status(tmpStatusCode);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let tmpResponseObject = (
|
|
46
|
+
{
|
|
47
|
+
Error:pError.message,
|
|
48
|
+
StatusCode:pError.StatusCode
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
tmpResponseObject = this._Controller.ErrorHandler.prepareRequestContextOutputObject(tmpResponseObject, pRequest, pRequestState, pError);
|
|
52
|
+
|
|
53
|
+
pResponse.send(tmpResponseObject);
|
|
54
|
+
|
|
55
|
+
fCallback(pError);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// This looks for some generic markers in the request state and puts them into a log or send object
|
|
59
|
+
prepareRequestContextOutputObject(pObjectToPopulate, pRequest, pRequestState, pError)
|
|
60
|
+
{
|
|
61
|
+
// Internally created errors supress stack traces
|
|
62
|
+
if (pError)
|
|
63
|
+
{
|
|
64
|
+
pObjectToPopulate.Error = pError.message;
|
|
65
|
+
pObjectToPopulate.Code = pError.code;
|
|
66
|
+
pObjectToPopulate.StatusCode = pError.StatusCode;
|
|
67
|
+
|
|
68
|
+
if (!pError.SuppressSoftwareTrace)
|
|
69
|
+
{
|
|
70
|
+
pObjectToPopulate.Stack = pError.stack;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (pRequestState.hasOwnProperty('Record'))
|
|
74
|
+
{
|
|
75
|
+
pObjectToPopulate.Record = pRequestState.Record;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (pRequestState.hasOwnProperty('Query') && (typeof(pRequestState.Query) == 'object'))
|
|
79
|
+
{
|
|
80
|
+
if (pRequestState.Query.query)
|
|
81
|
+
{
|
|
82
|
+
if (typeof(pRequestState.Query.query.body) == 'string')
|
|
83
|
+
{
|
|
84
|
+
pObjectToPopulate.Query = pRequestState.Query.query.body;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if ((typeof(pRequestState.Query.query.parameters) == 'object'))
|
|
88
|
+
{
|
|
89
|
+
pObjectToPopulate.QueryParameters = pRequestState.Query.query.parameters;
|
|
90
|
+
|
|
91
|
+
pObjectToPopulate.RebuiltQueryString = (typeof(pObjectToPopulate.Query) == 'string') ? pObjectToPopulate.Query : '';
|
|
92
|
+
|
|
93
|
+
// This gnarly bit of code attempts to reconstruct a non prepared string version of the query, to help.
|
|
94
|
+
let tmpQueryParameterSet = Object.keys(pObjectToPopulate.QueryParameters);
|
|
95
|
+
for (let i = 0; i < tmpQueryParameterSet.length; i++)
|
|
96
|
+
{
|
|
97
|
+
switch(typeof(tmpQueryParameterSet[i]))
|
|
98
|
+
{
|
|
99
|
+
case 'number':
|
|
100
|
+
pObjectToPopulate.RebuiltQueryString = pObjectToPopulate.RebuiltQueryString.replace(new RegExp(`:${tmpQueryParameterSet[i]}\\b`, 'g'), `'${pObjectToPopulate.QueryParameters[tmpQueryParameterSet[i]]}'`);
|
|
101
|
+
break;
|
|
102
|
+
case 'string':
|
|
103
|
+
// TODO: This may need more ... nuance...
|
|
104
|
+
default:
|
|
105
|
+
pObjectToPopulate.RebuiltQueryString = pObjectToPopulate.RebuiltQueryString.replace(new RegExp(`:${tmpQueryParameterSet[i]}\\b`,'g'), pObjectToPopulate.QueryParameters[tmpQueryParameterSet[i]]);
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return pObjectToPopulate;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = MeadowEndpointsControllerErrorBase;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
class MeadowEndpointsControllerLogBase
|
|
2
|
+
{
|
|
3
|
+
constructor(pController)
|
|
4
|
+
{
|
|
5
|
+
this._Controller = pController;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// This is called for every successful request log line. Be careful what you add in overloads!
|
|
9
|
+
prepareLogData(pRequest, pRequestState, pLogData)
|
|
10
|
+
{
|
|
11
|
+
// TODO: Discuss if these should be configurations
|
|
12
|
+
if (pRequestState.hasOwnProperty('Record'))
|
|
13
|
+
{
|
|
14
|
+
if (pRequestState.Record.hasOwnProperty(this._Controller.DAL.defaultIdentifier))
|
|
15
|
+
{
|
|
16
|
+
pLogData[this._Controller.DAL.defaultIdentifier] = pRequestState.Record[this._Controller.DAL.defaultIdentifier];
|
|
17
|
+
}
|
|
18
|
+
if (pRequestState.Record.hasOwnProperty(this._Controller.DAL.defaultGUIdentifier))
|
|
19
|
+
{
|
|
20
|
+
pLogData[this._Controller.DAL.defaultGUIdentifier] = pRequestState.Record[this._Controller.DAL.defaultGUIdentifier];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (pRequestState.hasOwnProperty('UpdatedRecords'))
|
|
24
|
+
{
|
|
25
|
+
pLogData.UpdatedRecordCount = pRequestState.UpdatedRecords.length;
|
|
26
|
+
}
|
|
27
|
+
if (pRequestState.hasOwnProperty('CreatedRecords'))
|
|
28
|
+
{
|
|
29
|
+
pLogData.UpdatedRecordCount = pRequestState.CreatedRecords.length;
|
|
30
|
+
}
|
|
31
|
+
if (pRequestState.hasOwnProperty('UpsertedRecords'))
|
|
32
|
+
{
|
|
33
|
+
pLogData.UpdatedRecordCount = pRequestState.UpsertedRecords.length;
|
|
34
|
+
}
|
|
35
|
+
return pLogData;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// This is called whenever an endpoint is completed successfully
|
|
39
|
+
requestCompletedSuccessfully(pRequest, pRequestState, pActionSummary)
|
|
40
|
+
{
|
|
41
|
+
let tmpLogData = (
|
|
42
|
+
{
|
|
43
|
+
SessionID: pRequestState.SessionData.SessionID,
|
|
44
|
+
RequestID: pRequest.RequestUUID,
|
|
45
|
+
RequestURL: pRequest.url,
|
|
46
|
+
Scope: this._Controller.DAL.scope,
|
|
47
|
+
Action: `${this._Controller.DAL.scope}-${pRequestState.Verb}`,
|
|
48
|
+
Verb: pRequestState.Verb
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
this._Controller.log.info(pActionSummary, this.prepareLogData(pRequest, pRequestState, tmpLogData));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// This is called whenever an endpoint is completed successfully
|
|
55
|
+
logRequestError(pRequest, pRequestState, pError)
|
|
56
|
+
{
|
|
57
|
+
let tmpErrorLogData = (
|
|
58
|
+
{
|
|
59
|
+
SessionID: pRequestState.SessionData.SessionID,
|
|
60
|
+
RequestID: pRequest.RequestUUID,
|
|
61
|
+
RequestURL: pRequest.url,
|
|
62
|
+
Scope: this._Controller.DAL.scope,
|
|
63
|
+
Action: `${this._Controller.DAL.scope}-${pRequestState.Verb}`,
|
|
64
|
+
Verb: pRequestState.Verb,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
tmpErrorLogData = this._Controller.ErrorHandler.prepareRequestContextOutputObject(tmpErrorLogData, pRequest, pRequestState, pError);
|
|
68
|
+
|
|
69
|
+
this._Controller.log.error(pError.message, this.prepareLogData(pRequest, pRequestState, tmpErrorLogData));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
trace(pLogText, pLogObject)
|
|
73
|
+
{
|
|
74
|
+
this._Controller.DAL.log.trace(pLogText, pLogObject);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
debug(pLogText, pLogObject)
|
|
78
|
+
{
|
|
79
|
+
this._Controller.DAL.log.debug(pLogText, pLogObject);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
info(pLogText, pLogObject)
|
|
83
|
+
{
|
|
84
|
+
this._Controller.DAL.log.info(pLogText, pLogObject);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
warn(pLogText, pLogObject)
|
|
88
|
+
{
|
|
89
|
+
this._Controller.DAL.log.warn(pLogText, pLogObject);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
error(pLogText, pLogObject)
|
|
93
|
+
{
|
|
94
|
+
this._Controller.DAL.log.error(pLogText, pLogObject);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
fatal(pLogText, pLogObject)
|
|
98
|
+
{
|
|
99
|
+
this._Controller.DAL.log.fatal(pLogText, pLogObject);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = MeadowEndpointsControllerLogBase;
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Meadow Endpoint Utility Class - Parse a Filter String and put it into a Query.
|
|
3
|
+
*
|
|
4
|
+
* @license MIT
|
|
5
|
+
*
|
|
6
|
+
* @author Steven Velozo <steven@velozo.com>
|
|
7
|
+
* @module Meadow
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Parse GET-passed Filter Strings, turn the results into proper Meadow query stanzas
|
|
11
|
+
|
|
12
|
+
Take the filter and return an array of filter instructions
|
|
13
|
+
Basic instruction anatomy:
|
|
14
|
+
INSTRUCTION~FIELD~OPERATOR~VALUE
|
|
15
|
+
FOP - Filter Open Paren
|
|
16
|
+
FOP~0~(~0
|
|
17
|
+
FCP - Filter Close Paren
|
|
18
|
+
FCP~0~)~0
|
|
19
|
+
FBV - Filter By Value (left-side AND connected)
|
|
20
|
+
FBV~Category~EQ~Books
|
|
21
|
+
Possible comparisons:
|
|
22
|
+
* EQ - Equals To (=)
|
|
23
|
+
* NE - Not Equals To (!=)
|
|
24
|
+
* GT - Greater Than (>)
|
|
25
|
+
* GE - Greater Than or Equals To (>=)
|
|
26
|
+
* LT - Less Than (<)
|
|
27
|
+
* LE - Less Than or Equals To (<=)
|
|
28
|
+
* LK - Like (Like)
|
|
29
|
+
* IN - Is NULL
|
|
30
|
+
* NN - Is NOT NULL
|
|
31
|
+
* INN - IN list
|
|
32
|
+
FBVOR - Filter By Value (left-side OR connected)
|
|
33
|
+
FBL - Filter By List (value list, separated by commas)
|
|
34
|
+
FBL~Category~EQ~Books,Movies
|
|
35
|
+
FBD - Filter by Date (exclude time)
|
|
36
|
+
FBD~UpdateDate~EQ~2015-10-01
|
|
37
|
+
FSF - Filter Sort Field
|
|
38
|
+
FSF~Category~ASC~0
|
|
39
|
+
FSF~Category~DESC~0
|
|
40
|
+
FDST - Filter by Distinct (adds distinct keyword to Read and Count queries)
|
|
41
|
+
FDST~0~0~0~
|
|
42
|
+
|
|
43
|
+
This means: FBV~Category~EQ~Books~FBV~PublishedYear~GT~2000~FSF~PublishedYear~DESC~0
|
|
44
|
+
Filters down to ALL BOOKS PUBLUSHED AFTER 2000 IN DESCENDING ORDER
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
class MeadowEndpointsFilterParser
|
|
48
|
+
{
|
|
49
|
+
constructor(pController)
|
|
50
|
+
{
|
|
51
|
+
this._Controller = pController;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Get the comparison operator for use in a query stanza
|
|
55
|
+
getFilterComparisonOperator(pFilterOperator)
|
|
56
|
+
{
|
|
57
|
+
let tmpOperator = '=';
|
|
58
|
+
switch(pFilterOperator)
|
|
59
|
+
{
|
|
60
|
+
case 'EQ':
|
|
61
|
+
tmpOperator = '=';
|
|
62
|
+
break;
|
|
63
|
+
case 'NE':
|
|
64
|
+
tmpOperator = '!=';
|
|
65
|
+
break;
|
|
66
|
+
case 'GT':
|
|
67
|
+
tmpOperator = '>';
|
|
68
|
+
break;
|
|
69
|
+
case 'GE':
|
|
70
|
+
tmpOperator = '>=';
|
|
71
|
+
break;
|
|
72
|
+
case 'LT':
|
|
73
|
+
tmpOperator = '<';
|
|
74
|
+
break;
|
|
75
|
+
case 'LE':
|
|
76
|
+
tmpOperator = '<=';
|
|
77
|
+
break;
|
|
78
|
+
case 'LK':
|
|
79
|
+
tmpOperator = 'LIKE';
|
|
80
|
+
break;
|
|
81
|
+
case 'NLK':
|
|
82
|
+
tmpOperator = 'NOT LIKE';
|
|
83
|
+
break;
|
|
84
|
+
case 'IN':
|
|
85
|
+
tmpOperator = 'IS NULL';
|
|
86
|
+
break;
|
|
87
|
+
case 'NN':
|
|
88
|
+
tmpOperator = 'IS NOT NULL';
|
|
89
|
+
break;
|
|
90
|
+
case 'INN':
|
|
91
|
+
tmpOperator = 'IN';
|
|
92
|
+
break;
|
|
93
|
+
case 'FOP':
|
|
94
|
+
tmpOperator = '(';
|
|
95
|
+
break;
|
|
96
|
+
case 'FCP':
|
|
97
|
+
tmpOperator = ')';
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
return tmpOperator;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
addFilterStanzaToQuery(pFilterStanza, pQuery)
|
|
104
|
+
{
|
|
105
|
+
if (!pFilterStanza.Instruction)
|
|
106
|
+
{
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
switch(pFilterStanza.Instruction)
|
|
111
|
+
{
|
|
112
|
+
case 'FBV': // Filter by Value (left-side AND)
|
|
113
|
+
pQuery.addFilter(pFilterStanza.Field, pFilterStanza.Value, this.getFilterComparisonOperator(pFilterStanza.Operator), 'AND');
|
|
114
|
+
break;
|
|
115
|
+
|
|
116
|
+
case 'FBVOR': // Filter by Value (left-side OR)
|
|
117
|
+
pQuery.addFilter(pFilterStanza.Field, pFilterStanza.Value, this.getFilterComparisonOperator(pFilterStanza.Operator), 'OR');
|
|
118
|
+
break;
|
|
119
|
+
|
|
120
|
+
case 'FBL': // Filter by List (left-side AND)
|
|
121
|
+
// Just split the value by comma for now. May want to revisit better characters or techniques later.
|
|
122
|
+
pQuery.addFilter(pFilterStanza.Field, pFilterStanza.Value.split(','), this.getFilterComparisonOperator(pFilterStanza.Operator), 'AND');
|
|
123
|
+
break;
|
|
124
|
+
|
|
125
|
+
case 'FBLOR': // Filter by List (left-side OR)
|
|
126
|
+
// Just split the value by comma for now. May want to revisit better characters or techniques later.
|
|
127
|
+
pQuery.addFilter(pFilterStanza.Field, pFilterStanza.Value.split(','), this.getFilterComparisonOperator(pFilterStanza.Operator), 'OR');
|
|
128
|
+
break;
|
|
129
|
+
|
|
130
|
+
case 'FBD': // Filter by Date (exclude time)
|
|
131
|
+
pQuery.addFilter(`DATE(${pFilterStanza.Field})`, pFilterStanza.Value.split(','), this.getFilterComparisonOperator(pFilterStanza.Operator), 'AND', pFilterStanza.Field);
|
|
132
|
+
break;
|
|
133
|
+
|
|
134
|
+
case 'FBDOR': // Filter by Date (exclude time)
|
|
135
|
+
pQuery.addFilter(`DATE(${pFilterStanza.Field})`, pFilterStanza.Value.split(','), this.getFilterComparisonOperator(pFilterStanza.Operator), 'OR', pFilterStanza.Field);
|
|
136
|
+
break;
|
|
137
|
+
|
|
138
|
+
case 'FSF': // Filter Sort Field
|
|
139
|
+
const tmpSortDirection = (pFilterStanza.Operator === 'DESC') ? 'Descending' : 'Ascending';
|
|
140
|
+
pQuery.addSort({ Column: pFilterStanza.Field, Direction: tmpSortDirection });
|
|
141
|
+
break;
|
|
142
|
+
|
|
143
|
+
case 'FOP': // Filter Open Paren
|
|
144
|
+
pQuery.addFilter('', '', '(');
|
|
145
|
+
break;
|
|
146
|
+
|
|
147
|
+
case 'FCP': // Filter Close Paren
|
|
148
|
+
pQuery.addFilter('', '', ')');
|
|
149
|
+
break;
|
|
150
|
+
|
|
151
|
+
case 'FDST': // Filter Distinct
|
|
152
|
+
// ensure we don't break if using an older foxhound version
|
|
153
|
+
if (pQuery.setDistinct)
|
|
154
|
+
{
|
|
155
|
+
pQuery.setDistinct(true);
|
|
156
|
+
}
|
|
157
|
+
break;
|
|
158
|
+
|
|
159
|
+
default:
|
|
160
|
+
//console.log('Unparsable filter stanza.');
|
|
161
|
+
return false;
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Be paranoid about the instruction
|
|
166
|
+
pFilterStanza.Instruction = false;
|
|
167
|
+
return true;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
parseFilter(pFilterString, pQuery)
|
|
171
|
+
{
|
|
172
|
+
if (typeof(pFilterString) !== 'string')
|
|
173
|
+
{
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const tmpFilterTerms = pFilterString.split('~');
|
|
178
|
+
|
|
179
|
+
if (tmpFilterTerms.length < 4)
|
|
180
|
+
{
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
let tmpFilterStanza = { Instruction: false };
|
|
185
|
+
|
|
186
|
+
for (let i = 0; i < tmpFilterTerms.length; i++)
|
|
187
|
+
{
|
|
188
|
+
switch(i % 4)
|
|
189
|
+
{
|
|
190
|
+
case 0: // INSTRUCTION
|
|
191
|
+
this.addFilterStanzaToQuery(tmpFilterStanza, pQuery);
|
|
192
|
+
//console.log(i+' Instruction: '+tmpFilterTerms[i]);
|
|
193
|
+
tmpFilterStanza = (
|
|
194
|
+
{
|
|
195
|
+
Instruction: tmpFilterTerms[i],
|
|
196
|
+
Field: '',
|
|
197
|
+
Operator: '',
|
|
198
|
+
Value: ''
|
|
199
|
+
});
|
|
200
|
+
break;
|
|
201
|
+
|
|
202
|
+
case 1: // FIELD
|
|
203
|
+
//console.log(i+' Field: '+tmpFilterTerms[i]);
|
|
204
|
+
tmpFilterStanza.Field = tmpFilterTerms[i];
|
|
205
|
+
break;
|
|
206
|
+
|
|
207
|
+
case 2: // OPERATOR
|
|
208
|
+
//console.log(i+' Operator: '+tmpFilterTerms[i]);
|
|
209
|
+
tmpFilterStanza.Operator = tmpFilterTerms[i];
|
|
210
|
+
break;
|
|
211
|
+
|
|
212
|
+
case 3: // VALUE
|
|
213
|
+
//console.log(i+' Value: '+tmpFilterTerms[i]);
|
|
214
|
+
tmpFilterStanza.Value = tmpFilterTerms[i];
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
this.addFilterStanzaToQuery(tmpFilterStanza, pQuery);
|
|
220
|
+
|
|
221
|
+
return true;
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
module.exports = MeadowEndpointsFilterParser;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
class MeadowEndpointsSessionMarshaler
|
|
2
|
+
{
|
|
3
|
+
constructor(pController)
|
|
4
|
+
{
|
|
5
|
+
this._Controller = pController;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
getSessionData(pRequest)
|
|
9
|
+
{
|
|
10
|
+
let tmpSession = Object.assign({}, this._Controller.settings.MeadowEndpointsDefaultSessionObject);
|
|
11
|
+
|
|
12
|
+
switch (this._Controller.settings.MeadowEndpointsSessionDataSource || 'Request')
|
|
13
|
+
{
|
|
14
|
+
default:
|
|
15
|
+
this._LogController.warn(`Unknown session source configured: ${_SessionDataSource} - defaulting to Request for backward compatibility`);
|
|
16
|
+
case 'Request':
|
|
17
|
+
// noop - already set by orator-session
|
|
18
|
+
tmpSession = this._Controller.extend(tmpSession, pRequest.UserSession);
|
|
19
|
+
break;
|
|
20
|
+
case 'None':
|
|
21
|
+
break;
|
|
22
|
+
case 'Header':
|
|
23
|
+
try
|
|
24
|
+
{
|
|
25
|
+
const tmpHeaderSessionString = pRequest.headers['x-trusted-session'];
|
|
26
|
+
if (!tmpHeaderSessionString)
|
|
27
|
+
{
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
tmpHeaderSession = JSON.parse(tmpHeaderSessionString);
|
|
31
|
+
tmpSession = this._Controller.extend(tmpSession, pRequest.tmpHeaderSession);
|
|
32
|
+
}
|
|
33
|
+
catch (pError)
|
|
34
|
+
{
|
|
35
|
+
this._LogController.error(`Meadow Endpoints attempted to process a Header Session String with value [${tmpHeaderSessionString}] and failed -- likely culprit is bad JSON.`)
|
|
36
|
+
}
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Do we keep this here for backwards compatibility?
|
|
41
|
+
// Yes this makes sense here.
|
|
42
|
+
pRequest.UserSession = tmpSession;
|
|
43
|
+
|
|
44
|
+
return tmpSession;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = MeadowEndpointsSessionMarshaler;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Meadow Endpoint Streamer - Stream an array of recods as JSON to an output stream.
|
|
3
|
+
*/
|
|
4
|
+
const libAsyncEachSeries = require('async/eachSeries');
|
|
5
|
+
const JSONStream = require('JSONStream');
|
|
6
|
+
|
|
7
|
+
class MeadowEndpointsStreamRecordArray
|
|
8
|
+
{
|
|
9
|
+
constructor(pController)
|
|
10
|
+
{
|
|
11
|
+
this._Controller = pController;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
chunk(pInput, pChunkSize, pChunkCache)
|
|
15
|
+
{
|
|
16
|
+
let tmpInputArray = [...pInput];
|
|
17
|
+
// Note lodash defaults to 1, underscore defaults to 0
|
|
18
|
+
let tmpChunkSize = (typeof(pChunkSize) == 'number') ? pChunkSize : 0;
|
|
19
|
+
let tmpChunkCache = (typeof(pChunkCache) != 'undefined') ? pChunkCache : [];
|
|
20
|
+
|
|
21
|
+
if (tmpChunkSize <= 0)
|
|
22
|
+
{
|
|
23
|
+
return tmpChunkCache;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
while (tmpInputArray.length)
|
|
27
|
+
{
|
|
28
|
+
tmpChunkCache.push(tmpInputArray.splice(0, tmpChunkSize));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return tmpChunkCache;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
streamRecordArray(pResponse, pRecords, fCallback)
|
|
35
|
+
{
|
|
36
|
+
// for meadow invoke, writeHead isn't provided, so just call send(), which is the shim it uses...
|
|
37
|
+
// also, for small arrays, don't bother with the async serialization; this threshold could use tuning
|
|
38
|
+
if (!pResponse.writeHead || !Array.isArray(pRecords) || pRecords.length < 2500)
|
|
39
|
+
{
|
|
40
|
+
pResponse.send(pRecords);
|
|
41
|
+
return fCallback();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
pResponse.writeHead(200,
|
|
45
|
+
{
|
|
46
|
+
'content-type': 'application/json',
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const recordJsonMarshaller = JSONStream.stringify();
|
|
50
|
+
recordJsonMarshaller.pipe(pResponse);
|
|
51
|
+
|
|
52
|
+
// we write the records in chunks; doing one per loop is very inefficient, doing all is the same as not doing this at all
|
|
53
|
+
libAsyncEachSeries(this.chunk(pRecords, 1000), (pRecordChunk, fNext) =>
|
|
54
|
+
{
|
|
55
|
+
pRecordChunk.forEach(recordJsonMarshaller.write);
|
|
56
|
+
setImmediate(fNext);
|
|
57
|
+
},
|
|
58
|
+
(error) =>
|
|
59
|
+
{
|
|
60
|
+
recordJsonMarshaller.end();
|
|
61
|
+
fCallback(error);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = MeadowEndpointsStreamRecordArray;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple browser shim loader - assign the npm module to a window global automatically
|
|
3
|
+
*
|
|
4
|
+
* @license MIT
|
|
5
|
+
* @author <steven@velozo.com>
|
|
6
|
+
*/
|
|
7
|
+
var libNPMModuleWrapper = require('./Meadow-Endpoints.js');
|
|
8
|
+
|
|
9
|
+
if ((typeof(window) === 'object') && !window.hasOwnProperty('MeadowEndpoints'))
|
|
10
|
+
{
|
|
11
|
+
window.MeadowEndpoints = libNPMModuleWrapper;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = libNPMModuleWrapper;
|