@miso.ai/server-commons 0.6.5-beta.9 → 0.6.6-beta.1

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
@@ -6,7 +6,9 @@
6
6
  "publishConfig": {
7
7
  "access": "public"
8
8
  },
9
- "scripts": {},
9
+ "scripts": {
10
+ "test": "npx uvu test -i 'ignored'"
11
+ },
10
12
  "repository": "MisoAI/miso-server-js-sdk",
11
13
  "license": "MIT",
12
14
  "contributors": [
@@ -21,5 +23,5 @@
21
23
  "uuid": "^9.0.0",
22
24
  "yargs": "^17.5.1"
23
25
  },
24
- "version": "0.6.5-beta.9"
26
+ "version": "0.6.6-beta.1"
25
27
  }
package/src/date.js CHANGED
@@ -7,8 +7,6 @@ TS_PER_UNIT.m = TS_PER_UNIT.s * 60;
7
7
  TS_PER_UNIT.h = TS_PER_UNIT.m * 60;
8
8
  TS_PER_UNIT.d = TS_PER_UNIT.h * 24;
9
9
  TS_PER_UNIT.w = TS_PER_UNIT.d * 7;
10
- //TS_PER_UNIT.M = TS_PER_UNIT.d * 31;
11
- //TS_PER_UNIT.y = TS_PER_UNIT.d * 366;
12
10
 
13
11
  export function parseDuration(expr, unit) {
14
12
  if (expr === undefined || typeof expr === 'number') {
@@ -33,69 +31,202 @@ export function getYear(dateStr) {
33
31
  return new Date(dateStr).getFullYear();
34
32
  }
35
33
 
34
+ const RE_DATE_EXPR = /^(?:\d{4})|(?:\d{4}-[Qq\d]\d)|(?:\d{4}-\d{2}-[Ww\d]\d)$/;
35
+
36
+ // TODO: support hour, minute
37
+
36
38
  export function startOfDate(expr) {
39
+ return floorDate(expr);
40
+ }
41
+
42
+ export function endOfDate(expr) {
37
43
  if (expr === undefined) {
38
44
  return undefined;
39
45
  }
40
- if (typeof expr === 'number') {
41
- if (expr < 10000) {
42
- expr = startOfYearByYearNum(expr);
43
- }
44
- return expr;
46
+ return nextDate(expr) - 1000; // 1 sec
47
+ }
48
+
49
+ export function floorDate(expr, unit) {
50
+ validateUnit(unit);
51
+ if (expr === undefined) {
52
+ return undefined;
45
53
  }
46
- const ts = Date.parse(expr);
47
- if (isNaN(ts)) {
48
- throw new Error(`Unrecognized date: ${expr}`);
54
+ const [_unit, ts] = parseDateExpr(expr);
55
+ //console.error('floorDate', expr, unit, _unit, ts, unit === undefined || !isGranular(unit, _unit));
56
+ if (unit === undefined || isGranular(unit, _unit)) {
57
+ return ts;
49
58
  }
50
- return ts;
59
+ return floorTimestamp(ts, unit);
51
60
  }
52
61
 
53
- const RE_YEAR = /^\d{4}$/g;
54
- const RE_MONTH = /^\d{4}-\d{2}$/g;
55
- const RE_DATE = /^\d{4}-\d{2}-\d{2}$/g;
62
+ function floorTimestamp(ts, unit) {
63
+ switch (unit) {
64
+ case 'millisecond':
65
+ return ts;
66
+ case 'second':
67
+ return ts - ts % TS_PER_UNIT.s;
68
+ case 'minute':
69
+ return ts - ts % TS_PER_UNIT.m;
70
+ case 'hour':
71
+ return ts - ts % TS_PER_UNIT.h;
72
+ case 'day':
73
+ return ts - ts % TS_PER_UNIT.d;
74
+ }
75
+ const d = new Date(ts);
76
+ const year = d.getUTCFullYear();
77
+ const month = d.getUTCMonth() + 1;
78
+ const day = d.getUTCDate();
79
+ switch (unit) {
80
+ case 'week':
81
+ return Date.UTC(year, month - 1, day > 21 ? 22 : day - (day - 1) % 7);
82
+ case 'month':
83
+ return Date.UTC(year, month - 1);
84
+ case 'quarter':
85
+ return Date.UTC(year, month - 1 - (month - 1) % 3);
86
+ case 'year':
87
+ return Date.UTC(year);
88
+ default:
89
+ throw new Error(`Unrecognized unit: ${unit}`);
90
+ }
91
+ }
56
92
 
57
- // TODO: support end of hour, minute
93
+ export function ceilDate(expr, unit) {
94
+ validateUnit(unit);
95
+ if (expr === undefined) {
96
+ return undefined;
97
+ }
98
+ const [_unit, ts] = parseDateExpr(expr);
99
+ unit = unit || _unit;
100
+ const floored = floorTimestamp(ts, unit);
101
+ return floored === ts ? floored : nextTimestamp(floored, unit);
102
+ }
58
103
 
59
- export function endOfDate(expr) {
104
+ export function nextDate(expr, unit) {
105
+ validateUnit(unit);
60
106
  if (expr === undefined) {
61
107
  return undefined;
62
108
  }
63
- if (typeof expr === 'number') {
64
- if (expr < 10000) {
65
- expr = endOfYearByYearNum(expr);
66
- }
67
- return expr;
109
+ const [_unit, ts] = parseDateExpr(expr);
110
+ return nextTimestamp(ts + 1, unit || _unit);
111
+ }
112
+
113
+ function nextTimestamp(ts, unit) {
114
+ switch (unit) {
115
+ case 'millisecond':
116
+ return ts;
117
+ case 'second':
118
+ return ts + TS_PER_UNIT.s - ts % TS_PER_UNIT.s;
119
+ case 'minute':
120
+ return ts + TS_PER_UNIT.m - ts % TS_PER_UNIT.m;
121
+ case 'hour':
122
+ return ts + TS_PER_UNIT.h - ts % TS_PER_UNIT.h;
123
+ case 'day':
124
+ return ts + TS_PER_UNIT.d - ts % TS_PER_UNIT.d;
68
125
  }
69
- let ts = Date.parse(expr);
70
- if (isNaN(ts)) {
71
- throw new Error(`Unrecognized date: ${expr}`);
126
+ const d = new Date(ts);
127
+ const year = d.getUTCFullYear();
128
+ const month = d.getUTCMonth() + 1;
129
+ const day = d.getUTCDate();
130
+ switch (unit) {
131
+ case 'week':
132
+ return day > 21 ? Date.UTC(year, month, 1) : Date.UTC(year, month - 1, day - (day - 1) % 7 + 7);
133
+ case 'month':
134
+ return Date.UTC(year, month, 1);
135
+ case 'quarter':
136
+ return Date.UTC(year, month - month % 3 + 3);
137
+ case 'year':
138
+ return Date.UTC(year + 1);
139
+ default:
140
+ throw new Error(`Unrecognized unit: ${unit}`);
72
141
  }
73
- return expr.match(RE_YEAR) ? endOfYear(ts) :
74
- expr.match(RE_MONTH) ? endOfMonth(ts) :
75
- expr.match(RE_DATE) ? endOfDay(ts) : ts;
76
142
  }
77
143
 
78
- function endOfYear(ts) {
79
- return endOfYearByYearNum(new Date(ts).getFullYear());
144
+ export function prevDate(expr, unit) {
145
+ validateUnit(unit);
146
+ if (expr === undefined) {
147
+ return undefined;
148
+ }
149
+ const [_unit, ts] = parseDateExpr(expr);
150
+ return floorTimestamp(ts - 1, unit || _unit);
80
151
  }
81
152
 
82
- function startOfYearByYearNum(fullYear) {
83
- return new Date(fullYear, 0, 1).getTime();
84
- }
85
153
 
86
- function endOfYearByYearNum(fullYear) {
87
- return new Date(fullYear + 1, 0, 1).getTime() - 1000;
88
- }
89
154
 
90
- function endOfMonth(ts) {
91
- const date = new Date(ts);
92
- const year = date.getFullYear();
93
- const month = date.getMonth();
94
- return (month == 11 ? new Date(year + 1, 0, 1) : new Date(year, month + 1, 1)).getTime() - 1000;
155
+ const UNIT_GRANULARITY = {
156
+ 'millisecond': 10,
157
+ 'second': 9,
158
+ 'minute': 8,
159
+ 'hour': 7,
160
+ 'day': 6,
161
+ 'week': 5,
162
+ 'month': 4,
163
+ 'quarter': 3,
164
+ 'year': 2,
165
+ };
166
+
167
+ function validateUnit(unit) {
168
+ if (unit === undefined) {
169
+ return;
170
+ }
171
+ if (typeof unit !== 'string' || !UNIT_GRANULARITY[unit]) {
172
+ throw new Error(`Unrecognized unit: ${unit}`);
173
+ }
95
174
  }
96
175
 
97
- const MS_PER_DAY = 1000 * 60 * 60 * 24;
176
+ function isGranular(unit0, unit1) {
177
+ return UNIT_GRANULARITY[unit0] > UNIT_GRANULARITY[unit1];
178
+ }
98
179
 
99
- function endOfDay(ts) {
100
- return (Math.floor(ts / MS_PER_DAY) + 1) * MS_PER_DAY - 1000;
180
+ function parseDateExpr(expr) {
181
+ switch (typeof expr) {
182
+ case 'number':
183
+ if (expr < 100) {
184
+ throw new Error(`Unrecognized date: ${expr}`);
185
+ }
186
+ return expr < 10000 ? ['year', Date.UTC(expr)] : ['millisecond', expr];
187
+ case 'string':
188
+ expr = expr.trim();
189
+ const len = expr.length;
190
+ if (!RE_DATE_EXPR.test(expr)) {
191
+ throw new Error(`Unrecognized date: ${expr}`);
192
+ }
193
+ const year = parseInt(expr.slice(0, 4), 10);
194
+ if (isNaN(year)) {
195
+ throw new Error(`Unrecognized date: ${expr}`);
196
+ }
197
+ if (len === 4) {
198
+ return ['year', Date.UTC(year)];
199
+ }
200
+ if (len === 7 && (expr.charAt(5) === 'Q' || expr.charAt(5) === 'q')) {
201
+ const quarter = parseInt(expr.charAt(6), 10);
202
+ if (isNaN(quarter) || quarter < 1 || quarter > 4) {
203
+ throw new Error(`Unrecognized date: ${expr}`);
204
+ }
205
+ return ['quarter', Date.UTC(year, (quarter - 1) * 3)];
206
+ }
207
+ const month = parseInt(expr.slice(5, 7), 10);
208
+ if (isNaN(month) || month < 1 || month > 12) {
209
+ throw new Error(`Unrecognized date: ${expr}`);
210
+ }
211
+ if (len === 7) {
212
+ return ['month', Date.UTC(year, month - 1)];
213
+ }
214
+ if (expr.charAt(8) === 'W' || expr.charAt(8) === 'w') {
215
+ const week = parseInt(expr.charAt(9), 10);
216
+ if (isNaN(week) || week < 1 || week > 4) {
217
+ throw new Error(`Unrecognized date: ${expr}`);
218
+ }
219
+ return ['week', Date.UTC(year, month - 1, week * 7 - 6)];
220
+ }
221
+ let date;
222
+ try {
223
+ // parse so we can validate the day part
224
+ date = new Date(Date.parse(expr));
225
+ } catch (_) {
226
+ throw new Error(`Unrecognized date: ${expr}`);
227
+ }
228
+ return ['day', Date.UTC(year, month - 1, date.getUTCDate())];
229
+ default:
230
+ throw new Error(`Unrecognized date: ${expr}`);
231
+ }
101
232
  }
package/src/sink/bps.js CHANGED
@@ -11,11 +11,13 @@ export default class BpsSink {
11
11
  }
12
12
 
13
13
  _normalizeOptions({
14
+ writesPerSecond = 10,
14
15
  recordsPerSecord = 100000,
15
16
  bytesPerSecond = 100 * 1024 * 1024,
16
17
  ...options
17
18
  } = {}) {
18
19
  return {
20
+ writesPerSecond,
19
21
  recordsPerSecord,
20
22
  bytesPerSecond,
21
23
  ...options,
@@ -62,9 +64,10 @@ export default class BpsSink {
62
64
  const elapsed = now - this._firstWriteAt;
63
65
  const targetBps = this._targetBps(now);
64
66
  const targetRps = this._targetRps(now);
67
+ const targetWps = this._targetWps(now);
65
68
 
66
69
  const { started } = this._stats;
67
- const shallElapsed = Math.max(started.records / targetRps, started.bytes / targetBps) * 1000;
70
+ const shallElapsed = Math.max(started.records / targetRps, started.bytes / targetBps, started.count / targetWps) * 1000;
68
71
 
69
72
  const blockedTime = shallElapsed - elapsed;
70
73
  if (blockedTime <= 1000) {
@@ -86,4 +89,8 @@ export default class BpsSink {
86
89
  return this._options.recordsPerSecord;
87
90
  }
88
91
 
92
+ _targetWps(timestamp) {
93
+ return this._options.writesPerSecond;
94
+ }
95
+
89
96
  }
@@ -193,12 +193,13 @@ class Strategy {
193
193
 
194
194
  constructor({
195
195
  highWatermark = 1000,
196
+ maxPendingLoads = 100,
196
197
  eagerLoad = false,
197
198
  initialize,
198
199
  shallLoad,
199
200
  terminate,
200
201
  } = {}) {
201
- this.options = Object.freeze({ highWatermark, eagerLoad });
202
+ this.options = Object.freeze({ highWatermark, maxPendingLoads, eagerLoad });
202
203
  // overwrite methods
203
204
  Object.assign(this, trimObj({ initialize, shallLoad, terminate }));
204
205
  }
@@ -211,7 +212,7 @@ class Strategy {
211
212
 
212
213
  shallLoad(state) {
213
214
  // TODO: we can have a slower start
214
- return state.watermark < this.options.highWatermark;
215
+ return state.pendingLoads < this.options.maxPendingLoads && state.watermark < this.options.highWatermark;
215
216
  }
216
217
 
217
218
  terminate(record, state) {
@@ -183,6 +183,7 @@ export default class BufferedWriteStream extends Transform {
183
183
  result: failed ? 'failed' : 'successful',
184
184
  index: request.index,
185
185
  records: request.records,
186
+ recovered: response.recovered || { records: 0, bytes: 0 },
186
187
  bytes: request.bytes,
187
188
  time: response.timestamp - request.timestamp,
188
189
  });
package/src/yargs.js CHANGED
@@ -73,6 +73,6 @@ export function handleFail(msg, err) {
73
73
 
74
74
  export function coerceToArray(arg) {
75
75
  return Array.isArray(arg) ? arg :
76
- typeof arg === 'string' ? arg.split(',') :
76
+ typeof arg === 'string' ? arg.split(',').map(s => s.trim()) :
77
77
  arg === undefined || arg === null ? [] : [arg];
78
78
  }
@@ -0,0 +1,111 @@
1
+ import { test } from 'uvu';
2
+ import * as assert from 'uvu/assert';
3
+ import { floorDate, ceilDate, nextDate, prevDate, startOfDate, endOfDate } from '../src/index.js';
4
+
5
+ function testDateFn(fn, input, output) {
6
+ assert.equal(new Date(fn(input)).toISOString(), output, `${fn.name}(${input})`);
7
+ }
8
+
9
+ function testDateFnWithUnit(fn, input, unit, output) {
10
+ assert.equal(new Date(fn(input, unit)).toISOString(), output, `${fn.name}(${input}, ${unit})`);
11
+ }
12
+
13
+ test('startOfDate', () => {
14
+ testDateFn(startOfDate, '2025', '2025-01-01T00:00:00.000Z');
15
+ testDateFn(startOfDate, '2025-Q1', '2025-01-01T00:00:00.000Z');
16
+ testDateFn(startOfDate, '2025-q2', '2025-04-01T00:00:00.000Z');
17
+ testDateFn(startOfDate, '2025-Q3', '2025-07-01T00:00:00.000Z');
18
+ testDateFn(startOfDate, '2025-q4', '2025-10-01T00:00:00.000Z');
19
+ testDateFn(startOfDate, '2025-02', '2025-02-01T00:00:00.000Z');
20
+ testDateFn(startOfDate, '2025-02-W1', '2025-02-01T00:00:00.000Z');
21
+ testDateFn(startOfDate, '2025-02-w2', '2025-02-08T00:00:00.000Z');
22
+ testDateFn(startOfDate, '2025-02-03', '2025-02-03T00:00:00.000Z');
23
+ });
24
+
25
+ test('endOfDate', () => {
26
+ testDateFn(endOfDate, '2025', '2025-12-31T23:59:59.000Z');
27
+ testDateFn(endOfDate, '2025-Q1', '2025-03-31T23:59:59.000Z');
28
+ testDateFn(endOfDate, '2025-q2', '2025-06-30T23:59:59.000Z');
29
+ testDateFn(endOfDate, '2025-Q3', '2025-09-30T23:59:59.000Z');
30
+ testDateFn(endOfDate, '2025-q4', '2025-12-31T23:59:59.000Z');
31
+ testDateFn(endOfDate, '2025-02', '2025-02-28T23:59:59.000Z');
32
+ testDateFn(endOfDate, '2025-03-W1', '2025-03-07T23:59:59.000Z');
33
+ testDateFn(endOfDate, '2025-03-w2', '2025-03-14T23:59:59.000Z');
34
+ testDateFn(endOfDate, '2025-03-W3', '2025-03-21T23:59:59.000Z');
35
+ testDateFn(endOfDate, '2025-03-w4', '2025-03-31T23:59:59.000Z');
36
+ testDateFn(endOfDate, '2025-03-03', '2025-03-03T23:59:59.000Z');
37
+ });
38
+
39
+ test('floorDate', () => {
40
+ testDateFn(floorDate, '2025', '2025-01-01T00:00:00.000Z');
41
+ testDateFn(floorDate, '2025-Q1', '2025-01-01T00:00:00.000Z');
42
+ testDateFn(floorDate, '2025-q2', '2025-04-01T00:00:00.000Z');
43
+ testDateFn(floorDate, '2025-Q3', '2025-07-01T00:00:00.000Z');
44
+ testDateFn(floorDate, '2025-q4', '2025-10-01T00:00:00.000Z');
45
+ testDateFn(floorDate, '2025-02', '2025-02-01T00:00:00.000Z');
46
+ testDateFn(floorDate, '2025-02-W1', '2025-02-01T00:00:00.000Z');
47
+ testDateFn(floorDate, '2025-02-w2', '2025-02-08T00:00:00.000Z');
48
+ testDateFn(floorDate, '2025-02-03', '2025-02-03T00:00:00.000Z');
49
+
50
+ const ts = Date.parse('2025-05-10T04:05:06Z');
51
+ testDateFnWithUnit(floorDate, ts, 'day', '2025-05-10T00:00:00.000Z');
52
+ testDateFnWithUnit(floorDate, ts, 'week', '2025-05-08T00:00:00.000Z');
53
+ testDateFnWithUnit(floorDate, ts, 'month', '2025-05-01T00:00:00.000Z');
54
+ testDateFnWithUnit(floorDate, ts, 'quarter', '2025-04-01T00:00:00.000Z');
55
+ testDateFnWithUnit(floorDate, ts, 'year', '2025-01-01T00:00:00.000Z');
56
+ });
57
+
58
+ test('ceilDate', () => {
59
+ testDateFn(ceilDate, '2025', '2025-01-01T00:00:00.000Z');
60
+ testDateFn(ceilDate, '2025-Q1', '2025-01-01T00:00:00.000Z');
61
+ testDateFn(ceilDate, '2025-q2', '2025-04-01T00:00:00.000Z');
62
+ testDateFn(ceilDate, '2025-Q3', '2025-07-01T00:00:00.000Z');
63
+ testDateFn(ceilDate, '2025-q4', '2025-10-01T00:00:00.000Z');
64
+ testDateFn(ceilDate, '2025-02', '2025-02-01T00:00:00.000Z');
65
+ testDateFn(ceilDate, '2025-02-W1', '2025-02-01T00:00:00.000Z');
66
+ testDateFn(ceilDate, '2025-02-w2', '2025-02-08T00:00:00.000Z');
67
+ testDateFn(ceilDate, '2025-02-03', '2025-02-03T00:00:00.000Z');
68
+ });
69
+
70
+ test('nextDate', () => {
71
+ testDateFn(nextDate, '2025', '2026-01-01T00:00:00.000Z');
72
+ testDateFn(nextDate, '2025-Q1', '2025-04-01T00:00:00.000Z');
73
+ testDateFn(nextDate, '2025-q2', '2025-07-01T00:00:00.000Z');
74
+ testDateFn(nextDate, '2025-Q3', '2025-10-01T00:00:00.000Z');
75
+ testDateFn(nextDate, '2025-q4', '2026-01-01T00:00:00.000Z');
76
+ testDateFn(nextDate, '2025-02', '2025-03-01T00:00:00.000Z');
77
+ testDateFn(nextDate, '2025-02-W1', '2025-02-08T00:00:00.000Z');
78
+ testDateFn(nextDate, '2025-02-w2', '2025-02-15T00:00:00.000Z');
79
+ testDateFn(nextDate, '2025-02-W3', '2025-02-22T00:00:00.000Z');
80
+ testDateFn(nextDate, '2025-02-w4', '2025-03-01T00:00:00.000Z');
81
+ testDateFn(nextDate, '2025-02-03', '2025-02-04T00:00:00.000Z');
82
+ });
83
+
84
+ test('prevDate', () => {
85
+ testDateFn(prevDate, '2025', '2024-01-01T00:00:00.000Z');
86
+ testDateFn(prevDate, '2025-Q1', '2024-10-01T00:00:00.000Z');
87
+ testDateFn(prevDate, '2025-q2', '2025-01-01T00:00:00.000Z');
88
+ testDateFn(prevDate, '2025-Q3', '2025-04-01T00:00:00.000Z');
89
+ testDateFn(prevDate, '2025-q4', '2025-07-01T00:00:00.000Z');
90
+ testDateFn(prevDate, '2025-02', '2025-01-01T00:00:00.000Z');
91
+ testDateFn(prevDate, '2025-02-W1', '2025-01-22T00:00:00.000Z');
92
+ testDateFn(prevDate, '2025-02-w2', '2025-02-01T00:00:00.000Z');
93
+ testDateFn(prevDate, '2025-02-W3', '2025-02-08T00:00:00.000Z');
94
+ testDateFn(prevDate, '2025-02-w4', '2025-02-15T00:00:00.000Z');
95
+ testDateFn(prevDate, '2025-02-03', '2025-02-02T00:00:00.000Z');
96
+
97
+ const ts = Date.parse('2025-05-10T04:05:06Z');
98
+ testDateFnWithUnit(prevDate, ts, 'day', '2025-05-10T00:00:00.000Z');
99
+ testDateFnWithUnit(prevDate, ts, 'week', '2025-05-08T00:00:00.000Z');
100
+ testDateFnWithUnit(prevDate, ts, 'month', '2025-05-01T00:00:00.000Z');
101
+ testDateFnWithUnit(prevDate, ts, 'quarter', '2025-04-01T00:00:00.000Z');
102
+ testDateFnWithUnit(prevDate, ts, 'year', '2025-01-01T00:00:00.000Z');
103
+
104
+ testDateFnWithUnit(prevDate, '2025', 'day', '2024-12-31T00:00:00.000Z');
105
+ testDateFnWithUnit(prevDate, '2025', 'week', '2024-12-22T00:00:00.000Z');
106
+ testDateFnWithUnit(prevDate, '2025', 'month', '2024-12-01T00:00:00.000Z');
107
+ testDateFnWithUnit(prevDate, '2025', 'quarter', '2024-10-01T00:00:00.000Z');
108
+
109
+ });
110
+
111
+ test.run();