@miso.ai/server-sdk 0.6.5-beta.6 → 0.6.5-beta.7

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
@@ -16,12 +16,12 @@
16
16
  "simonpai <simon.pai@askmiso.com>"
17
17
  ],
18
18
  "dependencies": {
19
- "@miso.ai/server-commons": "0.6.5-beta.6",
19
+ "@miso.ai/server-commons": "0.6.5-beta.7",
20
20
  "axios": "^1.6.2",
21
21
  "axios-retry": "^4.5.0",
22
22
  "dotenv": "^16.0.1",
23
23
  "split2": "^4.1.0",
24
24
  "yargs": "^17.5.1"
25
25
  },
26
- "version": "0.6.5-beta.6"
26
+ "version": "0.6.5-beta.7"
27
27
  }
@@ -2,9 +2,44 @@ import { asArray, trimObj, computeIfAbsent } from '@miso.ai/server-commons';
2
2
  import { Buffer } from 'buffer';
3
3
 
4
4
  export async function upload(client, type, records, options = {}) {
5
- const url = buildUrl(client, type, { ...options, async: true });
5
+ const url = buildUrl(client, type, options);
6
6
  const payload = buildUploadPayload(records);
7
- return client._axios.post(url, payload);
7
+ try {
8
+ // await to catch errors
9
+ return await client._axios.post(url, payload);
10
+ } catch (error) {
11
+ await recoverValidRecords(client, type, records, options, error.response);
12
+ throw error;
13
+ }
14
+ }
15
+
16
+ async function recoverValidRecords(client, type, records, options, response) {
17
+ if (!response || response.status !== 422 || !options.recoverValidRecordsOn422) {
18
+ return;
19
+ }
20
+ records = extractRecordsFromUploadPayload(records);
21
+ // try to collect valid records and resend them, which should pass the validation
22
+ const { groups = [], unrecognized = [] } = process422ResponseBody(records, response.data); // it takes records too
23
+ if (groups.length === 0 || groups.length === records.length || unrecognized.length > 0) {
24
+ // if there are unrecognized messages, it's hard to tell which records are valid
25
+ return;
26
+ }
27
+ const invalidIndices = new Set(groups.map(group => group.index));
28
+ const validRecords = records.filter((_, index) => !invalidIndices.has(index));
29
+ if (validRecords.length === 0) {
30
+ return; // should not be, just in case
31
+ }
32
+ const url = buildUrl(client, type, options);
33
+ const validPayload = buildUploadPayload(validRecords);
34
+ try {
35
+ await client._axios.post(url, validPayload);
36
+ } catch (_) {
37
+ return; // still fail, never mind...
38
+ }
39
+ response.recovered = {
40
+ records: validRecords.length,
41
+ bytes: validPayload.length * 2,
42
+ };
8
43
  }
9
44
 
10
45
  export async function merge(client, type, record, { mergeFn = defaultMerge } = {}) {
@@ -61,10 +96,10 @@ export function shimRecordForMerging(record) {
61
96
  return record;
62
97
  }
63
98
 
64
- const RE_422_MSG_LINE = /^\s*data\.(\d+)(?:\.(\S+))?\s+(?:\(([^)]*)\))?\s+is\s+invalid\.\s+(.*)$/;
99
+ const RE_422_MSG_LINE = /^\s*data\.(\d+)(?:\.(\S+))?\s+(?:\(([^)]*)\)\s+)?is\s+invalid\.\s+(.*)$/;
65
100
 
66
101
  export function process422ResponseBody(payload, { data } = {}) {
67
- const records = extractUploadPayload(payload);
102
+ const records = extractRecordsFromUploadPayload(payload);
68
103
  const unrecognized = [];
69
104
  const groupsMap = new Map();
70
105
  for (const line of data) {
@@ -122,10 +157,10 @@ export function buildUrl(client, path, { async, dryRun, params: extraParams } =
122
157
  export function buildUploadPayload(records) {
123
158
  return typeof records === 'string' ? records :
124
159
  Buffer.isBuffer(records) ? records.toString() :
125
- { data: Array.isArray(records) ? records : [records] };
160
+ JSON.stringify({ data: Array.isArray(records) ? records : [records] });
126
161
  }
127
162
 
128
- export function extractUploadPayload(records) {
163
+ export function extractRecordsFromUploadPayload(records) {
129
164
  if (Buffer.isBuffer(records)) {
130
165
  records = records.toString();
131
166
  }
@@ -52,9 +52,13 @@ export default class ApiSink extends sink.BpsSink {
52
52
  } else if (status) {
53
53
  data = { status, ...data };
54
54
  }
55
+ if (error.response.recovered) {
56
+ data.recovered = error.response.recovered;
57
+ }
55
58
  }
56
59
 
57
60
  // keep track of service stats on successful calls
61
+ // TODO: handle recovered records?
58
62
  if (!data.errors) {
59
63
  this._serviceStats.track({ records, bytes, took: data.took });
60
64
  }
@@ -22,7 +22,7 @@ class UploadSink extends ApiSink {
22
22
 
23
23
  async _execute(payload) {
24
24
  const { type, dryRun, params } = this._options;
25
- const { data } = await upload(this._client, type, payload, { dryRun, params });
25
+ const { data } = await upload(this._client, type, payload, { recoverValidRecordsOn422: true, dryRun, params });
26
26
  return data;
27
27
  }
28
28
 
@@ -65,12 +65,13 @@ export default class UploadStream extends stream.BufferedWriteStream {
65
65
 
66
66
  // if upload fails, emit extracted payload at response event
67
67
  if (message.event === 'response') {
68
+ // TODO: we can do these near recoverValidRecords()
68
69
  const { response, payload } = args;
69
70
  if (payload) {
70
71
  output.payload = JSON.parse(payload);
71
- }
72
- if (response && response.status === 422) {
73
- output.issues = process422ResponseBody(payload, response);
72
+ if (response && response.status === 422) {
73
+ output.issues = process422ResponseBody(payload, response);
74
+ }
74
75
  }
75
76
  }
76
77
 
package/src/version.js CHANGED
@@ -1 +1 @@
1
- export default '0.6.5-beta.6';
1
+ export default '0.6.5-beta.7';