meadow-endpoints 4.0.15 → 4.0.17

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 (33) hide show
  1. package/dist/indoctrinate_content_staging/Indoctrinate-Catalog-AppData.json +1286 -1065
  2. package/dist/meadow-endpoints.js +285 -148
  3. package/dist/meadow-endpoints.js.map +1 -1
  4. package/dist/meadow-endpoints.min.js +7 -7
  5. package/dist/meadow-endpoints.min.js.map +1 -1
  6. package/docs/_version.json +7 -0
  7. package/docs/css/docuserve.css +277 -23
  8. package/docs/index.html +2 -2
  9. package/docs/retold-catalog.json +13 -1
  10. package/docs/retold-keyword-index.json +1 -1
  11. package/package.json +8 -7
  12. package/source/Meadow-Endpoints-Browser-Shim.js +4 -1
  13. package/source/Meadow-Endpoints.js +6 -6
  14. package/source/controller/Meadow-Endpoints-Controller-Base.js +6 -6
  15. package/source/controller/components/Meadow-Endpoints-Controller-BehaviorInjection.js +11 -11
  16. package/source/controller/components/Meadow-Endpoints-Controller-Error.js +23 -15
  17. package/source/controller/components/Meadow-Endpoints-Controller-Log.js +9 -9
  18. package/source/controller/utility/Meadow-Endpoints-Filter-Parser.js +16 -16
  19. package/source/controller/utility/Meadow-Endpoints-Session-Marshaler.js +42 -42
  20. package/source/controller/utility/Meadow-Endpoints-Stream-RecordArray.js +6 -6
  21. package/source/endpoints/create/Meadow-Endpoint-BulkCreate.js +6 -0
  22. package/source/endpoints/create/Meadow-Endpoint-Create.js +8 -0
  23. package/source/endpoints/create/Meadow-Operation-Create.js +1 -1
  24. package/source/endpoints/delete/Meadow-Endpoint-Delete.js +6 -0
  25. package/source/endpoints/delete/Meadow-Endpoint-Undelete.js +5 -0
  26. package/source/endpoints/read/Meadow-Endpoint-ReadDistinctList.js +7 -0
  27. package/source/endpoints/read/Meadow-Endpoint-ReadLiteList.js +6 -0
  28. package/source/endpoints/read/Meadow-Endpoint-ReadSelectList.js +6 -0
  29. package/source/endpoints/schema/Meadow-Endpoint-Validate.js +1 -1
  30. package/source/endpoints/update/Meadow-Endpoint-BulkUpdate.js +2 -0
  31. package/source/endpoints/update/Meadow-Operation-Update.js +10 -0
  32. package/source/endpoints/upsert/Meadow-Endpoint-BulkUpsert.js +3 -1
  33. package/test/MeadowEndpoints_basic_tests.js +1069 -0
@@ -11,24 +11,24 @@ const { parse } = require('meadow-filter');
11
11
 
12
12
  class MeadowEndpointsFilterParser
13
13
  {
14
- /**
15
- * @param {import('../Meadow-Endpoints-Controller-Base.js')} pController - The controller instance to which this parser belongs.
16
- */
17
- constructor(pController)
14
+ /**
15
+ * @param {import('../Meadow-Endpoints-Controller-Base.js')} pController - The controller instance to which this parser belongs.
16
+ */
17
+ constructor(pController)
18
18
  {
19
- this._Controller = pController;
20
- }
19
+ this._Controller = pController;
20
+ }
21
21
 
22
- /**
23
- * @param {string} pFilterString - The filter string to parse.
24
- * @param {any} pQuery - The foxhound query object to populate.
25
- *
26
- * @return {boolean} - True if the filter was parsed successfully, false otherwise.
27
- */
28
- parseFilter(pFilterString, pQuery)
29
- {
30
- return parse(pFilterString, pQuery);
31
- }
22
+ /**
23
+ * @param {string} pFilterString - The filter string to parse.
24
+ * @param {any} pQuery - The foxhound query object to populate.
25
+ *
26
+ * @return {boolean} - True if the filter was parsed successfully, false otherwise.
27
+ */
28
+ parseFilter(pFilterString, pQuery)
29
+ {
30
+ return parse(pFilterString, pQuery);
31
+ }
32
32
  }
33
33
 
34
34
  module.exports = MeadowEndpointsFilterParser;
@@ -1,52 +1,52 @@
1
1
  class MeadowEndpointsSessionMarshaler
2
2
  {
3
- /**
4
- * @param {import('../Meadow-Endpoints-Controller-Base.js')} pController
5
- */
6
- constructor(pController)
3
+ /**
4
+ * @param {import('../Meadow-Endpoints-Controller-Base.js')} pController
5
+ */
6
+ constructor(pController)
7
7
  {
8
- this._Controller = pController;
9
- }
8
+ this._Controller = pController;
9
+ }
10
10
 
11
- getSessionData(pRequest)
12
- {
13
- let tmpSession = Object.assign({}, this._Controller.settings.MeadowEndpointsDefaultSessionObject);
14
- let tmpHeaderSessionString;
11
+ getSessionData(pRequest)
12
+ {
13
+ let tmpSession = Object.assign({}, this._Controller.settings.MeadowEndpointsDefaultSessionObject);
14
+ let tmpHeaderSessionString;
15
15
 
16
- switch (this._Controller.settings.MeadowEndpointsSessionDataSource || 'Request')
17
- {
18
- default:
19
- this._Controller.log.warn(`Unknown session source configured: ${this._Controller.settings.MeadowEndpointsSessionDataSource} - defaulting to Request for backward compatibility`);
20
- case 'Request':
21
- // noop - already set by orator-session
22
- tmpSession = this._Controller.extend(tmpSession, pRequest.UserSession);
23
- break;
24
- case 'None':
25
- break;
26
- case 'Header':
27
- try
28
- {
29
- tmpHeaderSessionString = pRequest.headers['x-trusted-session'];
30
- if (!tmpHeaderSessionString)
31
- {
32
- break;
33
- }
34
- const tmpHeaderSession = JSON.parse(tmpHeaderSessionString);
35
- tmpSession = this._Controller.extend(tmpSession, tmpHeaderSession);
36
- }
37
- catch (pError)
38
- {
39
- this._Controller.log.error(`Meadow Endpoints attempted to process a Header Session String with value [${tmpHeaderSessionString}] and failed -- likely culprit is bad JSON.`)
40
- }
41
- break;
42
- }
16
+ switch (this._Controller.settings.MeadowEndpointsSessionDataSource || 'Request')
17
+ {
18
+ default:
19
+ this._Controller.log.warn(`Unknown session source configured: ${this._Controller.settings.MeadowEndpointsSessionDataSource} - defaulting to Request for backward compatibility`);
20
+ case 'Request':
21
+ // noop - already set by orator-session
22
+ tmpSession = this._Controller.extend(tmpSession, pRequest.UserSession);
23
+ break;
24
+ case 'None':
25
+ break;
26
+ case 'Header':
27
+ try
28
+ {
29
+ tmpHeaderSessionString = pRequest.headers['x-trusted-session'];
30
+ if (!tmpHeaderSessionString)
31
+ {
32
+ break;
33
+ }
34
+ const tmpHeaderSession = JSON.parse(tmpHeaderSessionString);
35
+ tmpSession = this._Controller.extend(tmpSession, tmpHeaderSession);
36
+ }
37
+ catch (pError)
38
+ {
39
+ this._Controller.log.error(`Meadow Endpoints attempted to process a Header Session String with value [${tmpHeaderSessionString}] and failed -- likely culprit is bad JSON.`)
40
+ }
41
+ break;
42
+ }
43
43
 
44
- // Do we keep this here for backwards compatibility?
45
- // Yes this makes sense here.
46
- pRequest.UserSession = tmpSession;
44
+ // Do we keep this here for backwards compatibility?
45
+ // Yes this makes sense here.
46
+ pRequest.UserSession = tmpSession;
47
47
 
48
- return tmpSession;
49
- }
48
+ return tmpSession;
49
+ }
50
50
  }
51
51
 
52
52
  module.exports = MeadowEndpointsSessionMarshaler;
@@ -6,13 +6,13 @@ const JSONStream = require('JSONStream');
6
6
 
7
7
  class MeadowEndpointsStreamRecordArray
8
8
  {
9
- /**
10
- * @param {import('../Meadow-Endpoints-Controller-Base.js')} pController
11
- */
12
- constructor(pController)
9
+ /**
10
+ * @param {import('../Meadow-Endpoints-Controller-Base.js')} pController
11
+ */
12
+ constructor(pController)
13
13
  {
14
- this._Controller = pController;
15
- }
14
+ this._Controller = pController;
15
+ }
16
16
 
17
17
  chunk(pInput, pChunkSize, pChunkCache)
18
18
  {
@@ -23,6 +23,12 @@ const doAPIEndpointBulkCreate = function(pRequest, pResponse, fNext)
23
23
 
24
24
  return fStageComplete();
25
25
  },
26
+ // Endpoint-level pre-request hook for bulk creates. Mirror of
27
+ // Create-PreRequest in the singular Create endpoint — fires
28
+ // after body-array validation and before any per-record
29
+ // operation. Use cases include bulk idempotency suppression /
30
+ // dedup across the incoming batch.
31
+ fBehaviorInjector(`CreateBulk-PreRequest`),
26
32
  fBehaviorInjector(`CreateBulk-PreOperation`),
27
33
  (fStageComplete) =>
28
34
  {
@@ -19,6 +19,14 @@ const doAPIEndpointCreate = function(pRequest, pResponse, fNext)
19
19
 
20
20
  return fStageComplete();
21
21
  },
22
+ // Endpoint-level pre-request hook. Runs after the body-type check
23
+ // but before any operation work, mirroring ME 2.x's
24
+ // Create-PreRequest stage. Use cases include idempotency
25
+ // suppression (e.g. look up by primary GUID and short-circuit
26
+ // if the row already exists). Handlers can abort the operation
27
+ // by calling fStageComplete with a truthy error or by fully
28
+ // writing pResponse and returning a sentinel.
29
+ fBehaviorInjector(`Create-PreRequest`),
22
30
  (fStageComplete) =>
23
31
  {
24
32
  doCreate.call(this, pRequest.body, pRequest, tmpRequestState, pResponse, fStageComplete);
@@ -19,7 +19,7 @@ const doCreate = function(pRecord, pRequest, pRequestState, pResponse, fCallback
19
19
  tmpRequestState.RecordToCreate = pRecord;
20
20
 
21
21
  //Make sure record gets created with a customerID
22
- if (!tmpRequestState.RecordToCreate.hasOwnProperty('IDCustomer') && this.DAL.jsonSchema.properties.hasOwnProperty('IDCustomer'))
22
+ if (!tmpRequestState.RecordToCreate.hasOwnProperty('IDCustomer') && this.DAL.jsonSchema && this.DAL.jsonSchema.properties && this.DAL.jsonSchema.properties.hasOwnProperty('IDCustomer'))
23
23
  {
24
24
  tmpRequestState.RecordToCreate.IDCustomer = tmpRequestState.SessionData.CustomerID || 0;
25
25
  }
@@ -55,6 +55,12 @@ const doAPIEndpointDelete = function(pRequest, pResponse, fNext)
55
55
  return fStageComplete(this.ErrorHandler.getError('Record not found.', 404));
56
56
  }
57
57
  tmpRequestState.Record = pRecord;
58
+ // Alias the loaded pre-delete row for symmetry
59
+ // with Update (see Meadow-Operation-Update.js).
60
+ // Post-op hooks that compare pre/post values can
61
+ // reliably read OriginalRecord without having to
62
+ // know which stage overwrote Record.
63
+ tmpRequestState.OriginalRecord = pRecord;
58
64
  return fStageComplete();
59
65
  });
60
66
  },
@@ -70,6 +70,11 @@ const doAPIEndpointUndelete = function(pRequest, pResponse, fNext)
70
70
  return fStageComplete(this.ErrorHandler.getError('Record not found.', 404));
71
71
  }
72
72
  tmpRequestState.Record = pRecord;
73
+ // Alias the loaded pre-undelete row for symmetry
74
+ // with Update / Delete (see their endpoints). Post-op
75
+ // hooks that compare pre/post values can reliably
76
+ // read OriginalRecord.
77
+ tmpRequestState.OriginalRecord = pRecord;
73
78
  return fStageComplete();
74
79
  });
75
80
  },
@@ -70,6 +70,13 @@ const doAPIEndpointReadDistinct = function(pRequest, pResponse, fNext)
70
70
  tmpRequestState.Records = pRecords;
71
71
  return fStageComplete();
72
72
  },
73
+ // Stage-specific post-op hook. Fires after DAL read but BEFORE
74
+ // the records are projected to distinct-column shape, so
75
+ // handlers can run against full rows. Separate from
76
+ // Reads-PostOperation so registering one doesn't unintentionally
77
+ // fire on the other. Hash matches the endpoint's action label
78
+ // (initializeRequestState(..., 'ReadDistinct')).
79
+ fBehaviorInjector(`ReadDistinct-PostOperation`),
73
80
  (fStageComplete) =>
74
81
  {
75
82
  tmpRequestState.ResultRecords = marshalDistinctList.call(this, tmpRequestState.Records, pRequest, tmpRequestState.DistinctColumns);
@@ -60,8 +60,14 @@ const doAPIEndpointReadLite = function(pRequest, pResponse, fNext)
60
60
  pRecords = [];
61
61
  }
62
62
  tmpRequestState.RawRecords = pRecords;
63
+ // Expose the loaded records under pRequestState.Records
64
+ // so post-op hooks operate on the same shape regular
65
+ // Reads uses. Marshalling to lite shape runs AFTER the
66
+ // hook so hooks see full rows.
67
+ tmpRequestState.Records = pRecords;
63
68
  return fStageComplete();
64
69
  },
70
+ fBehaviorInjector(`ReadsLite-PostOperation`),
65
71
  (fStageComplete) =>
66
72
  {
67
73
  tmpRequestState.Records = marshalLiteList.call(this, tmpRequestState.RawRecords, pRequest, (typeof(pRequest.params.ExtraColumns) === 'string') ? pRequest.params.ExtraColumns.split(',') : []);
@@ -54,6 +54,12 @@ const doAPIEndpointReadSelectList = function(pRequest, pResponse, fNext)
54
54
 
55
55
  return fStageComplete();
56
56
  },
57
+ // Stage-specific post-op hook. Fires after DAL read but
58
+ // BEFORE the records are projected to select-list
59
+ // (Hash/Value) shape, so handlers can run against full
60
+ // rows. Separate from Reads-PostOperation so registering
61
+ // one doesn't unintentionally fire on the other.
62
+ fBehaviorInjector(`ReadSelectList-PostOperation`),
57
63
  (fStageComplete) =>
58
64
  {
59
65
  tmpRequestState.SelectList = [];
@@ -27,7 +27,7 @@ const doAPIEndpointValidate = function(pRequest, pResponse, fNext)
27
27
  (fStageComplete) =>
28
28
  {
29
29
  pResponse.send(tmpRequestState.RecordValidation);
30
- this.log.requestCompletedSuccessfully(pRequest, tmpRequestState, `Validated Record for ${this.DAL.scope} - ${tmpRequestState.RecordValidation}`);
30
+ this.log.requestCompletedSuccessfully(pRequest, tmpRequestState, `Validated Record for ${this.DAL.scope} - ${JSON.stringify(tmpRequestState.RecordValidation)}`);
31
31
  return fStageComplete();
32
32
  }
33
33
  ],
@@ -23,6 +23,7 @@ const doAPIEndpointUpdate = function(pRequest, pResponse, fNext)
23
23
 
24
24
  return fStageComplete();
25
25
  },
26
+ fBehaviorInjector(`UpdateBulk-PreOperation`),
26
27
  (fStageComplete) =>
27
28
  {
28
29
  this.eachLimit(tmpRequestState.BulkRecords, 1,
@@ -31,6 +32,7 @@ const doAPIEndpointUpdate = function(pRequest, pResponse, fNext)
31
32
  doUpdate.call(this, pRecord, pRequest, tmpRequestState, pResponse, fCallback);
32
33
  }, fStageComplete);
33
34
  },
35
+ fBehaviorInjector(`UpdateBulk-PostOperation`),
34
36
  (fStageComplete) =>
35
37
  {
36
38
  return this.doStreamRecordArray(pResponse, tmpRequestState.UpdatedRecords, fStageComplete);
@@ -49,6 +49,14 @@ const doUpdate = function(pRecordToModify, pRequest, pRequestState, pResponse, f
49
49
  return fStageComplete(this.ErrorHandler.getError('Record not Found', 404));
50
50
  }
51
51
  tmpRequestState.Record = pRecord;
52
+ // Alias the loaded pre-update row under an
53
+ // unambiguous name. pRequestState.Record gets
54
+ // overwritten with the POST-update row later in
55
+ // this waterfall; OriginalRecord preserves the
56
+ // PRE-update reference for post-op hooks that
57
+ // need to compare before/after values (change
58
+ // logs, customer-boundary checks, etc.).
59
+ tmpRequestState.OriginalRecord = pRecord;
52
60
  return fStageComplete();
53
61
  });
54
62
  }
@@ -58,6 +66,7 @@ const doUpdate = function(pRecordToModify, pRequest, pRequestState, pResponse, f
58
66
  tmpRequestState.Query = this.DAL.query;
59
67
  return fStageComplete();
60
68
  },
69
+ fBehaviorInjector(`Update-PreOperation`),
61
70
  (fStageComplete) =>
62
71
  {
63
72
  tmpRequestState.Query.setIDUser(tmpRequestState.SessionData.UserID);
@@ -65,6 +74,7 @@ const doUpdate = function(pRecordToModify, pRequest, pRequestState, pResponse, f
65
74
 
66
75
  return fStageComplete();
67
76
  },
77
+ fBehaviorInjector(`Update-QueryConfiguration`),
68
78
  (fStageComplete) =>
69
79
  {
70
80
  this.DAL.doUpdate(tmpRequestState.Query,
@@ -28,6 +28,7 @@ const doAPIEndpointUpserts = function(pRequest, pResponse, fNext)
28
28
 
29
29
  return fStageComplete();
30
30
  },
31
+ fBehaviorInjector(`UpsertBulk-PreOperation`),
31
32
  (fStageComplete) =>
32
33
  {
33
34
  this.eachLimit(tmpRequestState.BulkRecords, 1,
@@ -36,13 +37,14 @@ const doAPIEndpointUpserts = function(pRequest, pResponse, fNext)
36
37
  doUpsert.call(this, pRecord, pRequest, tmpRequestState, pResponse, fCallback);
37
38
  }, fStageComplete);
38
39
  },
40
+ fBehaviorInjector(`UpsertBulk-PostOperation`),
39
41
  (fStageComplete) =>
40
42
  {
41
43
  return this.doStreamRecordArray(pResponse, marshalLiteList.call(this, tmpRequestState.UpsertedRecords, pRequest), fStageComplete);
42
44
  },
43
45
  (fStageComplete) =>
44
46
  {
45
- this.log.requestCompletedSuccessfully(pRequest, tmpRequestState, `Bulk upsert complete -- ${tmpRequestState.UpsertedRecords} records processed`);
47
+ this.log.requestCompletedSuccessfully(pRequest, tmpRequestState, `Bulk upsert complete -- ${tmpRequestState.UpsertedRecords.length} records processed`);
46
48
  return fStageComplete();
47
49
  }
48
50
  ], (pError) =>