@miso.ai/server-commons 0.5.4-beta.4 → 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.4"
19
+ "version": "0.5.4-beta.5"
20
20
  }
package/src/index.js CHANGED
@@ -5,7 +5,7 @@ export * from './file.js';
5
5
  export * from './config.js';
6
6
  export * from './async.js';
7
7
 
8
- export * as stream from './stream.js';
8
+ export * as stream from './stream/index.js';
9
9
  export * as log from './log.js';
10
10
 
11
11
  export { default as TaskQueue } from './task-queue.js';
@@ -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';
File without changes
@@ -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
+ }