@miso.ai/server-commons 0.5.4-beta.3 → 0.5.4-beta.5

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,5 +16,5 @@
16
16
  "js-yaml": "^4.1.0",
17
17
  "toml": "^3.0.0"
18
18
  },
19
- "version": "0.5.4-beta.3"
19
+ "version": "0.5.4-beta.5"
20
20
  }
package/src/async.js ADDED
@@ -0,0 +1,4 @@
1
+ export async function delay(duration) {
2
+ // TODO: can accept abort singal
3
+ return new Promise((resolve) => setTimeout(resolve, duration));
4
+ }
package/src/index.js CHANGED
@@ -1,6 +1,12 @@
1
1
  export * from './object.js';
2
- export * as stream from './stream.js';
2
+ export * from './string.js';
3
3
  export * from './date.js';
4
4
  export * from './file.js';
5
5
  export * from './config.js';
6
+ export * from './async.js';
7
+
8
+ export * as stream from './stream/index.js';
9
+ export * as log from './log.js';
10
+
6
11
  export { default as TaskQueue } from './task-queue.js';
12
+ export { default as Resolution } from './resolution.js';
package/src/log.js ADDED
@@ -0,0 +1,96 @@
1
+ import { padLeft, padRight } from './string.js';
2
+
3
+ export const FATAL = 'fatal';
4
+ export const ERROR = 'error';
5
+ export const WARNING = 'warning';
6
+ export const INFO = 'info';
7
+ export const DEBUG = 'debug';
8
+
9
+ const LEVEL_VALUES = {
10
+ [FATAL]: 2,
11
+ [ERROR]: 4,
12
+ [WARNING]: 6,
13
+ [INFO]: 8,
14
+ [DEBUG]: 50,
15
+ };
16
+ const FALLBACK_LEVEL_VALUE = 40;
17
+
18
+ export const LEVELS = Object.keys(LEVEL_VALUES);
19
+
20
+ LEVELS.sort(compareLevel);
21
+
22
+ export function reachesThreshold(level, threshold) {
23
+ if (!LEVEL_VALUES[threshold]) {
24
+ throw new Error(`Unrecognized log level: ${threshold}`);
25
+ }
26
+ if (!level) {
27
+ throw new Error(`Level is absent: ${level}`);
28
+ }
29
+ return compareLevel(level, threshold) <= 0;
30
+ }
31
+
32
+ export function isError(level) {
33
+ return reachesThreshold(level, ERROR);
34
+ }
35
+
36
+ function compareLevel(a, b) {
37
+ return (LEVEL_VALUES[a] || FALLBACK_LEVEL_VALUE) - (LEVEL_VALUES[b] || FALLBACK_LEVEL_VALUE);
38
+ }
39
+
40
+ export function formatDuration(value) {
41
+ if (typeof value !== 'number') {
42
+ throw new Error(`Value must be a number: ${value}`);
43
+ }
44
+ value = Math.floor(value);
45
+ const ms = padLeft(`${value % 60000}`, 5, '0');
46
+ let min = '00';
47
+ let hour = '00';
48
+ if (value >= 60000) {
49
+ value = Math.floor(value / 60000);
50
+ min = padLeft(`${value % 60}`, 2, '0');
51
+ if (value >= 60) {
52
+ hour = padLeft(Math.floor(value / 60), 2, '0');
53
+ }
54
+ }
55
+ return `${hour}:${min}:${ms.substring(0, 2)}.${ms.substring(2, 5)}`;
56
+ }
57
+
58
+ export function formatBytes(value) {
59
+ let i = 0;
60
+ for (; i < 4; i++) {
61
+ if (value < 1024) {
62
+ break;
63
+ }
64
+ value /= 1024;
65
+ }
66
+ return `${Math.floor(value * 100) / 100} ${UNITS[i]}`;
67
+ }
68
+
69
+ const UNITS = ['B', 'KB', 'MB', 'GB', 'TB'];
70
+
71
+ export function formatTable(rows, { columnPadding: columnSpacing = 3 } = {}) {
72
+ const maxLens = [];
73
+ for (const cells of rows) {
74
+ for (let j = 0, len = cells.length; j < len; j++) {
75
+ const len = `${cells[j]}`.length;
76
+ if (!maxLens[j] || maxLens[j] < len) {
77
+ maxLens[j] = len;
78
+ }
79
+ }
80
+ }
81
+ const colSpc = ' '.repeat(columnSpacing);
82
+ let str = '';
83
+ for (let i = 0, len = rows.length; i < len; i++) {
84
+ if (i > 0) {
85
+ str += '\n';
86
+ }
87
+ const cells = rows[i];
88
+ for (let j = 0, len = cells.length; j < len; j++) {
89
+ if (j > 0) {
90
+ str += colSpc;
91
+ }
92
+ str += padRight(`${cells[j]}`, maxLens[j], ' ');
93
+ }
94
+ }
95
+ return str;
96
+ }
@@ -0,0 +1,12 @@
1
+ export default class Resolution {
2
+
3
+ constructor() {
4
+ const self = this;
5
+ self.promise = new Promise((resolve, reject) => {
6
+ self.resolve = resolve;
7
+ self.reject = reject;
8
+ });
9
+ Object.freeze(this);
10
+ }
11
+
12
+ }
@@ -0,0 +1,299 @@
1
+ import { Readable } from 'stream';
2
+ import Denque from 'denque';
3
+ import { trimObj } from '../object.js';
4
+ import TaskQueue from '../task-queue.js';
5
+ import Resolution from '../resolution.js';
6
+
7
+ export default class BufferedReadStream extends Readable {
8
+
9
+ constructor(source, { strategy, filter, transform, onLoad, debug } = {}) {
10
+ super({ objectMode: true });
11
+ this._debug = debug || (() => {});
12
+ this._source = source;
13
+ this._strategy = new Strategy(strategy);
14
+ this._state = new State();
15
+ this._loads = new TaskQueue();
16
+ this._buckets = new Denque();
17
+
18
+ this._filter = filter || (() => true);
19
+ this._transform = transform || (v => v);
20
+ this._onLoad = onLoad || (() => {});
21
+ this._index = 0;
22
+
23
+ this._debug(`[BufferedReadStream] strategy: ${this._strategy}`);
24
+ this._strategy.initialize(this, source);
25
+ }
26
+
27
+ async _construct() {
28
+ if (this._source.init) {
29
+ this._debug(`[BufferedReadStream] init source start`);
30
+ await this._source.init();
31
+ this._debug(`[BufferedReadStream] init source done`);
32
+ }
33
+ }
34
+
35
+ async _read() {
36
+ const record = await this._next();
37
+ this.push(record != null ? record : null);
38
+ }
39
+
40
+ async _next() {
41
+ // TODO: put in action queue to support parallel call
42
+ const bucket = await this._peekBucket();
43
+ if (!bucket) {
44
+ return undefined;
45
+ }
46
+ const record = bucket[this._index++];
47
+ this._state.serve();
48
+ if (this._index >= bucket.length) {
49
+ this._buckets.shift();
50
+ this._index = 0;
51
+ }
52
+ return record;
53
+ }
54
+
55
+ async peek() {
56
+ // TODO: put in action queue to support parallel call
57
+ const bucket = await this._peekBucket();
58
+ return bucket && bucket[this._index];
59
+ }
60
+
61
+ async _peekBucket() {
62
+ if (this._buckets.isEmpty()) {
63
+ if (this._state.terminated) {
64
+ return undefined;
65
+ }
66
+ this._loadIfNecessary();
67
+ await this._waitForData();
68
+ }
69
+ return this._buckets.isEmpty() ? undefined : this._buckets.peekFront();
70
+ }
71
+
72
+ _loadIfNecessaryNextTick() {
73
+ process.nextTick(() => this._loadIfNecessary());
74
+ }
75
+
76
+ _loadIfNecessary() {
77
+ if (this._shallLoad()) {
78
+ this._load();
79
+ this._loadIfNecessaryNextTick(true);
80
+ }
81
+ }
82
+
83
+ async _load() {
84
+ const request = this._state.request(this._source.request());
85
+
86
+ this._debug(`[BufferedReadStream] Load request: ${request}`);
87
+ const { data, ...info } = await this._source.get(request);
88
+ const response = new Response(request, info);
89
+ this._debug(`[BufferedReadStream] Load response: ${JSON.stringify(response)} => data = ${data && data.length}`);
90
+
91
+ // TODO: support strategy option: keepOrder = false
92
+ this._loads.push(request.index, () => this._resolveLoad(response, data));
93
+ }
94
+
95
+ _resolveLoad(response, records) {
96
+ const state = this._state;
97
+ const strategy = this._strategy;
98
+
99
+ state.resolve(response);
100
+ this._debug(`[BufferedReadStream] Load resolved: ${response}`);
101
+
102
+ // apply terminate and filter function
103
+ let terminate = false;
104
+ const accepted = [];
105
+ for (const record of records) {
106
+ terminate = terminate || strategy.terminate(record, state);
107
+ if (!terminate && this._filter(record)) {
108
+ state.accept();
109
+ accepted.push(this._transform(record));
110
+ }
111
+ if (terminate) {
112
+ break;
113
+ }
114
+ }
115
+ if (terminate || (this._state.pendingLoads === 0 && this._state.exhausted)) {
116
+ state.terminate();
117
+ }
118
+
119
+ if (accepted.length > 0) {
120
+ this._buckets.push(accepted);
121
+ this._onLoad(accepted);
122
+ } else {
123
+ this._loadIfNecessaryNextTick(); // just in case
124
+ }
125
+
126
+ if (accepted.length > 0 || state.terminated) {
127
+ this._resolveDataPromise();
128
+ }
129
+ }
130
+
131
+ _waitForData() {
132
+ if (this._state.pendingLoads === 0) {
133
+ throw new Error(`No pending loads.`);
134
+ }
135
+ if (this._dataRes) {
136
+ throw new Error(`Parallel bucket peek.`);
137
+ }
138
+ return (this._dataRes = new Resolution()).promise;
139
+ }
140
+
141
+ _resolveDataPromise() {
142
+ if (this._dataRes) {
143
+ this._dataRes.resolve();
144
+ this._dataRes = undefined;
145
+ }
146
+ }
147
+
148
+ _shallLoad() {
149
+ const state = this._state;
150
+ if (state.exhausted) {
151
+ return false;
152
+ }
153
+ return !state.exhausted && this._strategy.shallLoad(state);
154
+ }
155
+
156
+ }
157
+
158
+ class Request {
159
+
160
+ constructor(info) {
161
+ this.timestamp = Date.now();
162
+ Object.assign(this, info);
163
+ Object.freeze(this);
164
+ }
165
+
166
+ toString() {
167
+ return JSON.stringify(this);
168
+ }
169
+
170
+ }
171
+
172
+ class Response {
173
+
174
+ constructor({ timestamp, ...request }, info) {
175
+ const now = this.timestamp = Date.now();
176
+ this.took = now - timestamp;
177
+ Object.assign(this, request);
178
+ Object.assign(this, info);
179
+ Object.freeze(this);
180
+ }
181
+
182
+ toString() {
183
+ return JSON.stringify(this);
184
+ }
185
+
186
+ }
187
+
188
+ class Strategy {
189
+
190
+ constructor({
191
+ highWatermark = 1000,
192
+ eagerLoad = false,
193
+ initialize,
194
+ shallLoad,
195
+ terminate,
196
+ } = {}) {
197
+ this.options = Object.freeze({ highWatermark, eagerLoad });
198
+ // overwrite methods
199
+ Object.assign(this, trimObj({ initialize, shallLoad, terminate }));
200
+ }
201
+
202
+ initialize(stream) {
203
+ if (this.options.eagerLoad) {
204
+ stream._loadIfNecessaryNextTick();
205
+ }
206
+ }
207
+
208
+ shallLoad(state) {
209
+ // TODO: we can have a slower start
210
+ return state.watermark < this.options.highWatermark;
211
+ }
212
+
213
+ terminate(record, state) {
214
+ return false;
215
+ }
216
+
217
+ toString() {
218
+ return JSON.stringify(this);
219
+ }
220
+
221
+ }
222
+
223
+ class State {
224
+
225
+ constructor() {
226
+ this.records = {
227
+ requested: 0,
228
+ resolved: 0,
229
+ accepted: 0,
230
+ served: 0,
231
+ };
232
+ this.loads = {
233
+ requested: 0,
234
+ resolved: 0,
235
+ };
236
+ this.took = 0;
237
+ this.exhausted = false;
238
+ this.terminated = false;
239
+ }
240
+
241
+ request({ records, exhaust, ...data }) {
242
+ // note that we may not know how many records will come form this request, so record sum may become NaN
243
+ this.records.requested += records;
244
+ const index = this.loads.requested++;
245
+ if (exhaust) {
246
+ this.exhaust();
247
+ }
248
+ return new Request({ ...data, index, records, exhaust });
249
+ }
250
+
251
+ resolve({ records, took, terminate }) {
252
+ // note that we may not know how many records will come form this request, so record sum may become NaN
253
+ this.records.resolved += records;
254
+ this.loads.resolved++;
255
+ this.took += took;
256
+ if (terminate) {
257
+ this.terminate();
258
+ }
259
+ }
260
+
261
+ accept() {
262
+ this.records.accepted++;;
263
+ }
264
+
265
+ serve() {
266
+ this.records.served++;
267
+ }
268
+
269
+ exhaust() {
270
+ this.exhausted = true;
271
+ }
272
+
273
+ terminate() {
274
+ this.exhaust();
275
+ this.terminated = true;
276
+ }
277
+
278
+ get pendingLoads() {
279
+ const { loads: loads } = this;
280
+ return loads.requested - loads.resolved;
281
+ }
282
+
283
+ get pendingRecords() {
284
+ const { records } = this;
285
+ return records.requested - records.resolved;
286
+ }
287
+
288
+ get unservedRecords() {
289
+ const { records } = this;
290
+ return records.accepted - records.served;
291
+ }
292
+
293
+ get watermark() {
294
+ let { pendingRecords } = this;
295
+ // TODO: better estimated pendingRecords when NaN
296
+ return (isNaN(pendingRecords) ? 0 : pendingRecords) + this.unservedRecords;
297
+ }
298
+
299
+ }
@@ -0,0 +1,3 @@
1
+ export * from './misc.js';
2
+ export { default as BufferedReadStream } from './buffered-read.js';
3
+ export { default as OutputStream } from './output.js';
@@ -12,12 +12,22 @@ export function parse() {
12
12
  export function stringify() {
13
13
  return new Transform({
14
14
  transform(chunk, _, callback) {
15
- callback(null, JSON.stringify(chunk) + '\n');
15
+ try {
16
+ callback(null, JSON.stringify(chunk) + '\n');
17
+ } catch(e) {
18
+ callback(null, `${chunk}` + '\n');
19
+ }
16
20
  },
17
21
  writableObjectMode: true,
18
22
  });
19
23
  }
20
24
 
25
+ export async function pipeline(...streams) {
26
+ return new Promise((resolve, reject) =>
27
+ _pipeline(...streams, err => err ? reject(err) : resolve())
28
+ );
29
+ }
30
+
21
31
  export async function pipelineToStdout(...streams) {
22
32
  return new Promise((resolve, reject) =>
23
33
  _pipeline(...streams, err => err ? reject(err) : resolve())
@@ -0,0 +1,24 @@
1
+ import { Writable } from 'stream';
2
+
3
+ export default class OutputStream extends Writable {
4
+
5
+ constructor({
6
+ out = process.stdout,
7
+ err = process.stderr,
8
+ format,
9
+ objectMode = true,
10
+ } = {}) {
11
+ super({
12
+ objectMode,
13
+ });
14
+ this._format = format || (v => `${v}`);
15
+ this._out = out;
16
+ this._err = err;
17
+ }
18
+
19
+ _write(record, _, next) {
20
+ this._out.write(this._format(record) + '\n');
21
+ next();
22
+ }
23
+
24
+ }
package/src/string.js ADDED
@@ -0,0 +1,32 @@
1
+ // From:
2
+ // https://github.com/jonschlinkert/pad-right
3
+
4
+ const DEFAULT_PAD = ' ';
5
+ const DEFAULT_PAD_5 = DEFAULT_PAD.repeat(5);
6
+ const DEFAULT_PAD_25 = DEFAULT_PAD.repeat(25);
7
+
8
+ function pad(left, val, num, str) {
9
+ const diff = num - val.length;
10
+ if (diff <= 0) {
11
+ return val;
12
+ }
13
+ const padding = buildPadding(diff, str);
14
+ return left ? padding + val : val + padding;
15
+ }
16
+
17
+ function buildPadding(count, str) {
18
+ // Breakpoints based on benchmarks to use the fastest approach
19
+ // for the given number of zeros
20
+ return str ? str.repeat(count) :
21
+ count <= 5 ? DEFAULT_PAD_5.slice(0, count) :
22
+ count <= 25 ? DEFAULT_PAD_25.slice(0, count) :
23
+ DEFAULT_PAD.repeat(count);
24
+ }
25
+
26
+ export function padLeft(val, num, str) {
27
+ return pad(true, val, num, str);
28
+ }
29
+
30
+ export function padRight(val, num, str) {
31
+ return pad(false, val, num, str);
32
+ }