meadow-endpoints 4.0.17 → 4.0.18

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.17",
3
+ "version": "4.0.18",
4
4
  "description": "Automatic API endpoints for Meadow data.",
5
5
  "main": "source/Meadow-Endpoints.js",
6
6
  "scripts": {
@@ -82,6 +82,7 @@ class MeadowEndpoints
82
82
 
83
83
  Upsert: require('./endpoints/upsert/Meadow-Endpoint-Upsert.js'),
84
84
  Upserts: require('./endpoints/upsert/Meadow-Endpoint-BulkUpsert.js'),
85
+ UpsertsDetailed: require('./endpoints/upsert/Meadow-Endpoint-BulkUpsertDetailed.js'),
85
86
 
86
87
  Delete: require('./endpoints/delete/Meadow-Endpoint-Delete.js'),
87
88
  Undelete: require('./endpoints/delete/Meadow-Endpoint-Undelete.js'),
@@ -211,6 +212,7 @@ class MeadowEndpoints
211
212
  this.connectRoute(pServiceServer, 'putWithBodyParser', `s`, this._Endpoints.Updates, `the internal behavior _Endpoints.Updates`);
212
213
  this.connectRoute(pServiceServer, 'putWithBodyParser', `/Upsert`, this._Endpoints.Upsert, `the internal behavior _Endpoints.Upsert`);
213
214
  this.connectRoute(pServiceServer, 'putWithBodyParser', `/Upserts`, this._Endpoints.Upserts, `the internal behavior _Endpoints.Upserts`);
215
+ this.connectRoute(pServiceServer, 'putWithBodyParser', `/Upserts/Detailed`, this._Endpoints.UpsertsDetailed, `the internal behavior _Endpoints.UpsertsDetailed`);
214
216
  }
215
217
  if (this._EnabledBehaviorSets.Delete)
216
218
  {
@@ -40,11 +40,35 @@ const doAPIEndpointUpserts = function(pRequest, pResponse, fNext)
40
40
  fBehaviorInjector(`UpsertBulk-PostOperation`),
41
41
  (fStageComplete) =>
42
42
  {
43
+ // Surface per-row error counts to the caller via response
44
+ // headers BEFORE streaming the success array. Without these,
45
+ // callers can only see "N records came back" and have no
46
+ // signal that other records were silently dropped (e.g.
47
+ // from a NOT NULL constraint or a column-too-long error).
48
+ // We keep the response body shape stable (bare array of
49
+ // upserted records) for back-compat — the headers are the
50
+ // non-breaking surface for the failure count + total.
51
+ let tmpInputCount = (tmpRequestState.BulkRecords && tmpRequestState.BulkRecords.length) || 0;
52
+ let tmpErrorCount = (tmpRequestState.ErrorRecords && tmpRequestState.ErrorRecords.length) || 0;
53
+ let tmpUpsertedCount = (tmpRequestState.UpsertedRecords && tmpRequestState.UpsertedRecords.length) || 0;
54
+ try
55
+ {
56
+ pResponse.header('X-Meadow-Upsert-Total', String(tmpInputCount));
57
+ pResponse.header('X-Meadow-Upsert-Succeeded', String(tmpUpsertedCount));
58
+ pResponse.header('X-Meadow-Upsert-Errored', String(tmpErrorCount));
59
+ }
60
+ catch (pHdrErr) { /* response.header may not exist on all servers; degrade silently */ }
61
+
43
62
  return this.doStreamRecordArray(pResponse, marshalLiteList.call(this, tmpRequestState.UpsertedRecords, pRequest), fStageComplete);
44
63
  },
45
64
  (fStageComplete) =>
46
65
  {
47
- this.log.requestCompletedSuccessfully(pRequest, tmpRequestState, `Bulk upsert complete -- ${tmpRequestState.UpsertedRecords.length} records processed`);
66
+ let tmpUpsertedCount = (tmpRequestState.UpsertedRecords && tmpRequestState.UpsertedRecords.length) || 0;
67
+ let tmpErrorCount = (tmpRequestState.ErrorRecords && tmpRequestState.ErrorRecords.length) || 0;
68
+ let tmpMessage = (tmpErrorCount > 0)
69
+ ? `Bulk upsert complete -- ${tmpUpsertedCount} records succeeded, ${tmpErrorCount} errored`
70
+ : `Bulk upsert complete -- ${tmpUpsertedCount} records processed`;
71
+ this.log.requestCompletedSuccessfully(pRequest, tmpRequestState, tmpMessage);
48
72
  return fStageComplete();
49
73
  }
50
74
  ], (pError) =>
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Meadow Endpoint - Upsert a set of Records, with detailed envelope response.
3
+ *
4
+ * Same upsert pipeline as Meadow-Endpoint-BulkUpsert.js, but the
5
+ * response body is an envelope:
6
+ *
7
+ * {
8
+ * "Counts": { "Total": N, "Succeeded": K, "Errored": M },
9
+ * "UpsertedRecords": [ ... lite-marshaled successes ... ],
10
+ * "ErrorRecords": [ { "Record": {...}, "Operation": "...", "Error": "..." }, ... ]
11
+ * }
12
+ *
13
+ * The vanilla `/Upserts` endpoint streams a bare array of successes for
14
+ * back-compat; this `/Upserts/Detailed` variant trades that compatibility
15
+ * for full per-row error visibility. Same X-Meadow-Upsert-* response
16
+ * headers are set on both.
17
+ */
18
+ const doUpsert = require('./Meadow-Operation-Upsert.js');
19
+
20
+ const marshalLiteList = require('../read/Meadow-Marshal-LiteList.js');
21
+
22
+ const doAPIEndpointUpsertsDetailed = function(pRequest, pResponse, fNext)
23
+ {
24
+ let tmpRequestState = this.initializeRequestState(pRequest, 'UpsertBulkDetailed');
25
+ let fBehaviorInjector = (pBehaviorHash) => { return (fStageComplete) => { this.BehaviorInjection.runBehavior(pBehaviorHash, this, pRequest, tmpRequestState, fStageComplete); }; };
26
+
27
+ tmpRequestState.CreatedRecords = [];
28
+ tmpRequestState.UpdatedRecords = [];
29
+ tmpRequestState.UpsertedRecords = [];
30
+ tmpRequestState.ErrorRecords = [];
31
+
32
+ this.waterfall(
33
+ [
34
+ (fStageComplete) =>
35
+ {
36
+ if (!Array.isArray(pRequest.body))
37
+ {
38
+ return fStageComplete(this.ErrorHandler.getError(`Record bulk upsert (detailed) failure - a valid array of records is required.`, 500));
39
+ }
40
+
41
+ tmpRequestState.BulkRecords = pRequest.body;
42
+
43
+ return fStageComplete();
44
+ },
45
+ fBehaviorInjector(`UpsertBulk-PreOperation`),
46
+ (fStageComplete) =>
47
+ {
48
+ this.eachLimit(tmpRequestState.BulkRecords, 1,
49
+ (pRecord, fCallback) =>
50
+ {
51
+ doUpsert.call(this, pRecord, pRequest, tmpRequestState, pResponse, fCallback);
52
+ }, fStageComplete);
53
+ },
54
+ fBehaviorInjector(`UpsertBulk-PostOperation`),
55
+ (fStageComplete) =>
56
+ {
57
+ let tmpInputCount = (tmpRequestState.BulkRecords && tmpRequestState.BulkRecords.length) || 0;
58
+ let tmpErrorCount = (tmpRequestState.ErrorRecords && tmpRequestState.ErrorRecords.length) || 0;
59
+ let tmpUpsertedCount = (tmpRequestState.UpsertedRecords && tmpRequestState.UpsertedRecords.length) || 0;
60
+
61
+ // Mirror the X-Meadow-Upsert-* headers from /Upserts so a
62
+ // caller can dispatch to either endpoint and read the same
63
+ // summary surface. The body envelope adds the per-row
64
+ // detail; the headers stay the structured summary.
65
+ try
66
+ {
67
+ pResponse.header('X-Meadow-Upsert-Total', String(tmpInputCount));
68
+ pResponse.header('X-Meadow-Upsert-Succeeded', String(tmpUpsertedCount));
69
+ pResponse.header('X-Meadow-Upsert-Errored', String(tmpErrorCount));
70
+ }
71
+ catch (pHdrErr) { /* response.header may not exist on all servers; degrade silently */ }
72
+
73
+ let tmpEnvelope = {
74
+ Counts: { Total: tmpInputCount, Succeeded: tmpUpsertedCount, Errored: tmpErrorCount },
75
+ UpsertedRecords: marshalLiteList.call(this, tmpRequestState.UpsertedRecords, pRequest),
76
+ ErrorRecords: tmpRequestState.ErrorRecords || []
77
+ };
78
+ pResponse.send(tmpEnvelope);
79
+ return fStageComplete();
80
+ },
81
+ (fStageComplete) =>
82
+ {
83
+ let tmpUpsertedCount = (tmpRequestState.UpsertedRecords && tmpRequestState.UpsertedRecords.length) || 0;
84
+ let tmpErrorCount = (tmpRequestState.ErrorRecords && tmpRequestState.ErrorRecords.length) || 0;
85
+ let tmpMessage = (tmpErrorCount > 0)
86
+ ? `Bulk upsert (detailed) complete -- ${tmpUpsertedCount} records succeeded, ${tmpErrorCount} errored`
87
+ : `Bulk upsert (detailed) complete -- ${tmpUpsertedCount} records processed`;
88
+ this.log.requestCompletedSuccessfully(pRequest, tmpRequestState, tmpMessage);
89
+ return fStageComplete();
90
+ }
91
+ ], (pError) =>
92
+ {
93
+ return this.ErrorHandler.handleErrorIfSet(pRequest, tmpRequestState, pResponse, pError, fNext);
94
+ });
95
+ };
96
+ module.exports = doAPIEndpointUpsertsDetailed;
@@ -126,6 +126,21 @@ const doUpsert = function(pRecordToUpsert, pRequest, pRequestState, pResponse, f
126
126
  if (pError)
127
127
  {
128
128
  tmpRequestState.Record.Error = pError;
129
+ // Surface per-row failures back to the bulk caller. The
130
+ // parent BulkUpsert endpoint reads this array to count
131
+ // errors and signal partial-success to clients via a
132
+ // response header. Without this push, individual upsert
133
+ // failures vanish silently and the bulk response only
134
+ // reflects what survived.
135
+ if (tmpRequestState.ParentRequestState && Array.isArray(tmpRequestState.ParentRequestState.ErrorRecords))
136
+ {
137
+ let tmpErrorMessage = (pError && (pError.message || pError.Error)) ? (pError.message || pError.Error) : String(pError);
138
+ tmpRequestState.ParentRequestState.ErrorRecords.push({
139
+ Record: tmpRequestState.Record,
140
+ Operation: tmpRequestState.Operation || 'Unknown',
141
+ Error: tmpErrorMessage
142
+ });
143
+ }
129
144
  }
130
145
  return fCallback();
131
146
  });