@miso.ai/server-sdk 0.6.6-beta.16 → 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 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 stream.pipeline(
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 {
@@ -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 stream.pipeline(
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 stream.pipeline(
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 stream.pipeline(
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 stream.pipeline(
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 stream.pipeline(
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 stream.pipeline([
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 stream.pipeline(...streams);
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 stream.pipeline(
111
+ await pipeline(
102
112
  process.stdin,
103
- split2(),
104
113
  ...(lenient ? [
105
- stream.parse(),
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.16",
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.16"
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;
@@ -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 } = {}) {
@@ -135,18 +137,15 @@ export function process422ResponseBody(payload, { data } = {}) {
135
137
  }
136
138
 
137
139
  export async function batchDelete(client, type, ids, options = {}) {
138
- const url = buildUrl(client, `${type}/_delete`, { ...options, async: true });
140
+ const url = buildUrl(client, `${type}/_delete`, options);
139
141
  const payload = buildBatchDeletePayload(type, ids);
140
142
  const { data } = await client._axios.post(url, payload);
141
143
  return data;
142
144
  }
143
145
 
144
- export function buildUrl(client, path, { async, dryRun, params: extraParams } = {}) {
146
+ export function buildUrl(client, path, { dryRun, params: extraParams } = {}) {
145
147
  let { server, key } = client._options;
146
148
  let params = `?api_key=${key}`;
147
- if (async) {
148
- params += '&async=1';
149
- }
150
149
  if (dryRun) {
151
150
  params += '&dry_run=1';
152
151
  }
@@ -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,2 @@
1
+ export { default as DeleteChannel } from './delete.js';
2
+ export { default as UploadChannel } from './upload.js';
@@ -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
+ }
@@ -11,6 +11,8 @@ export default class DeleteStream extends stream.BufferedWriteStream {
11
11
  objectMode,
12
12
  heartbeatInterval,
13
13
  // sink
14
+ dryRun,
15
+ params,
14
16
  recordsPerSecond,
15
17
  // buffer
16
18
  recordsPerRequest,
@@ -35,6 +37,8 @@ export default class DeleteStream extends stream.BufferedWriteStream {
35
37
 
36
38
  this._sink = new DeleteSink(client, {
37
39
  type,
40
+ dryRun,
41
+ params,
38
42
  recordsPerSecond,
39
43
  });
40
44
 
package/src/version.js CHANGED
@@ -1 +1 @@
1
- export default '0.6.6-beta.16';
1
+ export default '0.6.6-beta.18';