@miso.ai/server-sdk 0.6.5-beta.1 → 0.6.5-beta.10
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/cli/delete.js +1 -1
- package/cli/get.js +8 -3
- package/cli/hybrid-search.js +36 -0
- package/cli/ids-diff.js +2 -1
- package/cli/ids.js +3 -2
- package/cli/index.js +4 -0
- package/cli/merge.js +2 -1
- package/cli/search.js +30 -0
- package/cli/status.js +3 -2
- package/cli/upload.js +1 -1
- package/cli/utils.js +35 -1
- package/package.json +3 -2
- package/src/api/ask.js +13 -0
- package/src/api/base.js +4 -5
- package/src/api/experiments.js +1 -2
- package/src/api/helpers.js +44 -14
- package/src/api/index.js +2 -0
- package/src/axios.js +43 -0
- package/src/client.js +4 -2
- package/src/stream/api-sink.js +4 -0
- package/src/stream/delete.js +1 -1
- package/src/stream/upload-sink.js +1 -1
- package/src/stream/upload.js +4 -3
- package/src/version.js +1 -1
package/cli/delete.js
CHANGED
|
@@ -49,7 +49,7 @@ const run = type => async ({
|
|
|
49
49
|
loglevel = (debug || progress) ? log.DEBUG : loglevel;
|
|
50
50
|
logFormat = progress ? logger.FORMAT.PROGRESS : logFormat;
|
|
51
51
|
|
|
52
|
-
const client = new MisoClient({ key, server });
|
|
52
|
+
const client = new MisoClient({ key, server, debug });
|
|
53
53
|
|
|
54
54
|
const deleteStream = client.api[type].deleteStream({
|
|
55
55
|
name,
|
package/cli/get.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { MisoClient } from '../src/index.js';
|
|
2
|
+
import { formatError } from './utils.js';
|
|
2
3
|
|
|
3
4
|
const build = yargs => yargs;
|
|
4
5
|
|
|
@@ -8,9 +9,13 @@ const run = type => async ({
|
|
|
8
9
|
id,
|
|
9
10
|
debug,
|
|
10
11
|
}) => {
|
|
11
|
-
const client = new MisoClient({ key, server });
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
const client = new MisoClient({ key, server, debug });
|
|
13
|
+
try {
|
|
14
|
+
const entity = await client.api[type].get(id);
|
|
15
|
+
console.log(JSON.stringify(entity));
|
|
16
|
+
} catch (err) {
|
|
17
|
+
console.error(formatError(err));
|
|
18
|
+
}
|
|
14
19
|
};
|
|
15
20
|
|
|
16
21
|
export default function(type) {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Readable } from 'stream';
|
|
2
|
+
import { stream } from '@miso.ai/server-commons';
|
|
3
|
+
import { MisoClient } from '../src/index.js';
|
|
4
|
+
import { buildForApi, buildForSearch } from './utils.js';
|
|
5
|
+
|
|
6
|
+
function build(yargs) {
|
|
7
|
+
return buildForSearch(buildForApi(yargs))
|
|
8
|
+
.positional('query', {
|
|
9
|
+
describe: 'Search query',
|
|
10
|
+
})
|
|
11
|
+
.option('answer', {
|
|
12
|
+
type: 'boolean',
|
|
13
|
+
describe: 'Return answer',
|
|
14
|
+
default: false,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function run({ query, fq, fl, rows, answer, key, server, debug }) {
|
|
19
|
+
const client = new MisoClient({ key, server, debug });
|
|
20
|
+
const { products } = await client.api.ask.search({ q: query, fq, fl, rows, answer });
|
|
21
|
+
const readStream = Readable.from(products);
|
|
22
|
+
const outputStream = new stream.OutputStream();
|
|
23
|
+
|
|
24
|
+
await stream.pipeline(
|
|
25
|
+
readStream,
|
|
26
|
+
outputStream,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default {
|
|
31
|
+
command: 'hybrid-search [query]',
|
|
32
|
+
aliases: ['hs'],
|
|
33
|
+
description: `Hybrid search records by a query.`,
|
|
34
|
+
builder: build,
|
|
35
|
+
handler: run,
|
|
36
|
+
};
|
package/cli/ids-diff.js
CHANGED
|
@@ -30,10 +30,11 @@ const run = type => async ({
|
|
|
30
30
|
output,
|
|
31
31
|
plus,
|
|
32
32
|
minus,
|
|
33
|
+
debug,
|
|
33
34
|
}) => {
|
|
34
35
|
output = output || (plus ? 'plus' : minus ? 'minus' : undefined);
|
|
35
36
|
|
|
36
|
-
const client = new MisoClient({ key, server });
|
|
37
|
+
const client = new MisoClient({ key, server, debug });
|
|
37
38
|
const misoIds = await client.api[type].ids();
|
|
38
39
|
|
|
39
40
|
const diffStream = new stream.DiffStream(misoIds, { output });
|
package/cli/ids.js
CHANGED
|
@@ -22,8 +22,9 @@ const run = type => async ({
|
|
|
22
22
|
key,
|
|
23
23
|
server,
|
|
24
24
|
type: recordType,
|
|
25
|
+
debug,
|
|
25
26
|
}) => {
|
|
26
|
-
const client = new MisoClient({ key, server });
|
|
27
|
+
const client = new MisoClient({ key, server, debug });
|
|
27
28
|
let ids;
|
|
28
29
|
try {
|
|
29
30
|
const options = recordType ? { type: recordType } : {};
|
|
@@ -45,7 +46,7 @@ const run = type => async ({
|
|
|
45
46
|
export default function(type) {
|
|
46
47
|
return {
|
|
47
48
|
command: 'ids',
|
|
48
|
-
description:
|
|
49
|
+
description: `Get all ids in the catalog`,
|
|
49
50
|
builder: build(type),
|
|
50
51
|
handler: run(type),
|
|
51
52
|
};
|
package/cli/index.js
CHANGED
|
@@ -9,6 +9,8 @@ import ids from './ids.js';
|
|
|
9
9
|
import transform from './transform.js';
|
|
10
10
|
import status from './status.js';
|
|
11
11
|
import get from './get.js';
|
|
12
|
+
import search from './search.js';
|
|
13
|
+
import hybridSearch from './hybrid-search.js';
|
|
12
14
|
|
|
13
15
|
const interactions = {
|
|
14
16
|
command: 'interactions',
|
|
@@ -68,5 +70,7 @@ yargs.build(yargs => {
|
|
|
68
70
|
.command(users)
|
|
69
71
|
.command(experiments)
|
|
70
72
|
.command(transform)
|
|
73
|
+
.command(search)
|
|
74
|
+
.command(hybridSearch)
|
|
71
75
|
.version(MisoClient.version);
|
|
72
76
|
});
|
package/cli/merge.js
CHANGED
|
@@ -27,11 +27,12 @@ const run = type => async ({
|
|
|
27
27
|
server,
|
|
28
28
|
file,
|
|
29
29
|
base,
|
|
30
|
+
debug,
|
|
30
31
|
...options
|
|
31
32
|
}) => {
|
|
32
33
|
const mergeFn = await getMergeFn(file);
|
|
33
34
|
const records = await buildBaseRecords(base);
|
|
34
|
-
const client = new MisoClient({ key, server });
|
|
35
|
+
const client = new MisoClient({ key, server, debug });
|
|
35
36
|
const mergeStream = client.api[type].mergeStream({ ...options, mergeFn, records });
|
|
36
37
|
const outputStream = new stream.OutputStream({ objectMode: true });
|
|
37
38
|
await stream.pipeline(
|
package/cli/search.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Readable } from 'stream';
|
|
2
|
+
import { stream } from '@miso.ai/server-commons';
|
|
3
|
+
import { MisoClient } from '../src/index.js';
|
|
4
|
+
import { buildForApi, buildForSearch } from './utils.js';
|
|
5
|
+
|
|
6
|
+
function build(yargs) {
|
|
7
|
+
return buildForSearch(buildForApi(yargs))
|
|
8
|
+
.positional('query', {
|
|
9
|
+
describe: 'Search query',
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function run({ query, fq, fl, rows, key, server, debug }) {
|
|
14
|
+
const client = new MisoClient({ key, server, debug });
|
|
15
|
+
const records = await client.api.search.search({ q: query, fq, fl, rows });
|
|
16
|
+
const readStream = Readable.from(records);
|
|
17
|
+
const outputStream = new stream.OutputStream();
|
|
18
|
+
|
|
19
|
+
await stream.pipeline(
|
|
20
|
+
readStream,
|
|
21
|
+
outputStream,
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default {
|
|
26
|
+
command: 'search [query]',
|
|
27
|
+
description: `Search records by a query.`,
|
|
28
|
+
builder: build,
|
|
29
|
+
handler: run,
|
|
30
|
+
};
|
package/cli/status.js
CHANGED
|
@@ -10,8 +10,9 @@ const run = type => async ({
|
|
|
10
10
|
key,
|
|
11
11
|
server,
|
|
12
12
|
taskId,
|
|
13
|
+
debug,
|
|
13
14
|
}) => {
|
|
14
|
-
const client = new MisoClient({ key, server });
|
|
15
|
+
const client = new MisoClient({ key, server, debug });
|
|
15
16
|
if (taskId) {
|
|
16
17
|
runOne(client, type, taskId);
|
|
17
18
|
} else {
|
|
@@ -21,7 +22,7 @@ const run = type => async ({
|
|
|
21
22
|
|
|
22
23
|
async function runOne(client, type, taskId) {
|
|
23
24
|
try {
|
|
24
|
-
console.log(await client.api[type].status(taskId));
|
|
25
|
+
console.log(JSON.stringify(await client.api[type].status(taskId)));
|
|
25
26
|
} catch (err) {
|
|
26
27
|
console.error(err);
|
|
27
28
|
throw err;
|
package/cli/upload.js
CHANGED
|
@@ -61,7 +61,7 @@ const run = type => async ({
|
|
|
61
61
|
loglevel = (debug || progress) ? log.DEBUG : loglevel;
|
|
62
62
|
logFormat = progress ? logger.FORMAT.PROGRESS : logFormat;
|
|
63
63
|
|
|
64
|
-
const client = new MisoClient({ key, server });
|
|
64
|
+
const client = new MisoClient({ key, server, debug });
|
|
65
65
|
|
|
66
66
|
const uploadStreamObjectMode = lenient;
|
|
67
67
|
|
package/cli/utils.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { yargs as _yargs } from '@miso.ai/server-commons';
|
|
2
|
+
|
|
1
3
|
export function buildForApi(yargs) {
|
|
2
4
|
return yargs
|
|
3
5
|
.option('key', {
|
|
@@ -12,7 +14,7 @@ export function buildForApi(yargs) {
|
|
|
12
14
|
alias: ['v', 'var'],
|
|
13
15
|
describe: 'Extra URL parameters',
|
|
14
16
|
type: 'array',
|
|
15
|
-
coerce:
|
|
17
|
+
coerce: _yargs.coerceToArray,
|
|
16
18
|
})
|
|
17
19
|
.option('debug', {
|
|
18
20
|
describe: 'Set log level to debug',
|
|
@@ -20,3 +22,35 @@ export function buildForApi(yargs) {
|
|
|
20
22
|
})
|
|
21
23
|
.demandOption(['key'], 'API key is required.');
|
|
22
24
|
}
|
|
25
|
+
|
|
26
|
+
export function buildForSearch(yargs) {
|
|
27
|
+
return yargs
|
|
28
|
+
.option('fq', {
|
|
29
|
+
type: 'string',
|
|
30
|
+
describe: 'Filter query',
|
|
31
|
+
})
|
|
32
|
+
.option('fl', {
|
|
33
|
+
type: 'string',
|
|
34
|
+
coerce: _yargs.coerceToArray,
|
|
35
|
+
describe: 'Fields to return',
|
|
36
|
+
})
|
|
37
|
+
.option('rows', {
|
|
38
|
+
alias: ['n'],
|
|
39
|
+
type: 'number',
|
|
40
|
+
describe: 'Number of rows to return',
|
|
41
|
+
})
|
|
42
|
+
.option('start', {
|
|
43
|
+
alias: ['s'],
|
|
44
|
+
type: 'number',
|
|
45
|
+
describe: 'Start index',
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function formatError(err) {
|
|
50
|
+
const { response } = err;
|
|
51
|
+
if (response) {
|
|
52
|
+
const { data, status } = response;
|
|
53
|
+
return { errors: true, status, ...data };
|
|
54
|
+
}
|
|
55
|
+
return { errors: true, message: err.message };
|
|
56
|
+
}
|
package/package.json
CHANGED
|
@@ -16,11 +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.10",
|
|
20
20
|
"axios": "^1.6.2",
|
|
21
|
+
"axios-retry": "^4.5.0",
|
|
21
22
|
"dotenv": "^16.0.1",
|
|
22
23
|
"split2": "^4.1.0",
|
|
23
24
|
"yargs": "^17.5.1"
|
|
24
25
|
},
|
|
25
|
-
"version": "0.6.5-beta.
|
|
26
|
+
"version": "0.6.5-beta.10"
|
|
26
27
|
}
|
package/src/api/ask.js
ADDED
package/src/api/base.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
2
1
|
import { asArray } from '@miso.ai/server-commons';
|
|
3
2
|
import { upload, merge, batchDelete, buildUrl } from './helpers.js';
|
|
4
3
|
import UploadStream from '../stream/upload.js';
|
|
@@ -16,7 +15,7 @@ export class Queries {
|
|
|
16
15
|
async _run(path, payload, options) {
|
|
17
16
|
// TODO: options
|
|
18
17
|
const url = buildUrl(this._client, `${this._group}/${path}`);
|
|
19
|
-
return (await
|
|
18
|
+
return (await this._client._axios.post(url, payload)).data.data;
|
|
20
19
|
}
|
|
21
20
|
|
|
22
21
|
}
|
|
@@ -46,13 +45,13 @@ export class Entities extends Writable {
|
|
|
46
45
|
|
|
47
46
|
async get(id) {
|
|
48
47
|
const url = buildUrl(this._client, `${this._type}/${id}`);
|
|
49
|
-
return (await
|
|
48
|
+
return (await this._client._axios.get(url)).data.data;
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
async ids({ type } = {}) {
|
|
53
52
|
const options = type ? { params: { type } } : {};
|
|
54
53
|
const url = buildUrl(this._client, `${this._type}/_ids`, options);
|
|
55
|
-
return (await
|
|
54
|
+
return (await this._client._axios.get(url)).data.data.ids;
|
|
56
55
|
}
|
|
57
56
|
|
|
58
57
|
async delete(ids, options = {}) {
|
|
@@ -65,7 +64,7 @@ export class Entities extends Writable {
|
|
|
65
64
|
|
|
66
65
|
async status(taskId) {
|
|
67
66
|
const url = buildUrl(this._client, `${this._type}/_status/${taskId}`);
|
|
68
|
-
return (await
|
|
67
|
+
return (await this._client._axios.get(url)).data;
|
|
69
68
|
}
|
|
70
69
|
|
|
71
70
|
statusStream() {
|
package/src/api/experiments.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
2
1
|
import { buildUrl } from './helpers.js';
|
|
3
2
|
|
|
4
3
|
export default class Experiments {
|
|
@@ -12,7 +11,7 @@ export default class Experiments {
|
|
|
12
11
|
const url = buildUrl(this._client, `experiments/${experimentId}/events`);
|
|
13
12
|
// TODO: make content type header global
|
|
14
13
|
const headers = { 'Content-Type': 'application/json' };
|
|
15
|
-
const response = await
|
|
14
|
+
const response = await this._client._axios.post(url, record, { headers });
|
|
16
15
|
// 200 response body does not have .data layer
|
|
17
16
|
return response.data ? response : { data: response };
|
|
18
17
|
}
|
package/src/api/helpers.js
CHANGED
|
@@ -1,11 +1,45 @@
|
|
|
1
1
|
import { asArray, trimObj, computeIfAbsent } from '@miso.ai/server-commons';
|
|
2
|
-
import axios from 'axios';
|
|
3
2
|
import { Buffer } from 'buffer';
|
|
4
3
|
|
|
5
4
|
export async function upload(client, type, records, options = {}) {
|
|
6
|
-
const url = buildUrl(client, type,
|
|
5
|
+
const url = buildUrl(client, type, options);
|
|
7
6
|
const payload = buildUploadPayload(records);
|
|
8
|
-
|
|
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
|
+
};
|
|
9
43
|
}
|
|
10
44
|
|
|
11
45
|
export async function merge(client, type, record, { mergeFn = defaultMerge } = {}) {
|
|
@@ -62,10 +96,10 @@ export function shimRecordForMerging(record) {
|
|
|
62
96
|
return record;
|
|
63
97
|
}
|
|
64
98
|
|
|
65
|
-
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+(.*)$/;
|
|
66
100
|
|
|
67
101
|
export function process422ResponseBody(payload, { data } = {}) {
|
|
68
|
-
const records =
|
|
102
|
+
const records = extractRecordsFromUploadPayload(payload);
|
|
69
103
|
const unrecognized = [];
|
|
70
104
|
const groupsMap = new Map();
|
|
71
105
|
for (const line of data) {
|
|
@@ -74,7 +108,7 @@ export function process422ResponseBody(payload, { data } = {}) {
|
|
|
74
108
|
unrecognized.push(line);
|
|
75
109
|
continue;
|
|
76
110
|
}
|
|
77
|
-
let [_, index, path, message] = m;
|
|
111
|
+
let [_, index, path, value, message] = m;
|
|
78
112
|
index = Number(index);
|
|
79
113
|
const { violations } = computeIfAbsent(groupsMap, index, index => ({
|
|
80
114
|
index,
|
|
@@ -83,6 +117,7 @@ export function process422ResponseBody(payload, { data } = {}) {
|
|
|
83
117
|
}));
|
|
84
118
|
violations.push({
|
|
85
119
|
path,
|
|
120
|
+
value,
|
|
86
121
|
message,
|
|
87
122
|
});
|
|
88
123
|
}
|
|
@@ -98,12 +133,7 @@ export function process422ResponseBody(payload, { data } = {}) {
|
|
|
98
133
|
export async function batchDelete(client, type, ids, options = {}) {
|
|
99
134
|
const url = buildUrl(client, `${type}/_delete`, { ...options, async: true });
|
|
100
135
|
const payload = buildBatchDeletePayload(type, ids);
|
|
101
|
-
|
|
102
|
-
const { data } = await axios.post(url, payload, {
|
|
103
|
-
headers: {
|
|
104
|
-
'Content-Type': 'application/json',
|
|
105
|
-
},
|
|
106
|
-
});
|
|
136
|
+
const { data } = await client._axios.post(url, payload);
|
|
107
137
|
return data;
|
|
108
138
|
}
|
|
109
139
|
|
|
@@ -127,10 +157,10 @@ export function buildUrl(client, path, { async, dryRun, params: extraParams } =
|
|
|
127
157
|
export function buildUploadPayload(records) {
|
|
128
158
|
return typeof records === 'string' ? records :
|
|
129
159
|
Buffer.isBuffer(records) ? records.toString() :
|
|
130
|
-
{ data: Array.isArray(records) ? records : [records] };
|
|
160
|
+
JSON.stringify({ data: Array.isArray(records) ? records : [records] });
|
|
131
161
|
}
|
|
132
162
|
|
|
133
|
-
export function
|
|
163
|
+
export function extractRecordsFromUploadPayload(records) {
|
|
134
164
|
if (Buffer.isBuffer(records)) {
|
|
135
165
|
records = records.toString();
|
|
136
166
|
}
|
package/src/api/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import Products from './products.js';
|
|
|
2
2
|
import Users from './users.js';
|
|
3
3
|
import Interactions from './interactions.js';
|
|
4
4
|
import Experiments from './experiments.js';
|
|
5
|
+
import Ask from './ask.js';
|
|
5
6
|
import Search from './search.js';
|
|
6
7
|
import Recommendation from './recommendation.js';
|
|
7
8
|
|
|
@@ -12,6 +13,7 @@ export default class Api {
|
|
|
12
13
|
this.users = new Users(client);
|
|
13
14
|
this.interactions = new Interactions(client);
|
|
14
15
|
this.experiments = new Experiments(client);
|
|
16
|
+
this.ask = new Ask(client);
|
|
15
17
|
this.search = new Search(client);
|
|
16
18
|
this.recommendation = new Recommendation(client);
|
|
17
19
|
}
|
package/src/axios.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import axiosRetry from 'axios-retry';
|
|
3
|
+
import version from './version.js';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_RETRY_OPTIONS = {
|
|
6
|
+
retries: 3,
|
|
7
|
+
retryDelay: count => count * 300,
|
|
8
|
+
retryCondition: (error) => {
|
|
9
|
+
// Only retry on 5xx server errors
|
|
10
|
+
return error.response && error.response.status >= 500 && error.response.status < 600;
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function createAxios(options = {}, debug = false) {
|
|
15
|
+
if (typeof options.get === 'function' && typeof options.post === 'function') {
|
|
16
|
+
return options; // assume this is an axios instance already
|
|
17
|
+
}
|
|
18
|
+
const { retry } = options;
|
|
19
|
+
const instance = axios.create({
|
|
20
|
+
headers: {
|
|
21
|
+
'User-Agent': `MisoNodeSDK/${version}`,
|
|
22
|
+
'Content-Type': 'application/json',
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
axiosRetry(instance, { ...DEFAULT_RETRY_OPTIONS, ...retry });
|
|
26
|
+
if (debug) {
|
|
27
|
+
instance.interceptors.request.use(config => {
|
|
28
|
+
explainAsCurl(config);
|
|
29
|
+
return config;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return instance;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function explainAsCurl(config) {
|
|
36
|
+
// format into a curl command
|
|
37
|
+
const { method, url, data } = config;
|
|
38
|
+
let command = `curl -X ${method.toUpperCase()} '${url}'`;
|
|
39
|
+
if (data) {
|
|
40
|
+
command += ` -d '${typeof data === 'string' ? data : JSON.stringify(data)}'`;
|
|
41
|
+
}
|
|
42
|
+
console.log(`[Explain] ${command}`);
|
|
43
|
+
}
|
package/src/client.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import version from './version.js';
|
|
2
2
|
import Api from './api/index.js';
|
|
3
|
+
import { createAxios } from './axios.js';
|
|
3
4
|
|
|
4
5
|
export default class MisoClient {
|
|
5
6
|
|
|
6
7
|
static version = version;
|
|
7
8
|
|
|
8
|
-
constructor(options) {
|
|
9
|
-
this._options = normalizeOptions(options);
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this._options = options = normalizeOptions(options);
|
|
11
|
+
this._axios = createAxios(options.axios, options.debug); // TODO: pass onRetry() for debug message
|
|
10
12
|
this.version = version;
|
|
11
13
|
this.api = new Api(this);
|
|
12
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
|
}
|
package/src/stream/delete.js
CHANGED
|
@@ -68,7 +68,7 @@ export default class DeleteStream extends stream.BufferedWriteStream {
|
|
|
68
68
|
const output = super._output(message, args);
|
|
69
69
|
|
|
70
70
|
// if upload fails, emit extracted payload at response event
|
|
71
|
-
if (message.event === 'response' && args.
|
|
71
|
+
if (message.event === 'response' && args.payload) {
|
|
72
72
|
output.payload = JSON.parse(args.payload);
|
|
73
73
|
}
|
|
74
74
|
|
|
@@ -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.10';
|