@miso.ai/server-sdk 0.6.5-beta.5 → 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 +2 -2
- package/src/api/helpers.js +43 -7
- package/src/client.js +3 -3
- package/src/stream/api-sink.js +4 -0
- package/src/stream/upload-sink.js +1 -1
- package/src/stream/upload.js +4 -3
- package/src/version.js +1 -1
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.
|
|
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.
|
|
26
|
+
"version": "0.6.5-beta.7"
|
|
27
27
|
}
|
package/src/api/helpers.js
CHANGED
|
@@ -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,
|
|
5
|
+
const url = buildUrl(client, type, options);
|
|
6
6
|
const payload = buildUploadPayload(records);
|
|
7
|
-
|
|
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+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 =
|
|
102
|
+
const records = extractRecordsFromUploadPayload(payload);
|
|
68
103
|
const unrecognized = [];
|
|
69
104
|
const groupsMap = new Map();
|
|
70
105
|
for (const line of data) {
|
|
@@ -73,7 +108,7 @@ export function process422ResponseBody(payload, { data } = {}) {
|
|
|
73
108
|
unrecognized.push(line);
|
|
74
109
|
continue;
|
|
75
110
|
}
|
|
76
|
-
let [_, index, path, message] = m;
|
|
111
|
+
let [_, index, path, value, message] = m;
|
|
77
112
|
index = Number(index);
|
|
78
113
|
const { violations } = computeIfAbsent(groupsMap, index, index => ({
|
|
79
114
|
index,
|
|
@@ -82,6 +117,7 @@ export function process422ResponseBody(payload, { data } = {}) {
|
|
|
82
117
|
}));
|
|
83
118
|
violations.push({
|
|
84
119
|
path,
|
|
120
|
+
value,
|
|
85
121
|
message,
|
|
86
122
|
});
|
|
87
123
|
}
|
|
@@ -121,10 +157,10 @@ export function buildUrl(client, path, { async, dryRun, params: extraParams } =
|
|
|
121
157
|
export function buildUploadPayload(records) {
|
|
122
158
|
return typeof records === 'string' ? records :
|
|
123
159
|
Buffer.isBuffer(records) ? records.toString() :
|
|
124
|
-
{ data: Array.isArray(records) ? records : [records] };
|
|
160
|
+
JSON.stringify({ data: Array.isArray(records) ? records : [records] });
|
|
125
161
|
}
|
|
126
162
|
|
|
127
|
-
export function
|
|
163
|
+
export function extractRecordsFromUploadPayload(records) {
|
|
128
164
|
if (Buffer.isBuffer(records)) {
|
|
129
165
|
records = records.toString();
|
|
130
166
|
}
|
package/src/client.js
CHANGED
|
@@ -6,9 +6,9 @@ export default class MisoClient {
|
|
|
6
6
|
|
|
7
7
|
static version = version;
|
|
8
8
|
|
|
9
|
-
constructor(
|
|
10
|
-
this._options = normalizeOptions(options);
|
|
11
|
-
this._axios = createAxios(
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this._options = options = normalizeOptions(options);
|
|
11
|
+
this._axios = createAxios(options.axios); // TODO: pass onRetry() for debug message
|
|
12
12
|
this.version = version;
|
|
13
13
|
this.api = new Api(this);
|
|
14
14
|
}
|
package/src/stream/api-sink.js
CHANGED
|
@@ -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
|
|
package/src/stream/upload.js
CHANGED
|
@@ -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
|
-
|
|
73
|
-
|
|
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.
|
|
1
|
+
export default '0.6.5-beta.7';
|