@miso.ai/server-sdk 0.6.6-beta.17 → 0.6.6-beta.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/cli/delete.js +33 -20
- package/cli/hybrid-search.js +2 -1
- package/cli/ids-diff.js +2 -1
- package/cli/ids.js +2 -1
- package/cli/index.js +2 -0
- package/cli/merge.js +3 -3
- package/cli/search.js +2 -1
- package/cli/status.js +3 -2
- package/cli/transform.js +9 -12
- package/cli/upgrade.js +37 -0
- package/cli/upload.js +40 -29
- package/cli/utils.js +30 -0
- package/package.json +2 -2
- package/src/api/base.js +9 -0
- package/src/api/helpers.js +5 -3
- package/src/channel/api.js +194 -0
- package/src/channel/delete.js +68 -0
- package/src/channel/index.js +2 -0
- package/src/channel/upload.js +68 -0
- package/src/version.js +1 -1
package/cli/delete.js
CHANGED
|
@@ -1,21 +1,11 @@
|
|
|
1
|
+
import { pipeline } from 'stream/promises';
|
|
1
2
|
import split2 from 'split2';
|
|
2
|
-
import { log, stream } from '@miso.ai/server-commons';
|
|
3
|
+
import { log, stream, splitObj } from '@miso.ai/server-commons';
|
|
3
4
|
import { MisoClient, logger } from '../src/index.js';
|
|
5
|
+
import { buildForWrite } from './utils.js';
|
|
4
6
|
|
|
5
7
|
function build(yargs) {
|
|
6
|
-
return yargs
|
|
7
|
-
.option('records-per-request', {
|
|
8
|
-
alias: ['rpr'],
|
|
9
|
-
describe: 'How many records to send in a request',
|
|
10
|
-
})
|
|
11
|
-
.option('records-per-second', {
|
|
12
|
-
alias: ['rps'],
|
|
13
|
-
describe: 'How many records to send per second',
|
|
14
|
-
})
|
|
15
|
-
.option('debug', {
|
|
16
|
-
describe: 'Set log level to debug',
|
|
17
|
-
type: 'boolean',
|
|
18
|
-
})
|
|
8
|
+
return buildForWrite(yargs)
|
|
19
9
|
.option('progress', {
|
|
20
10
|
alias: ['p'],
|
|
21
11
|
describe: 'Set log format progress',
|
|
@@ -37,6 +27,32 @@ const run = type => async ({
|
|
|
37
27
|
env,
|
|
38
28
|
key,
|
|
39
29
|
server,
|
|
30
|
+
channel,
|
|
31
|
+
...options
|
|
32
|
+
}) => {
|
|
33
|
+
const { debug } = options;
|
|
34
|
+
const client = new MisoClient({ env, key, server, debug });
|
|
35
|
+
|
|
36
|
+
if (channel) {
|
|
37
|
+
await runChannel(client, type, options);
|
|
38
|
+
} else {
|
|
39
|
+
await runStream(client, type, options);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
async function runChannel(client, type, options) {
|
|
44
|
+
const [deleteOptions] = splitObj(options, ['params', 'requestsPerSecond', 'bytesPerSecond', 'recordsPerRequest', 'bytesPerRequest', 'debug']);
|
|
45
|
+
const deleteChannel = client.api[type].deleteChannel(deleteOptions);
|
|
46
|
+
|
|
47
|
+
await pipeline(
|
|
48
|
+
process.stdin,
|
|
49
|
+
split2(JSON.parse),
|
|
50
|
+
deleteChannel,
|
|
51
|
+
new stream.OutputStream({ objectMode: true }),
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function runStream(client, type, {
|
|
40
56
|
param: params,
|
|
41
57
|
['records-per-request']: recordsPerRequest,
|
|
42
58
|
['records-per-second']: recordsPerSecond,
|
|
@@ -45,13 +61,10 @@ const run = type => async ({
|
|
|
45
61
|
['stream-name']: name,
|
|
46
62
|
['log-level']: loglevel,
|
|
47
63
|
['log-format']: logFormat,
|
|
48
|
-
})
|
|
49
|
-
|
|
64
|
+
}) {
|
|
50
65
|
loglevel = (debug || progress) ? log.DEBUG : loglevel;
|
|
51
66
|
logFormat = progress ? logger.FORMAT.PROGRESS : logFormat;
|
|
52
67
|
|
|
53
|
-
const client = new MisoClient({ env, key, server, debug });
|
|
54
|
-
|
|
55
68
|
const deleteStream = client.api[type].deleteStream({
|
|
56
69
|
name,
|
|
57
70
|
params,
|
|
@@ -67,13 +80,13 @@ const run = type => async ({
|
|
|
67
80
|
format: logFormat,
|
|
68
81
|
});
|
|
69
82
|
|
|
70
|
-
await
|
|
83
|
+
await pipeline(
|
|
71
84
|
process.stdin,
|
|
72
85
|
split2(),
|
|
73
86
|
deleteStream,
|
|
74
87
|
logStream,
|
|
75
88
|
);
|
|
76
|
-
}
|
|
89
|
+
}
|
|
77
90
|
|
|
78
91
|
export default function(type) {
|
|
79
92
|
return {
|
package/cli/hybrid-search.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Readable } from 'stream';
|
|
2
|
+
import { pipeline } from 'stream/promises';
|
|
2
3
|
import { stream } from '@miso.ai/server-commons';
|
|
3
4
|
import { MisoClient } from '../src/index.js';
|
|
4
5
|
import { buildForApi, buildForSearch } from './utils.js';
|
|
@@ -21,7 +22,7 @@ async function run({ query, fq, fl, rows, answer, env, key, server, debug }) {
|
|
|
21
22
|
const readStream = Readable.from(products);
|
|
22
23
|
const outputStream = new stream.OutputStream();
|
|
23
24
|
|
|
24
|
-
await
|
|
25
|
+
await pipeline(
|
|
25
26
|
readStream,
|
|
26
27
|
outputStream,
|
|
27
28
|
);
|
package/cli/ids-diff.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { pipeline } from 'stream/promises';
|
|
1
2
|
import split2 from 'split2';
|
|
2
3
|
import { stream } from '@miso.ai/server-commons';
|
|
3
4
|
import { MisoClient } from '../src/index.js';
|
|
@@ -41,7 +42,7 @@ const run = type => async ({
|
|
|
41
42
|
const diffStream = new stream.DiffStream(misoIds, { output });
|
|
42
43
|
const outputStream = new stream.OutputStream({ objectMode: false });
|
|
43
44
|
|
|
44
|
-
await
|
|
45
|
+
await pipeline(
|
|
45
46
|
process.stdin,
|
|
46
47
|
split2(),
|
|
47
48
|
diffStream,
|
package/cli/ids.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Readable } from 'stream';
|
|
2
|
+
import { pipeline } from 'stream/promises';
|
|
2
3
|
import { stream } from '@miso.ai/server-commons';
|
|
3
4
|
import { MisoClient } from '../src/index.js';
|
|
4
5
|
import diff from './ids-diff.js';
|
|
@@ -38,7 +39,7 @@ const run = type => async ({
|
|
|
38
39
|
const readStream = Readable.from(ids);
|
|
39
40
|
const outputStream = new stream.OutputStream();
|
|
40
41
|
|
|
41
|
-
await
|
|
42
|
+
await pipeline(
|
|
42
43
|
readStream,
|
|
43
44
|
outputStream,
|
|
44
45
|
);
|
package/cli/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import status from './status.js';
|
|
|
12
12
|
import get from './get.js';
|
|
13
13
|
import search from './search.js';
|
|
14
14
|
import hybridSearch from './hybrid-search.js';
|
|
15
|
+
import upgrade from './upgrade.js';
|
|
15
16
|
|
|
16
17
|
const interactions = {
|
|
17
18
|
command: 'interactions',
|
|
@@ -54,6 +55,7 @@ yargs.build(yargs => {
|
|
|
54
55
|
.command(products)
|
|
55
56
|
.command(users)
|
|
56
57
|
.command(transform)
|
|
58
|
+
.command(upgrade)
|
|
57
59
|
.command(mergeLocal)
|
|
58
60
|
.command(search)
|
|
59
61
|
.command(hybridSearch)
|
package/cli/merge.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { join } from 'path';
|
|
2
2
|
import { createReadStream } from 'fs';
|
|
3
3
|
import { createGunzip } from 'zlib';
|
|
4
|
+
import { pipeline } from 'stream/promises';
|
|
4
5
|
import split2 from 'split2';
|
|
5
6
|
import { stream } from '@miso.ai/server-commons';
|
|
6
7
|
import { MisoClient } from '../src/index.js';
|
|
@@ -36,10 +37,9 @@ const run = type => async ({
|
|
|
36
37
|
const client = new MisoClient({ env, key, server, debug });
|
|
37
38
|
const mergeStream = client.api[type].mergeStream({ ...options, mergeFn, records });
|
|
38
39
|
const outputStream = new stream.OutputStream({ objectMode: true });
|
|
39
|
-
await
|
|
40
|
+
await pipeline(
|
|
40
41
|
process.stdin,
|
|
41
|
-
split2(),
|
|
42
|
-
stream.parse(),
|
|
42
|
+
split2(JSON.parse),
|
|
43
43
|
mergeStream,
|
|
44
44
|
outputStream
|
|
45
45
|
);
|
package/cli/search.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Readable } from 'stream';
|
|
2
|
+
import { pipeline } from 'stream/promises';
|
|
2
3
|
import { stream } from '@miso.ai/server-commons';
|
|
3
4
|
import { MisoClient } from '../src/index.js';
|
|
4
5
|
import { buildForApi, buildForSearch } from './utils.js';
|
|
@@ -16,7 +17,7 @@ async function run({ query, fq, fl, rows, env, key, server, debug }) {
|
|
|
16
17
|
const readStream = Readable.from(records);
|
|
17
18
|
const outputStream = new stream.OutputStream();
|
|
18
19
|
|
|
19
|
-
await
|
|
20
|
+
await pipeline(
|
|
20
21
|
readStream,
|
|
21
22
|
outputStream,
|
|
22
23
|
);
|
package/cli/status.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { pipeline } from 'stream/promises';
|
|
1
2
|
import split2 from 'split2';
|
|
2
3
|
import { stream } from '@miso.ai/server-commons';
|
|
3
4
|
import { MisoClient } from '../src/index.js';
|
|
@@ -31,14 +32,14 @@ async function runOne(client, type, taskId) {
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
async function runStream(client, type) {
|
|
34
|
-
await
|
|
35
|
+
await pipeline(
|
|
35
36
|
process.stdin,
|
|
36
37
|
split2(),
|
|
37
38
|
client.api[type].statusStream(),
|
|
38
39
|
new stream.OutputStream({
|
|
39
40
|
objectMode: true,
|
|
40
41
|
}),
|
|
41
|
-
|
|
42
|
+
);
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
export default function(type) {
|
package/cli/transform.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { pipeline } from 'stream/promises';
|
|
1
2
|
import split2 from 'split2';
|
|
2
3
|
import { stream } from '@miso.ai/server-commons';
|
|
3
4
|
|
|
@@ -11,19 +12,15 @@ function build(yargs) {
|
|
|
11
12
|
|
|
12
13
|
async function run({ file }) {
|
|
13
14
|
const transform = await stream.getTransformStream(file);
|
|
14
|
-
const streams = [
|
|
15
|
-
process.stdin,
|
|
16
|
-
split2(),
|
|
17
|
-
];
|
|
18
|
-
if (transform.writableObjectMode) {
|
|
19
|
-
streams.push(stream.parse());
|
|
20
|
-
}
|
|
21
|
-
streams.push(transform);
|
|
22
|
-
streams.push(new stream.OutputStream({
|
|
23
|
-
objectMode: transform.readableObjectMode,
|
|
24
|
-
}));
|
|
25
15
|
|
|
26
|
-
await
|
|
16
|
+
await pipeline(
|
|
17
|
+
process.stdin,
|
|
18
|
+
transform.writableObjectMode ? split2(JSON.parse) : split2(),
|
|
19
|
+
transform,
|
|
20
|
+
new stream.OutputStream({
|
|
21
|
+
objectMode: transform.readableObjectMode,
|
|
22
|
+
}),
|
|
23
|
+
);
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
export default {
|
package/cli/upgrade.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { pipeline } from 'stream/promises';
|
|
2
|
+
import split2 from 'split2';
|
|
3
|
+
import { stream, UpgradeChannel, splitObj } from '@miso.ai/server-commons';
|
|
4
|
+
|
|
5
|
+
function build(yargs) {
|
|
6
|
+
return yargs
|
|
7
|
+
.option('name', {
|
|
8
|
+
describe: 'Channel name',
|
|
9
|
+
type: 'string',
|
|
10
|
+
})
|
|
11
|
+
.option('id-field', {
|
|
12
|
+
alias: 'id',
|
|
13
|
+
describe: 'Payload field to use as ID',
|
|
14
|
+
type: 'string',
|
|
15
|
+
})
|
|
16
|
+
.option('as-id', {
|
|
17
|
+
describe: 'Upgrade as ID',
|
|
18
|
+
type: 'boolean',
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function run(options) {
|
|
23
|
+
const [upgradeOptions] = splitObj(options, ['name', 'asId', 'idField']);
|
|
24
|
+
await pipeline(
|
|
25
|
+
process.stdin,
|
|
26
|
+
split2(),
|
|
27
|
+
new UpgradeChannel({ objectMode: false, ...upgradeOptions }),
|
|
28
|
+
new stream.OutputStream({ objectMode: true }),
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default {
|
|
33
|
+
command: ['upgrade', 'up'],
|
|
34
|
+
description: `Upgrade the stream to a channel`,
|
|
35
|
+
builder: build,
|
|
36
|
+
handler: run,
|
|
37
|
+
};
|
package/cli/upload.js
CHANGED
|
@@ -1,33 +1,20 @@
|
|
|
1
|
+
import { pipeline } from 'stream/promises';
|
|
1
2
|
import split2 from 'split2';
|
|
2
|
-
import { log, stream } from '@miso.ai/server-commons';
|
|
3
|
+
import { log, stream, splitObj } from '@miso.ai/server-commons';
|
|
3
4
|
import { MisoClient, logger, normalize } from '../src/index.js';
|
|
5
|
+
import { buildForWrite } from './utils.js';
|
|
4
6
|
|
|
5
7
|
function build(yargs) {
|
|
6
|
-
return yargs
|
|
8
|
+
return buildForWrite(yargs)
|
|
7
9
|
.option('dry-run', {
|
|
8
10
|
alias: ['dry'],
|
|
9
11
|
describe: 'Dry run mode',
|
|
12
|
+
type: 'boolean',
|
|
10
13
|
})
|
|
11
14
|
.option('lenient', {
|
|
12
15
|
describe: 'Accept some lenient record schema',
|
|
13
16
|
type: 'boolean',
|
|
14
17
|
})
|
|
15
|
-
.option('requests-per-second', {
|
|
16
|
-
alias: ['rps'],
|
|
17
|
-
describe: 'How many requests to send per second',
|
|
18
|
-
})
|
|
19
|
-
.option('records-per-request', {
|
|
20
|
-
alias: ['rpr'],
|
|
21
|
-
describe: 'How many records to send in a request',
|
|
22
|
-
})
|
|
23
|
-
.option('bytes-per-request', {
|
|
24
|
-
alias: ['bpr'],
|
|
25
|
-
describe: 'How many bytes to send in a request',
|
|
26
|
-
})
|
|
27
|
-
.option('bytes-per-second', {
|
|
28
|
-
alias: ['bps'],
|
|
29
|
-
describe: 'How many bytes to send per second',
|
|
30
|
-
})
|
|
31
18
|
.option('progress', {
|
|
32
19
|
alias: ['p'],
|
|
33
20
|
describe: 'Set log format progress',
|
|
@@ -49,6 +36,32 @@ const run = type => async ({
|
|
|
49
36
|
env,
|
|
50
37
|
key,
|
|
51
38
|
server,
|
|
39
|
+
channel,
|
|
40
|
+
...options
|
|
41
|
+
}) => {
|
|
42
|
+
const { debug } = options;
|
|
43
|
+
const client = new MisoClient({ env, key, server, debug });
|
|
44
|
+
|
|
45
|
+
if (channel) {
|
|
46
|
+
await runChannel(client, type, options);
|
|
47
|
+
} else {
|
|
48
|
+
await runStream(client, type, options);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
async function runChannel(client, type, options) {
|
|
53
|
+
const [uploadOptions] = splitObj(options, ['dryRun', 'params', 'requestsPerSecond', 'bytesPerSecond', 'recordsPerRequest', 'bytesPerRequest', 'debug']);
|
|
54
|
+
const uploadChannel = client.api[type].uploadChannel(uploadOptions);
|
|
55
|
+
|
|
56
|
+
await pipeline(
|
|
57
|
+
process.stdin,
|
|
58
|
+
split2(JSON.parse),
|
|
59
|
+
uploadChannel,
|
|
60
|
+
new stream.OutputStream({ objectMode: true }),
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function runStream(client, type, {
|
|
52
65
|
param: params,
|
|
53
66
|
['dry-run']: dryRun,
|
|
54
67
|
lenient,
|
|
@@ -56,19 +69,16 @@ const run = type => async ({
|
|
|
56
69
|
['records-per-request']: recordsPerRequest,
|
|
57
70
|
['bytes-per-request']: bytesPerRequest,
|
|
58
71
|
['bytes-per-second']: bytesPerSecond,
|
|
59
|
-
['experiment-id']: experimentId,
|
|
60
|
-
debug,
|
|
61
72
|
progress,
|
|
73
|
+
debug,
|
|
74
|
+
['experiment-id']: experimentId,
|
|
62
75
|
['stream-name']: name,
|
|
63
76
|
['log-level']: loglevel,
|
|
64
77
|
['log-format']: logFormat,
|
|
65
|
-
})
|
|
66
|
-
|
|
78
|
+
}) {
|
|
67
79
|
loglevel = (debug || progress) ? log.DEBUG : loglevel;
|
|
68
80
|
logFormat = progress ? logger.FORMAT.PROGRESS : logFormat;
|
|
69
81
|
|
|
70
|
-
const client = new MisoClient({ env, key, server, debug });
|
|
71
|
-
|
|
72
82
|
const uploadStreamObjectMode = lenient;
|
|
73
83
|
|
|
74
84
|
const uploadStream = client.api[type].uploadStream({
|
|
@@ -98,17 +108,18 @@ const run = type => async ({
|
|
|
98
108
|
// lenient: stdin -> split2 -> parse -> normalize -> upload -> log
|
|
99
109
|
// notice that the output of split2 are strings, while input/output of normalize are objects
|
|
100
110
|
|
|
101
|
-
await
|
|
111
|
+
await pipeline(
|
|
102
112
|
process.stdin,
|
|
103
|
-
split2(),
|
|
104
113
|
...(lenient ? [
|
|
105
|
-
|
|
114
|
+
split2(JSON.parse),
|
|
106
115
|
new normalize.Stream(type),
|
|
107
|
-
] : [
|
|
116
|
+
] : [
|
|
117
|
+
split2(),
|
|
118
|
+
]),
|
|
108
119
|
uploadStream,
|
|
109
120
|
logStream,
|
|
110
121
|
);
|
|
111
|
-
}
|
|
122
|
+
}
|
|
112
123
|
|
|
113
124
|
export default function(type) {
|
|
114
125
|
return {
|
package/cli/utils.js
CHANGED
|
@@ -49,6 +49,36 @@ export function buildForSearch(yargs) {
|
|
|
49
49
|
});
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
export function buildForWrite(yargs) {
|
|
53
|
+
return yargs
|
|
54
|
+
.option('channel', {
|
|
55
|
+
alias: ['c'],
|
|
56
|
+
describe: 'Use channel protocol',
|
|
57
|
+
type: 'boolean',
|
|
58
|
+
default: false,
|
|
59
|
+
})
|
|
60
|
+
.option('requests-per-second', {
|
|
61
|
+
alias: ['rps'],
|
|
62
|
+
describe: 'How many requests to send per second',
|
|
63
|
+
type: 'number',
|
|
64
|
+
})
|
|
65
|
+
.option('bytes-per-second', {
|
|
66
|
+
alias: ['bps'],
|
|
67
|
+
describe: 'How many bytes to send per second',
|
|
68
|
+
type: 'number',
|
|
69
|
+
})
|
|
70
|
+
.option('records-per-request', {
|
|
71
|
+
alias: ['rpr'],
|
|
72
|
+
describe: 'How many records to send in a request',
|
|
73
|
+
type: 'number',
|
|
74
|
+
})
|
|
75
|
+
.option('bytes-per-request', {
|
|
76
|
+
alias: ['bpr'],
|
|
77
|
+
describe: 'How many bytes to send in a request',
|
|
78
|
+
type: 'number',
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
52
82
|
export function formatError(err) {
|
|
53
83
|
const { response } = err;
|
|
54
84
|
if (response) {
|
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.6-beta.
|
|
19
|
+
"@miso.ai/server-commons": "0.6.6-beta.18",
|
|
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.6-beta.
|
|
26
|
+
"version": "0.6.6-beta.18"
|
|
27
27
|
}
|
package/src/api/base.js
CHANGED
|
@@ -4,6 +4,7 @@ import UploadStream from '../stream/upload.js';
|
|
|
4
4
|
import DeleteStream from '../stream/delete.js';
|
|
5
5
|
import StatusStream from '../stream/status.js';
|
|
6
6
|
import MergeStream from '../stream/merge.js';
|
|
7
|
+
import { UploadChannel, DeleteChannel } from '../channel/index.js';
|
|
7
8
|
|
|
8
9
|
export class Queries {
|
|
9
10
|
|
|
@@ -35,6 +36,10 @@ export class Writable {
|
|
|
35
36
|
return new UploadStream(this._client, this._type, options);
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
uploadChannel(options = {}) {
|
|
40
|
+
return new UploadChannel(this._client, this._type, options);
|
|
41
|
+
}
|
|
42
|
+
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
export class Entities extends Writable {
|
|
@@ -62,6 +67,10 @@ export class Entities extends Writable {
|
|
|
62
67
|
return new DeleteStream(this._client, this._type, options);
|
|
63
68
|
}
|
|
64
69
|
|
|
70
|
+
deleteChannel(options = {}) {
|
|
71
|
+
return new DeleteChannel(this._client, this._type, options);
|
|
72
|
+
}
|
|
73
|
+
|
|
65
74
|
async status(taskId) {
|
|
66
75
|
const url = buildUrl(this._client, `${this._type}/_status/${taskId}`);
|
|
67
76
|
return (await this._client._axios.get(url)).data;
|
package/src/api/helpers.js
CHANGED
|
@@ -34,16 +34,18 @@ async function recoverValidRecords(client, type, records, options, response) {
|
|
|
34
34
|
}
|
|
35
35
|
const url = buildUrl(client, type, options);
|
|
36
36
|
const validPayload = buildUploadPayload(validRecords);
|
|
37
|
+
let secondResponse;
|
|
37
38
|
try {
|
|
38
|
-
await client._axios.post(url, validPayload);
|
|
39
|
+
secondResponse = await client._axios.post(url, validPayload);
|
|
39
40
|
} catch (_) {
|
|
40
41
|
return; // still fail, never mind...
|
|
41
42
|
}
|
|
42
|
-
response.recovered = {
|
|
43
|
+
response.recovered = trimObj({
|
|
44
|
+
...secondResponse,
|
|
43
45
|
product_ids: validRecords.map(record => record.product_id),
|
|
44
46
|
records: validRecords.length,
|
|
45
47
|
bytes: validPayload.length * 2,
|
|
46
|
-
};
|
|
48
|
+
});
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
export async function merge(client, type, record, { mergeFn = defaultMerge } = {}) {
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { WriteChannelSink, WriteChannel, trimObj } from '@miso.ai/server-commons';
|
|
2
|
+
|
|
3
|
+
export function normalizeApiSinkGateOptions({
|
|
4
|
+
writesPerSecond = 10,
|
|
5
|
+
recordsPerSecond = 100000,
|
|
6
|
+
bytesPerSecond = 100 * 1024 * 1024,
|
|
7
|
+
...options
|
|
8
|
+
} = {}) {
|
|
9
|
+
return {
|
|
10
|
+
writesPerSecond,
|
|
11
|
+
recordsPerSecond,
|
|
12
|
+
bytesPerSecond,
|
|
13
|
+
...options,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function normalizeParams(params) {
|
|
18
|
+
if (!params || params.length === 0) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
return params.reduce((acc, param) => {
|
|
22
|
+
const [key, value = '1'] = param.split('=');
|
|
23
|
+
acc[key] = value;
|
|
24
|
+
return acc;
|
|
25
|
+
}, {});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function splitData(data, ids) {
|
|
29
|
+
ids = new Set(ids);
|
|
30
|
+
const positive = [];
|
|
31
|
+
const negative = [];
|
|
32
|
+
for (const record of data) {
|
|
33
|
+
if (ids.has(record.id)) {
|
|
34
|
+
positive.push(record);
|
|
35
|
+
} else {
|
|
36
|
+
negative.push(record);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return [positive, negative];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function writeIssuesToData(data, { groups = [] } = {}, { name: channel } = {}) {
|
|
43
|
+
for (const { index, violations = [] } of groups) {
|
|
44
|
+
try {
|
|
45
|
+
const errors = (data[index].errors || (data[index].errors = []));
|
|
46
|
+
errors.push(...(violations.map(v => trimObj({ channel, status: 422, ...v }))));
|
|
47
|
+
} catch(_) {}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function writeResponseErrorToFailedData(response, { name: channel } = {}) {
|
|
52
|
+
// 422 errors are already handled by writeIssuesToData
|
|
53
|
+
if (!response.error && (response.status < 400 || response.status === 422)) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const error = trimObj({
|
|
57
|
+
channel,
|
|
58
|
+
status: response.status,
|
|
59
|
+
message: response.error || response.statusText,
|
|
60
|
+
});
|
|
61
|
+
for (const event of response.failed.data) {
|
|
62
|
+
(event.errors || (event.errors = [])).push({ ...error });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export class ChannelApiSink extends WriteChannelSink {
|
|
67
|
+
|
|
68
|
+
constructor(client, options) {
|
|
69
|
+
super(options);
|
|
70
|
+
this._client = client;
|
|
71
|
+
// TODO: take axios options?
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
_normalizeOptions({
|
|
75
|
+
params,
|
|
76
|
+
dryRun,
|
|
77
|
+
...options
|
|
78
|
+
} = {}) {
|
|
79
|
+
return trimObj({
|
|
80
|
+
...super._normalizeOptions(options),
|
|
81
|
+
params: normalizeParams(params),
|
|
82
|
+
dryRun: !!dryRun,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async _write(request) {
|
|
87
|
+
const { payload, records, data } = request;
|
|
88
|
+
let response;
|
|
89
|
+
try {
|
|
90
|
+
response = await this._send(payload);
|
|
91
|
+
response = processMisoApiResponse(response);
|
|
92
|
+
response.writes = 1;
|
|
93
|
+
response.successful = { records, data };
|
|
94
|
+
response.failed = { records: 0, data: [] };
|
|
95
|
+
} catch(error) {
|
|
96
|
+
// not axios-handled error
|
|
97
|
+
if (!error.response) {
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
response = processMisoApiResponse(error.response);
|
|
101
|
+
if (typeof response !== 'object') {
|
|
102
|
+
response = trimObj({ error: response });
|
|
103
|
+
}
|
|
104
|
+
const { recovered, issues } = error.response;
|
|
105
|
+
// write issues into failed data events
|
|
106
|
+
// TODO: need to add channel name
|
|
107
|
+
writeIssuesToData(data, issues, this._channel);
|
|
108
|
+
// TODO: handle issue.unrecognized
|
|
109
|
+
|
|
110
|
+
if (recovered) {
|
|
111
|
+
const [positive, negative] = splitData(data, recovered.product_ids);
|
|
112
|
+
response.writes = 2;
|
|
113
|
+
response.successful = { records: positive.length, data: positive };
|
|
114
|
+
response.failed = { records: negative.length, data: negative };
|
|
115
|
+
response.recovered = processMisoApiResponse(recovered);
|
|
116
|
+
} else {
|
|
117
|
+
response.writes = 1;
|
|
118
|
+
response.successful = { records: 0, data: [] };
|
|
119
|
+
response.failed = { records, data };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// TODO: need to add channel name
|
|
124
|
+
writeResponseErrorToFailedData(response, this._channel);
|
|
125
|
+
|
|
126
|
+
return response;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async _send(payload) {
|
|
130
|
+
throw new Error(`Unimplemented.`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export class ApiWriteChannel extends WriteChannel {
|
|
136
|
+
|
|
137
|
+
constructor(client, type, options = {}) {
|
|
138
|
+
super({
|
|
139
|
+
...options,
|
|
140
|
+
sinkGate: normalizeApiSinkGateOptions(options),
|
|
141
|
+
});
|
|
142
|
+
this._client = client;
|
|
143
|
+
this._type = type;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async _runCustomTransform(event) {
|
|
147
|
+
switch (event.type) {
|
|
148
|
+
case 'data':
|
|
149
|
+
await this._runData(event);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
await super._runCustomTransform(event);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async _runData(event) {
|
|
156
|
+
await this.writeData(event);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
_createWriteEvent(context) {
|
|
160
|
+
return trimObj({
|
|
161
|
+
...super._createWriteEvent(context),
|
|
162
|
+
taskId: getTaskIdFromResponse(context.response),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function getTaskIdFromResponse(response) {
|
|
169
|
+
return getTaskIdFromResponse0(response) || getTaskIdFromResponse0(response.recovered);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function getTaskIdFromResponse0({ body } = {}) {
|
|
173
|
+
return body && body.data && body.data.task_id;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function maskApiKeyInMisoUrl(url) {
|
|
177
|
+
// mask the api_key from the url
|
|
178
|
+
return url.replace(/api_key=\w+/, 'api_key=****');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function processMisoApiResponse(response) {
|
|
182
|
+
if (typeof response !== 'object') {
|
|
183
|
+
return response;
|
|
184
|
+
}
|
|
185
|
+
const { data: body, status, statusText, config = {} } = response;
|
|
186
|
+
const { method, url } = config;
|
|
187
|
+
return trimObj({
|
|
188
|
+
status,
|
|
189
|
+
statusText,
|
|
190
|
+
method,
|
|
191
|
+
url: maskApiKeyInMisoUrl(url),
|
|
192
|
+
body,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { ChannelApiSink, ApiWriteChannel } from './api.js';
|
|
2
|
+
import { batchDelete } from '../api/helpers.js';
|
|
3
|
+
|
|
4
|
+
// channel //
|
|
5
|
+
export default class DeleteChannel extends ApiWriteChannel {
|
|
6
|
+
|
|
7
|
+
constructor(client, type, { name = 'delete', ...options } = {}) {
|
|
8
|
+
super(client, type, {
|
|
9
|
+
...options,
|
|
10
|
+
name,
|
|
11
|
+
buffer: normalizeBufferOptions(type, options),
|
|
12
|
+
sink: createSink(client, type, options),
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// buffer //
|
|
19
|
+
const DEFAULT_BUFFER_OPTIONS = Object.freeze({
|
|
20
|
+
payloadSuffix: ']}}',
|
|
21
|
+
payloadDelimiter: ',',
|
|
22
|
+
serialize: event => JSON.stringify(event.id),
|
|
23
|
+
recordCap: 1000,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
function normalizeBufferOptions(type, {
|
|
27
|
+
bytesPerRequest,
|
|
28
|
+
recordsPerRequest,
|
|
29
|
+
...options
|
|
30
|
+
}) {
|
|
31
|
+
const key = type === 'products' ? 'product_ids' : 'user_ids';
|
|
32
|
+
options = {
|
|
33
|
+
...DEFAULT_BUFFER_OPTIONS,
|
|
34
|
+
...options,
|
|
35
|
+
payloadPrefix: `{"data":{"${key}":[`,
|
|
36
|
+
};
|
|
37
|
+
if (recordsPerRequest) {
|
|
38
|
+
options.recordCap = recordsPerRequest;
|
|
39
|
+
}
|
|
40
|
+
if (bytesPerRequest) {
|
|
41
|
+
options.byteCap = bytesPerRequest;
|
|
42
|
+
}
|
|
43
|
+
return options;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// sink //
|
|
47
|
+
function createSink(client, type, options) {
|
|
48
|
+
switch (type) {
|
|
49
|
+
case 'users':
|
|
50
|
+
case 'products':
|
|
51
|
+
return new ChannelDeleteSink(client, { ...options, type });
|
|
52
|
+
default:
|
|
53
|
+
throw new Error(`Unrecognized type: ${type}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
class ChannelDeleteSink extends ChannelApiSink {
|
|
58
|
+
|
|
59
|
+
constructor(client, options) {
|
|
60
|
+
super(client, options);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async _send(payload) {
|
|
64
|
+
const { type, params } = this._options;
|
|
65
|
+
return await batchDelete(this._client, type, payload, { params });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { ChannelApiSink, ApiWriteChannel } from './api.js';
|
|
2
|
+
import { upload } from '../api/helpers.js';
|
|
3
|
+
|
|
4
|
+
// channel //
|
|
5
|
+
export default class UploadChannel extends ApiWriteChannel {
|
|
6
|
+
|
|
7
|
+
constructor(client, type, { name = 'upload', ...options } = {}) {
|
|
8
|
+
super(client, type, {
|
|
9
|
+
...options,
|
|
10
|
+
name,
|
|
11
|
+
buffer: normalizeBufferOptions(type, options),
|
|
12
|
+
sink: createSink(client, type, options),
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// buffer //
|
|
19
|
+
const DEFAULT_BUFFER_OPTIONS = Object.freeze({
|
|
20
|
+
payloadPrefix: '{"data":[',
|
|
21
|
+
payloadSuffix: ']}',
|
|
22
|
+
payloadDelimiter: ',',
|
|
23
|
+
byteCap: 1024 * 1024,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
function normalizeBufferOptions(type, { recordsPerRequest, bytesPerRequest, ...options } = {}) {
|
|
27
|
+
options = { ...DEFAULT_BUFFER_OPTIONS, ...options };
|
|
28
|
+
switch (type) {
|
|
29
|
+
case 'users':
|
|
30
|
+
case 'products':
|
|
31
|
+
options.recordCap = recordsPerRequest || 200;
|
|
32
|
+
break;
|
|
33
|
+
case 'interactions':
|
|
34
|
+
options.recordCap = recordsPerRequest || 1000;
|
|
35
|
+
break;
|
|
36
|
+
default:
|
|
37
|
+
throw new Error(`Unrecognized type: ${type}`);
|
|
38
|
+
}
|
|
39
|
+
if (bytesPerRequest) {
|
|
40
|
+
options.byteCap = bytesPerRequest;
|
|
41
|
+
}
|
|
42
|
+
return options;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// sink //
|
|
46
|
+
function createSink(client, type, options) {
|
|
47
|
+
switch (type) {
|
|
48
|
+
case 'users':
|
|
49
|
+
case 'products':
|
|
50
|
+
case 'interactions':
|
|
51
|
+
return new ChannelUploadSink(client, { ...options, type });
|
|
52
|
+
default:
|
|
53
|
+
throw new Error(`Unrecognized type: ${type}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
class ChannelUploadSink extends ChannelApiSink {
|
|
58
|
+
|
|
59
|
+
constructor(client, options) {
|
|
60
|
+
super(client, options);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async _send(payload) {
|
|
64
|
+
const { type, dryRun, params } = this._options;
|
|
65
|
+
return await upload(this._client, type, payload, { recoverValidRecordsOn422: true, dryRun, params });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
}
|
package/src/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export default '0.6.6-beta.
|
|
1
|
+
export default '0.6.6-beta.18';
|