dolphindb 2.0.603 → 2.0.801

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/index.js CHANGED
@@ -4,8 +4,8 @@ import DayjsCustomParseFormat from 'dayjs/plugin/customParseFormat.js';
4
4
  dayjs.extend(DayjsCustomParseFormat);
5
5
  import ipaddrjs from 'ipaddr.js';
6
6
  const { fromByteArray: buf2ipaddr } = ipaddrjs;
7
+ import { concat, assert, inspect, typed_array_to_buffer } from 'xshell/utils.js';
7
8
  import { connect_websocket } from 'xshell/net.js';
8
- import { concat, inspect, typed_array_to_buffer } from 'xshell/utils.js';
9
9
  export var DdbForm;
10
10
  (function (DdbForm) {
11
11
  DdbForm[DdbForm["scalar"] = 0] = "scalar";
@@ -62,7 +62,11 @@ export var DdbType;
62
62
  DdbType[DdbType["complex"] = 34] = "complex";
63
63
  DdbType[DdbType["point"] = 35] = "point";
64
64
  DdbType[DdbType["duration"] = 36] = "duration";
65
- DdbType[DdbType["object"] = 37] = "object";
65
+ DdbType[DdbType["decimal32"] = 37] = "decimal32";
66
+ DdbType[DdbType["decimal64"] = 38] = "decimal64";
67
+ DdbType[DdbType["decimal128"] = 39] = "decimal128";
68
+ DdbType[DdbType["object"] = 40] = "object";
69
+ DdbType[DdbType["pynone"] = 41] = "pynone";
66
70
  DdbType[DdbType["symbol_extended"] = 145] = "symbol_extended";
67
71
  })(DdbType = DdbType || (DdbType = {}));
68
72
  export var DdbFunctionType;
@@ -114,7 +118,15 @@ export const nulls = {
114
118
  bytes16: Uint8Array.from(new Array(16).fill(0))
115
119
  };
116
120
  export const timezone_offset = 1000 * 60 * new Date().getTimezoneOffset();
117
- /** 可以表示所有 DolphinDB 数据库中的数据类型 */
121
+ export var StreamingStatusCode;
122
+ (function (StreamingStatusCode) {
123
+ StreamingStatusCode[StreamingStatusCode["ok"] = 0] = "ok";
124
+ StreamingStatusCode[StreamingStatusCode["connection_existed"] = 1] = "connection_existed";
125
+ StreamingStatusCode[StreamingStatusCode["not_leader"] = 2] = "not_leader";
126
+ StreamingStatusCode[StreamingStatusCode["other"] = 3] = "other";
127
+ StreamingStatusCode[StreamingStatusCode["error"] = 4] = "error";
128
+ })(StreamingStatusCode = StreamingStatusCode || (StreamingStatusCode = {}));
129
+ /** Can represent data types in all DolphinDB databases */
118
130
  export class DdbObj {
119
131
  static dec = new TextDecoder('utf-8');
120
132
  static enc = new TextEncoder();
@@ -146,7 +158,7 @@ export class DdbObj {
146
158
  rows;
147
159
  /** 第 2 维 */
148
160
  cols;
149
- /** 实际数据。不同的 DdbForm, DdbType 使用 DdbValue 中不同的类型来表示实际数据 */
161
+ /** The actual data. Different DdbForm, DdbType use different types in DdbValue to represent actual data */
150
162
  value;
151
163
  /** 原始二进制数据,仅在 parse_object 为 false 时通过 parse_message 生成的顶层对象有这个属性 */
152
164
  buffer;
@@ -331,8 +343,7 @@ export class DdbObj {
331
343
  col_labels.length += 2;
332
344
  offset += col_labels.length;
333
345
  }
334
- if (buf_data[offset] !== type)
335
- throw new Error('matrix.datatype !== matrix.type');
346
+ assert(buf_data[offset] === type, 'matrix.datatype === matrix.type');
336
347
  const rows = dv.getUint32(offset + 2, le);
337
348
  const cols = dv.getUint32(offset + 6, le);
338
349
  const [len_items, data] = this.parse_vector_items(buf_data.subarray(offset + 10), le, type, rows * cols // 假设小于 2**32
@@ -443,38 +454,30 @@ export class DdbObj {
443
454
  case DdbType.uuid:
444
455
  case DdbType.ipaddr:
445
456
  case DdbType.int128:
446
- return [
447
- 16,
448
- buf.slice(0, 16)
449
- ];
457
+ return [16, buf.slice(0, 16)];
450
458
  case DdbType.blob: {
451
459
  const dv = new DataView(buf.buffer, buf.byteOffset);
452
460
  const length = dv.getUint32(0, le);
453
- return [
454
- 4 + length,
455
- buf.slice(4, 4 + length)
456
- ];
461
+ return [4 + length, buf.slice(4, 4 + length)];
457
462
  }
458
463
  case DdbType.complex:
459
464
  case DdbType.point: {
460
465
  const dv = new DataView(buf.buffer, buf.byteOffset);
461
- return [
462
- 16,
463
- [
464
- dv.getFloat64(0, le),
465
- dv.getFloat64(8, le)
466
- ]
467
- ];
466
+ return [16, [dv.getFloat64(0, le), dv.getFloat64(8, le)]];
468
467
  }
469
468
  case DdbType.duration: {
470
469
  const dv = new DataView(buf.buffer, buf.byteOffset);
471
- return [
472
- 8,
473
- {
474
- unit: dv.getUint32(4, le),
475
- data: dv.getInt32(0, le)
476
- }
477
- ];
470
+ return [8, { unit: dv.getUint32(4, le), data: dv.getInt32(0, le) }];
471
+ }
472
+ case DdbType.decimal32: {
473
+ const dv = new DataView(buf.buffer, buf.byteOffset);
474
+ const data = dv.getInt32(4, le);
475
+ return [8, { scale: dv.getInt32(0, le), data: data === nulls.int32 ? null : data }];
476
+ }
477
+ case DdbType.decimal64: {
478
+ const dv = new DataView(buf.buffer, buf.byteOffset);
479
+ const data = dv.getBigInt64(4, le);
480
+ return [12, { scale: dv.getInt32(0, le), data: data === nulls.int64 ? null : data }];
478
481
  }
479
482
  default:
480
483
  throw new Error(`${DdbType[type] || type} 暂时不支持解析`);
@@ -635,7 +638,7 @@ export class DdbObj {
635
638
  // 02 00 00 00 symbol base size = 2
636
639
  // 00 61 61 00 以 \0 分割的字符串
637
640
  // 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00>
638
- const dv = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
641
+ const dv = new DataView(buf.buffer, buf.byteOffset);
639
642
  const base_id = dv.getUint32(0, le);
640
643
  const base_size = dv.getUint32(4, le);
641
644
  let base_length = 0;
@@ -670,7 +673,7 @@ export class DdbObj {
670
673
  // 04 00 00 00 61 62 63 64
671
674
  // 04 00 00 00 61 62 63 64>
672
675
  let value = new Array(length);
673
- const dv = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
676
+ const dv = new DataView(buf.buffer, buf.byteOffset);
674
677
  let i_head = 0;
675
678
  for (let i = 0; i < length; i++) {
676
679
  const sublen = dv.getUint32(i_head, le);
@@ -710,6 +713,31 @@ export class DdbObj {
710
713
  values
711
714
  ];
712
715
  }
716
+ // 25 01 type = decimal32, form = vector
717
+ // 02 00 00 00 01 00 00 00
718
+ // 00 00 00 00 scale = 0
719
+ // 01 00 00 00 data[0] = 1
720
+ // 3a 01 00 00 data[1] = 0x013a = 314
721
+ case DdbType.decimal32: {
722
+ const dv = new DataView(buf.buffer, buf.byteOffset);
723
+ return [
724
+ 4 + 4 * length,
725
+ {
726
+ scale: dv.getInt32(0, le),
727
+ data: new Int32Array(buf.buffer.slice(buf.byteOffset + 4, buf.byteOffset + 4 + 4 * length))
728
+ }
729
+ ];
730
+ }
731
+ case DdbType.decimal64: {
732
+ const dv = new DataView(buf.buffer, buf.byteOffset);
733
+ return [
734
+ 4 + 8 * length,
735
+ {
736
+ scale: dv.getInt32(0, le),
737
+ data: new BigInt64Array(buf.buffer.slice(buf.byteOffset + 4, buf.byteOffset + 4 + 8 * length))
738
+ }
739
+ ];
740
+ }
713
741
  // case DdbType.duration: -> 实际会返回一个 any vector
714
742
  // [2y, 1M, 3d, 7H, 11m, 12s, 15ms, 16us, 17ns]
715
743
  // <Buffer 19 01 type = any, form = vector
@@ -813,10 +841,7 @@ export class DdbObj {
813
841
  case DdbType.int128:
814
842
  return [this.value];
815
843
  case DdbType.blob:
816
- return [
817
- Uint32Array.of(this.value.byteLength),
818
- this.value
819
- ];
844
+ return [Uint32Array.of(this.value.byteLength), this.value];
820
845
  case DdbType.complex:
821
846
  case DdbType.point:
822
847
  return [Float64Array.from(this.value)];
@@ -824,8 +849,16 @@ export class DdbObj {
824
849
  const { unit, data } = this.value;
825
850
  return [Int32Array.of(data, unit)];
826
851
  }
852
+ case DdbType.decimal32: {
853
+ const { scale, data } = this.value;
854
+ return [Int32Array.of(scale, data === null ? nulls.int32 : data)];
855
+ }
856
+ case DdbType.decimal64: {
857
+ const { scale, data } = this.value;
858
+ return [Int32Array.of(scale), BigInt64Array.of(data === null ? nulls.int64 : data)];
859
+ }
827
860
  default:
828
- throw new Error(`${DdbType[type] || type} 暂时不支持序列化`);
861
+ throw new Error(`${DdbType[type] || type} serialization is not currently supported`);
829
862
  }
830
863
  case DdbForm.vector:
831
864
  case DdbForm.pair:
@@ -845,12 +878,7 @@ export class DdbObj {
845
878
  ...DdbObj.pack_vector_body(value, type, this.rows)
846
879
  ];
847
880
  case DdbForm.set:
848
- return [
849
- new DdbObj({
850
- ...this,
851
- form: DdbForm.vector,
852
- }).pack()
853
- ];
881
+ return [new DdbObj({ ...this, form: DdbForm.vector }).pack()];
854
882
  case DdbForm.table:
855
883
  return [
856
884
  Uint32Array.of(this.rows, this.cols),
@@ -862,43 +890,27 @@ export class DdbObj {
862
890
  ...this.value.map(col => col.pack())
863
891
  ];
864
892
  case DdbForm.dict:
865
- return [
866
- value[0].pack(),
867
- value[1].pack(),
868
- ];
893
+ return [value[0].pack(), value[1].pack()];
869
894
  case DdbForm.chart: {
870
- const { type, stacking, bin_start, bin_end, bin_count, titles: { chart, x_axis, y_axis, }, extras, data } = this.value;
895
+ const { type, stacking, bin_start, bin_end, bin_count, titles: { chart, x_axis, y_axis }, extras, data } = this.value;
871
896
  const { value: [keys, values] } = new DdbDict({
872
897
  chartType: new DdbInt(type),
873
898
  stacking,
874
- ...bin_start ? {
875
- binStart: bin_start,
876
- binEnd: bin_end
877
- } : {},
878
- ...bin_count ? {
879
- binCount: bin_count
880
- } : {},
899
+ ...bin_start ? { binStart: bin_start, binEnd: bin_end } : {},
900
+ ...bin_count ? { binCount: bin_count } : {},
881
901
  title: new DdbVectorString([chart, x_axis, y_axis]),
882
902
  ...extras ? (() => {
883
903
  const { multi_y_axes, ...extras_other } = extras;
884
- return {
885
- extras: new DdbDict({
886
- multiYAxes: multi_y_axes,
887
- ...extras_other
888
- })
889
- };
904
+ return { extras: new DdbDict({ multiYAxes: multi_y_axes, ...extras_other }) };
890
905
  })() : {},
891
906
  data,
892
907
  });
893
- return [
894
- keys.pack(),
895
- values.pack(),
896
- ];
908
+ return [keys.pack(), values.pack()];
897
909
  }
898
910
  case DdbForm.matrix: {
899
911
  const { rows, cols, data } = value;
900
912
  return [
901
- Uint8Array.of((rows && 0x01) | (cols && 0x02)),
913
+ Uint8Array.of((rows ? 0x01 : 0x00) | (cols ? 0x02 : 0x00)),
902
914
  ...rows ? [rows.pack()] : [],
903
915
  ...cols ? [cols.pack()] : [],
904
916
  Uint8Array.of(this.type, this.form),
@@ -907,7 +919,7 @@ export class DdbObj {
907
919
  ];
908
920
  }
909
921
  default:
910
- throw new Error(`${DdbForm[form]} 暂不支持序列化`);
922
+ throw new Error(`${DdbForm[form]} serialization is not currently supported`);
911
923
  }
912
924
  })();
913
925
  if (!body)
@@ -996,11 +1008,17 @@ export class DdbObj {
996
1008
  data
997
1009
  ];
998
1010
  }
1011
+ case DdbType.decimal32:
1012
+ case DdbType.decimal64: {
1013
+ const { scale, data } = value;
1014
+ return [Int32Array.of(scale), data];
1015
+ }
999
1016
  default:
1000
1017
  throw new Error(`vector ${DdbType[type]} 暂不支持序列化`);
1001
1018
  }
1002
1019
  }
1003
- [inspect.custom](depth, options, _inspect) {
1020
+ [inspect.custom](depth, _options, _inspect) {
1021
+ const options = { nullstr: true, quote: true, ..._options };
1004
1022
  const type = this.inspect_type();
1005
1023
  const data = (() => {
1006
1024
  switch (this.form) {
@@ -1075,6 +1093,15 @@ export class DdbObj {
1075
1093
  items[i] = format(this.type, value.subarray(2 * i, 2 * (i + 1)), this.le, options);
1076
1094
  return format_array(items, len_data > limit);
1077
1095
  }
1096
+ case DdbType.decimal32:
1097
+ case DdbType.decimal64: {
1098
+ const limit = 50;
1099
+ const { data } = this.value;
1100
+ let items = new Array(Math.min(limit, data.length));
1101
+ for (let i = 0; i < items.length; i++)
1102
+ items[i] = formati(this, i, options);
1103
+ return format_array(items, data.length > limit);
1104
+ }
1078
1105
  default: {
1079
1106
  const limit = 50;
1080
1107
  let items = new Array(Math.min(limit, this.value.length));
@@ -1137,8 +1164,7 @@ export class DdbObj {
1137
1164
  return values.map(value => this.to_ddbobj(value));
1138
1165
  }
1139
1166
  to_rows() {
1140
- if (this.form !== DdbForm.table)
1141
- throw new Error('this.form is not DdbForm.table, cannot to_rows');
1167
+ assert(this.form === DdbForm.table, 'form must be DdbForm.table, otherwise it cannot to_rows');
1142
1168
  let rows = new Array(this.rows);
1143
1169
  for (let i = 0; i < this.rows; i++) {
1144
1170
  let row = {};
@@ -1170,13 +1196,10 @@ export class DdbObj {
1170
1196
  return rows;
1171
1197
  }
1172
1198
  to_dict({ strip, deep, } = {}) {
1173
- if (this.form !== DdbForm.dict)
1174
- throw new Error('this.form is not DdbForm.dict and cannot be converted to js object');
1199
+ assert(this.form === DdbForm.dict, 'this.form must be DdbForm.dict, otherwise to_dict cannot be called to convert to js object');
1175
1200
  const [{ value: keys, type: key_type }, { value: values, type: value_type }] = this.value;
1176
- if (key_type !== DdbType.string || value_type !== DdbType.any)
1177
- throw new Error('Not dict<string, any>, automatic conversion to js object is not supported for the time being');
1178
- if (deep && !strip)
1179
- throw new Error('strip = true must be set when deep = true');
1201
+ assert(key_type === DdbType.string && value_type === DdbType.any, 'Currently only supports automatic conversion of dict<string, any> to js object');
1202
+ assert(!(deep && !strip), 'strip = true must be set when deep = true');
1180
1203
  let obj = {};
1181
1204
  for (let i = 0; i < this.rows; i++) {
1182
1205
  let value = values[i];
@@ -1188,8 +1211,23 @@ export class DdbObj {
1188
1211
  return obj;
1189
1212
  }
1190
1213
  }
1214
+ /** 整数一定用这个 number formatter, InspectOptions.decimals 不传也用这个 */
1215
+ let default_formatter = Intl.NumberFormat('en-US', { maximumFractionDigits: 20 });
1216
+ let _decimals = 20;
1217
+ /** 缓存,为了优化性能,通常 options.decimals 都是不变的 */
1218
+ let _formatter = Intl.NumberFormat('en-US', { maximumFractionDigits: 20 });
1191
1219
  /** Formats a single element (value) as a string according to DdbType, null returns a 'null' string */
1192
- export function format(type, value, le, options) {
1220
+ export function format(type, value, le, options = {}) {
1221
+ const { decimals } = options;
1222
+ const formatter = (() => {
1223
+ if (decimals === undefined || decimals === null)
1224
+ return default_formatter;
1225
+ if (decimals !== _decimals) {
1226
+ _decimals = decimals;
1227
+ _formatter = Intl.NumberFormat('en-US', { maximumFractionDigits: decimals, minimumFractionDigits: decimals });
1228
+ }
1229
+ return _formatter;
1230
+ })();
1193
1231
  switch (type) {
1194
1232
  case DdbType.bool:
1195
1233
  return inspect((value === null || value === nulls.int8) ?
@@ -1207,111 +1245,123 @@ export function format(type, value, le, options) {
1207
1245
  :
1208
1246
  value, options);
1209
1247
  case DdbType.short:
1210
- return inspect((value === null || value === nulls.int16) ?
1211
- null
1248
+ return value === null || value === nulls.int16 ?
1249
+ inspect(null, options)
1212
1250
  :
1213
- value, options);
1251
+ options.colors ?
1252
+ default_formatter.format(value).green
1253
+ :
1254
+ default_formatter.format(value);
1214
1255
  case DdbType.int:
1215
- return inspect((value === null || value === nulls.int32) ?
1216
- null
1256
+ return value === null || value === nulls.int32 ?
1257
+ inspect(null, options)
1217
1258
  :
1218
- value, options);
1259
+ options.colors ?
1260
+ default_formatter.format(value).green
1261
+ :
1262
+ default_formatter.format(value);
1219
1263
  case DdbType.long:
1220
- return (value === null || value === nulls.int64) ?
1221
- 'null'
1264
+ return value === null || value === nulls.int64 ?
1265
+ inspect(null, options)
1222
1266
  :
1223
1267
  options.colors ?
1224
- String(value).green
1268
+ default_formatter.format(value).green
1225
1269
  :
1226
- String(value);
1270
+ default_formatter.format(value);
1227
1271
  case DdbType.date:
1228
1272
  return (value === null || value === nulls.int32) ?
1229
- 'null'
1273
+ inspect(null, options)
1230
1274
  :
1231
1275
  options.colors ?
1232
- date2str(value).green
1276
+ date2str(value).magenta
1233
1277
  :
1234
1278
  date2str(value);
1235
1279
  case DdbType.month:
1236
1280
  return (value === null || value === nulls.int32) ?
1237
- 'null'
1281
+ inspect(null, options)
1238
1282
  :
1239
1283
  options.colors ?
1240
- month2str(value).green
1284
+ month2str(value).magenta
1241
1285
  :
1242
1286
  month2str(value);
1243
1287
  case DdbType.time:
1244
1288
  return (value === null || value === nulls.int32) ?
1245
- 'null'
1289
+ inspect(null, options)
1246
1290
  :
1247
1291
  options.colors ?
1248
- time2str(value).green
1292
+ time2str(value).magenta
1249
1293
  :
1250
1294
  time2str(value);
1251
1295
  case DdbType.minute:
1252
1296
  return (value === null || value === nulls.int32) ?
1253
- 'null'
1297
+ inspect(null, options)
1254
1298
  :
1255
1299
  options.colors ?
1256
- minute2str(value).green
1300
+ minute2str(value).magenta
1257
1301
  :
1258
1302
  minute2str(value);
1259
1303
  case DdbType.second:
1260
1304
  return (value === null || value === nulls.int32) ?
1261
- 'null'
1305
+ inspect(null, options)
1262
1306
  :
1263
1307
  options.colors ?
1264
- second2str(value).green
1308
+ second2str(value).magenta
1265
1309
  :
1266
1310
  second2str(value);
1267
1311
  case DdbType.datetime:
1268
1312
  return (value === null || value === nulls.int32) ?
1269
- 'null'
1313
+ inspect(null, options)
1270
1314
  :
1271
1315
  options.colors ?
1272
- datetime2str(value).green
1316
+ datetime2str(value).magenta
1273
1317
  :
1274
1318
  datetime2str(value);
1275
1319
  case DdbType.timestamp:
1276
1320
  return (value === null || value === nulls.int64) ?
1277
- 'null'
1321
+ inspect(null, options)
1278
1322
  :
1279
1323
  options.colors ?
1280
- timestamp2str(value).green
1324
+ timestamp2str(value).magenta
1281
1325
  :
1282
1326
  timestamp2str(value);
1283
1327
  case DdbType.nanotime:
1284
1328
  return (value === null || value === nulls.int64) ?
1285
- 'null'
1329
+ inspect(null, options)
1286
1330
  :
1287
1331
  options.colors ?
1288
- nanotime2str(value).green
1332
+ nanotime2str(value).magenta
1289
1333
  :
1290
1334
  nanotime2str(value);
1291
1335
  case DdbType.nanotimestamp:
1292
1336
  return (value === null || value === nulls.int64) ?
1293
- 'null'
1337
+ inspect(null, options)
1294
1338
  :
1295
1339
  options.colors ?
1296
- nanotimestamp2str(value).green
1340
+ nanotimestamp2str(value).magenta
1297
1341
  :
1298
1342
  nanotimestamp2str(value);
1299
1343
  case DdbType.float:
1300
1344
  return (value === null || value === nulls.float32) ?
1301
- 'null'
1345
+ inspect(null, options)
1302
1346
  :
1303
- inspect(value, options);
1347
+ options.colors ?
1348
+ formatter.format(value).green
1349
+ :
1350
+ formatter.format(value);
1304
1351
  case DdbType.double:
1305
1352
  return (value === null || value === nulls.double) ?
1306
- 'null'
1353
+ inspect(null, options)
1307
1354
  :
1308
- inspect(value, options);
1355
+ options.colors ?
1356
+ formatter.format(value).green
1357
+ :
1358
+ formatter.format(value);
1309
1359
  case DdbType.symbol:
1310
1360
  case DdbType.string:
1311
1361
  return inspect(value, options);
1312
1362
  case DdbType.uuid:
1313
1363
  return options.colors ?
1314
- uuid2str(value, le).green
1364
+ uuid2str(value, le).cyan
1315
1365
  :
1316
1366
  uuid2str(value, le);
1317
1367
  case DdbType.functiondef:
@@ -1325,12 +1375,12 @@ export function format(type, value, le, options) {
1325
1375
  'null'
1326
1376
  :
1327
1377
  options.colors ?
1328
- datehour2str(value).green
1378
+ datehour2str(value).magenta
1329
1379
  :
1330
1380
  datehour2str(value);
1331
1381
  case DdbType.ipaddr:
1332
1382
  return options.colors ?
1333
- ipaddr2str(value, le).green
1383
+ ipaddr2str(value, le).cyan
1334
1384
  :
1335
1385
  ipaddr2str(value, le);
1336
1386
  case DdbType.int128:
@@ -1344,30 +1394,27 @@ export function format(type, value, le, options) {
1344
1394
  :
1345
1395
  DdbObj.dec.decode(value), options);
1346
1396
  case DdbType.point: {
1347
- let [x, y] = value;
1348
- if (x === nulls.double)
1349
- x = null;
1350
- if (y === nulls.double)
1351
- y = null;
1352
- return options.colors ?
1353
- `(${String(x).green}, ${String(y).green})`
1354
- :
1355
- `(${String(x)}, ${String(y)})`;
1397
+ const [x, y] = value;
1398
+ return `(${format(DdbType.double, x, le, options)}, ${format(DdbType.double, y, le, options)})`;
1356
1399
  }
1357
1400
  case DdbType.complex: {
1358
- let [x, y] = value;
1359
- if (x === nulls.double)
1360
- x = null;
1361
- if (y === nulls.double)
1362
- y = null;
1363
- return options.colors ?
1364
- `${String(x).green}+${`${String(y)}i`.green}`
1365
- :
1366
- `${String(x)}+${String(y)}i`;
1401
+ const [x, y] = value;
1402
+ return `${format(DdbType.double, x, le, options)}+${format(DdbType.double, y, le, options)}i`;
1367
1403
  }
1368
1404
  case DdbType.duration: {
1369
1405
  const { data, unit } = value;
1370
1406
  const str = `${data}${DdbDurationUnit[unit]}`;
1407
+ return options.colors ? str.magenta : str;
1408
+ }
1409
+ case DdbType.decimal32:
1410
+ case DdbType.decimal64: {
1411
+ const { scale, data } = value;
1412
+ if (data === null ||
1413
+ (data === nulls.int64 && type === DdbType.decimal64) ||
1414
+ (data === nulls.int32 && type === DdbType.decimal32))
1415
+ return 'null';
1416
+ const s = String(data < 0 ? -data : data).padStart(scale, '0');
1417
+ const str = (data < 0 ? '-' : '') + (scale ? `${s.slice(0, -scale) || '0'}.${s.slice(-scale)}` : s);
1371
1418
  return options.colors ? str.green : str;
1372
1419
  }
1373
1420
  default:
@@ -1375,7 +1422,7 @@ export function format(type, value, le, options) {
1375
1422
  }
1376
1423
  }
1377
1424
  /** formatted vector, the index-th item in the collection is a string, a null value returns a 'null' string */
1378
- export function formati(obj, index, options) {
1425
+ export function formati(obj, index, options = {}) {
1379
1426
  if (64 <= obj.type && obj.type < 128) { // array vector
1380
1427
  // 因为 array vector 目前只支持:Logical, Integral(不包括 INT128, COMPRESS 类型), Floating, Temporal
1381
1428
  // 都对应 TypedArray 中的一格,所以 lengths.length 等于 block 中的 row 的个数
@@ -1409,12 +1456,9 @@ export function formati(obj, index, options) {
1409
1456
  }
1410
1457
  }
1411
1458
  switch (obj.type) {
1412
- case DdbType.string:
1413
- case DdbType.symbol:
1414
- return obj.value[index];
1415
1459
  case DdbType.symbol_extended: {
1416
1460
  const { base, data } = obj.value;
1417
- return base[data[index]];
1461
+ return format(DdbType.string, base[data[index]], obj.le, options);
1418
1462
  }
1419
1463
  case DdbType.uuid:
1420
1464
  case DdbType.int128:
@@ -1430,6 +1474,17 @@ export function formati(obj, index, options) {
1430
1474
  case DdbType.complex:
1431
1475
  case DdbType.point:
1432
1476
  return format(obj.type, obj.value.subarray(2 * index, 2 * (index + 1)), obj.le, options);
1477
+ case DdbType.decimal32:
1478
+ case DdbType.decimal64: {
1479
+ const { scale, data } = obj.value;
1480
+ const x = data[index];
1481
+ if (x === nulls.int64 && obj.type === DdbType.decimal64 ||
1482
+ x === nulls.int32 && obj.type === DdbType.decimal32)
1483
+ return '';
1484
+ const s = String(x < 0 ? -x : x).padStart(scale, '0');
1485
+ const str = (x < 0 ? '-' : '') + (scale ? `${s.slice(0, -scale) || '0'}.${s.slice(-scale)}` : s);
1486
+ return options.colors ? str.green : str;
1487
+ }
1433
1488
  default:
1434
1489
  return format(obj.type, obj.value[index], obj.le, options);
1435
1490
  }
@@ -1844,8 +1899,7 @@ export function datehour2str(datehour, format = 'YYYY.MM.DDTHH') {
1844
1899
  export function str2timestamp(str, format = 'YYYY.MM.DD HH:mm:ss.SSS') {
1845
1900
  if (!str || str === 'null')
1846
1901
  return nulls.int64;
1847
- if (str.length !== format.length)
1848
- throw new Error('The length of the timestamp string is not equal to the length of the format string');
1902
+ assert('The length of the timestamp string must be equal to the length of the format string');
1849
1903
  return BigInt(-timezone_offset +
1850
1904
  dayjs(str, format).valueOf());
1851
1905
  }
@@ -1858,12 +1912,10 @@ export function nanotime2str(nanotime, format = 'HH:mm:ss.SSSSSSSSS') {
1858
1912
  if (nanotime < 0n)
1859
1913
  return String(nanotime);
1860
1914
  const i_second_start = format.indexOf('ss');
1861
- if (i_second_start === -1)
1862
- throw new Error('The format string must contain the format for seconds (ss)');
1915
+ assert(i_second_start !== -1, 'The format string must contain the format for seconds (ss)');
1863
1916
  const i_second_end = i_second_start + 2;
1864
1917
  const i_nanosecond_start = format.indexOf('SSSSSSSSS', i_second_end);
1865
- if (i_nanosecond_start === -1)
1866
- throw new Error('The format string must contain the format for nanoseconds (SSSSSSSSS)');
1918
+ assert(i_nanosecond_start !== -1, 'Format string must contain nanosecond format (SSSSSSSSS)');
1867
1919
  return (dayjs(timezone_offset + (Number(nanotime) / 1000000)).format(format.slice(0, i_second_end)) +
1868
1920
  format.slice(i_second_end, i_nanosecond_start) +
1869
1921
  String(nanotime % 1000000000n).padStart(9, '0'));
@@ -1891,12 +1943,10 @@ export function nanotimestamp2str(nanotimestamp, format = 'YYYY.MM.DD HH:mm:ss.S
1891
1943
  if (nanotimestamp === null || nanotimestamp === nulls.int64)
1892
1944
  return 'null';
1893
1945
  const i_second_start = format.indexOf('ss');
1894
- if (i_second_start === -1)
1895
- throw new Error('The format string must contain the format for seconds (ss)');
1946
+ assert(i_second_start !== -1, 'The format string must contain the format for seconds (ss)');
1896
1947
  const i_second_end = i_second_start + 2;
1897
1948
  const i_nanosecond_start = format.indexOf('SSSSSSSSS', i_second_end);
1898
- if (i_nanosecond_start === -1)
1899
- throw new Error('The format string must contain the format for nanoseconds (SSSSSSSSS)');
1949
+ assert(i_nanosecond_start !== -1, 'Format string must contain nanosecond format (SSSSSSSSS)');
1900
1950
  const remainder = nanotimestamp % 1000000000n;
1901
1951
  const borrow = remainder < 0n;
1902
1952
  return (dayjs(timezone_offset +
@@ -1918,15 +1968,12 @@ export function nanotimestamp2str(nanotimestamp, format = 'YYYY.MM.DD HH:mm:ss.S
1918
1968
  export function str2nanotimestamp(str, format = 'YYYY.MM.DD HH:mm:ss.SSSSSSSSS') {
1919
1969
  if (!str || str === 'null')
1920
1970
  return nulls.int64;
1921
- if (str.length !== format.length)
1922
- throw new Error('nanotimestamp string length is not equal to format string length');
1971
+ assert(str.length === format.length, 'nanotimestamp string length must be equal to format string length');
1923
1972
  const i_second_start = format.indexOf('ss');
1924
- if (i_second_start === -1)
1925
- throw new Error('The format string must contain the format for seconds (ss)');
1973
+ assert(i_second_start !== -1, 'The format string must contain the format for seconds (ss)');
1926
1974
  const i_second_end = i_second_start + 2;
1927
1975
  const i_nanosecond_start = format.indexOf('SSSSSSSSS', i_second_end);
1928
- if (i_nanosecond_start === -1)
1929
- throw new Error('Format string must contain nanosecond format (SSSSSSSSS)');
1976
+ assert(i_nanosecond_start !== -1, 'Format string must contain nanosecond format (SSSSSSSSS)');
1930
1977
  return (BigInt(-timezone_offset +
1931
1978
  dayjs(str.slice(0, i_second_end), format.slice(0, i_second_end)).valueOf()) * 1000000n
1932
1979
  +
@@ -1954,6 +2001,14 @@ export function int1282str(buffer, le = true) {
1954
2001
  return [...buf].map(x => x.toString(16)
1955
2002
  .padStart(2, '0')).join('');
1956
2003
  }
2004
+ export const winsize = 40;
2005
+ export class ConnectionError extends Error {
2006
+ ddb;
2007
+ constructor(ddb) {
2008
+ super(`websocket is disconnected: ${ddb.url}`);
2009
+ this.ddb = ddb;
2010
+ }
2011
+ }
1957
2012
  export class DDB {
1958
2013
  /** 当前的 session id (http 或 tcp) */
1959
2014
  sid = '0';
@@ -1977,6 +2032,8 @@ export class DDB {
1977
2032
  password = '123456';
1978
2033
  /** python session flag (2048) */
1979
2034
  python = false;
2035
+ /** Whether it is a streaming data connection, this field is always null for non-streaming data */
2036
+ streaming = null;
1980
2037
  // --- 内部选项, 状态
1981
2038
  print_message_buffer = false;
1982
2039
  print_object_buffer = false;
@@ -2000,11 +2057,12 @@ export class DDB {
2000
2057
  - username?: DolphinDB username, default `'admin'`
2001
2058
  - password?: DolphinDB password, default `'123456'`
2002
2059
  - python?: set python session flag, default `false`
2060
+ - streaming?: When this option is set, the WebSocket connection is only used for streaming data
2003
2061
 
2004
2062
  @example
2005
2063
  let ddb = new DDB('ws://127.0.0.1:8848')
2006
2064
 
2007
- // 使用 HTTPS 加密
2065
+ // Encrypt with HTTPS
2008
2066
  let ddbsecure = new DDB('wss://dolphindb.com', {
2009
2067
  autologin: true,
2010
2068
  username: 'admin',
@@ -2022,43 +2080,54 @@ export class DDB {
2022
2080
  this.password = options.password;
2023
2081
  if (options.python !== undefined)
2024
2082
  this.python = options.python;
2083
+ if (options.streaming !== undefined)
2084
+ this.streaming = options.streaming;
2025
2085
  }
2026
- on_message(event) { }
2027
- /** Establish the actual WebSocket connection to the DolphinDB corresponding to the URL
2028
- - options?:
2029
- - url?: DolphinDB WebSocket URL. By default, the WebSocket URL passed in when the instance is initialized is used
2030
- - autologin?: Whether to log in automatically after establishing a connection, default `true`
2031
- - username?: DolphinDB username, default `'admin'`
2032
- - password?: DolphinDB password, default `'123456'`
2033
- - python?: set python session flag, default `false`
2034
- */
2035
- async connect(options = {}) {
2036
- if (options.url !== undefined)
2037
- this.url = options.url;
2038
- if (options.autologin !== undefined)
2039
- this.autologin = options.autologin;
2040
- if (options.username !== undefined)
2041
- this.username = options.username;
2042
- if (options.password !== undefined)
2043
- this.password = options.password;
2044
- if (options.python !== undefined)
2045
- this.python = options.python;
2046
- this.disconnect();
2047
- await connect_websocket(this.url, {
2048
- protocols: this.python ? ['python'] : [],
2049
- on_open: async (event, websocket) => {
2050
- this.websocket = websocket;
2051
- await this.rpc('connect', {});
2052
- },
2053
- on_message: (event) => {
2054
- this.on_message(event);
2055
- }
2086
+ on_message(event) {
2087
+ // 这里的实现一定会被 connect 中的实现覆盖
2088
+ }
2089
+ /** Establish a actual websocket connection to the DolphindB corresponding to the URL
2090
+ It is a power, and after calling, it will ensure that it has been connected to the database (ensure that websocket.ReadyState is open), otherwise an error will be reported
2091
+ this.autologin automatically log in when it is true */
2092
+ async connect() {
2093
+ if (this.connected)
2094
+ return;
2095
+ const ptail = this.pconnect;
2096
+ let resolve;
2097
+ this.pconnect = new Promise((_resolve, _reject) => {
2098
+ resolve = _resolve;
2056
2099
  });
2057
- if (this.autologin)
2058
- if (this.python)
2059
- await this.eval(`login(${this.username.quote('double')}, ${this.password.quote('double')})`, { urgent: true });
2060
- else
2061
- await this.call('login', [this.username, this.password], { urgent: true });
2100
+ await ptail;
2101
+ try {
2102
+ if (!this.connected) {
2103
+ this.on_message = (event) => {
2104
+ assert(false, '这是在调用 this.rpc 之前默认的 on_message, 不应该被调用到,除非建立连接后 server 先推送了 message');
2105
+ };
2106
+ this.presult = Promise.resolve(null);
2107
+ this.pnode_run_defined = false;
2108
+ this.websocket = await connect_websocket(this.url, {
2109
+ protocols: (() => {
2110
+ if (this.streaming)
2111
+ return 'streaming';
2112
+ if (this.python)
2113
+ return 'python';
2114
+ })(),
2115
+ on_message: (event) => {
2116
+ this.on_message(event);
2117
+ }
2118
+ });
2119
+ if (this.streaming)
2120
+ await this.subscribe();
2121
+ else {
2122
+ await this.rpc('connect', {});
2123
+ if (this.autologin)
2124
+ await this.call('login', [this.username, this.password], { urgent: true });
2125
+ }
2126
+ }
2127
+ }
2128
+ finally {
2129
+ resolve();
2130
+ }
2062
2131
  }
2063
2132
  get_rpc_options({ urgent = false, secondary = false, async: _async = false, pickle = false, clear = false, api = false, compress = false, cancellable = true, priority = urgent ? 8 : 4, parallelism = 8, root_id = '', limit, } = {}) {
2064
2133
  let flag = 0;
@@ -2094,11 +2163,9 @@ export class DDB {
2094
2163
  disconnect() {
2095
2164
  if (this.connected)
2096
2165
  this.websocket.close(1000);
2097
- this.on_message = () => { };
2098
- this.presult = Promise.resolve(null);
2099
- this.pconnect = Promise.resolve();
2100
2166
  }
2101
2167
  /** rpc through websocket (function/script/variable command)
2168
+ When the DDB is not connected, the call will be automatically connected. When the connection is disconnected, the call will throw the Connectionerror
2102
2169
  - type: API 类型: 'script' | 'function' | 'variable'
2103
2170
  - options:
2104
2171
  - urgent?: 决定 `行为标识` 那一行字符串的取值(只适用于 script 和 function)
@@ -2108,24 +2175,12 @@ export class DDB {
2108
2175
  为 false 时返回的 DdbObj 仅含有 buffer 和 le,不做解析,以便后续转发、序列化
2109
2176
  */
2110
2177
  async rpc(type, { script, func, args = [], vars = [], urgent, listener, parse_object, }) {
2111
- if (!this.websocket) {
2112
- const ptail = this.pconnect;
2113
- let resolve;
2114
- this.pconnect = new Promise((_resolve, _reject) => {
2115
- resolve = _resolve;
2116
- });
2117
- await ptail;
2118
- try {
2119
- if (!this.websocket)
2120
- await this.connect();
2121
- }
2122
- finally {
2123
- resolve();
2124
- }
2125
- }
2178
+ if (!this.websocket)
2179
+ await this.connect();
2126
2180
  if (!this.connected)
2127
- throw new Error(`${this.url} is already disconnected`);
2181
+ throw new ConnectionError(this);
2128
2182
  if (func === 'pnode_run' && !this.pnode_run_defined) {
2183
+ // 保证并发调用 rpc 时只定义一次 pnode_run
2129
2184
  const ptail = this.ppnoderun;
2130
2185
  let resolve;
2131
2186
  this.ppnoderun = new Promise((_resolve, _reject) => {
@@ -2275,8 +2330,7 @@ export class DDB {
2275
2330
  */
2276
2331
  async call(func, args = [], { urgent, node, nodes, func_type, add_node_alias, listener, parse_object, } = {}) {
2277
2332
  if (node) {
2278
- if (typeof func_type === 'undefined')
2279
- throw new Error('指定 node 时必须设置 func_type');
2333
+ assert(func_type in DdbFunctionType, 'func_type must be set when specifying node');
2280
2334
  args = [
2281
2335
  node,
2282
2336
  new DdbFunction(func, func_type),
@@ -2313,10 +2367,21 @@ export class DDB {
2313
2367
  vars,
2314
2368
  /** Uploaded variables' value */
2315
2369
  args, { listener, parse_object, } = {}) {
2316
- if (!args.length || args.length !== vars.length)
2317
- throw new Error('variable command parameter is empty or parameter name is empty, or the number does not match');
2370
+ assert(args.length && args.length === vars.length, 'variable command parameter cannot be empty and parameter name cannot be empty, and the number should match');
2318
2371
  return this.rpc('variable', { vars, args, listener, parse_object });
2319
2372
  }
2373
+ /** Cancel all console jobs corresponding to the current session id */
2374
+ async cancel() {
2375
+ let ddb = new DDB(this.url, this);
2376
+ try {
2377
+ await ddb.eval(`jobs = exec rootJobId from getConsoleJobs() where sessionId = ${this.sid}\n` +
2378
+ 'if (size(jobs))\n' +
2379
+ ' cancelConsoleJob(jobs)\n', { urgent: true });
2380
+ }
2381
+ finally {
2382
+ ddb.disconnect();
2383
+ }
2384
+ }
2320
2385
  /** 解析服务端响应报文,返回去掉 header 的 data buf */
2321
2386
  parse_message(buf, parse_object = this.parse_object) {
2322
2387
  // MSG\n
@@ -2331,8 +2396,8 @@ export class DDB {
2331
2396
  // 'OK\n'
2332
2397
  // '\x04\x00\x02\x00\x00\x00'
2333
2398
  /** index of line feed 0 */
2334
- const i_lf_0 = buf.indexOf(0x0a); // '\n'
2335
- const parts = this.dec.decode(buf.subarray(0, i_lf_0)).split(' ');
2399
+ const ilf0 = buf.indexOf(0x0a); // '\n'
2400
+ const parts = this.dec.decode(buf.subarray(0, ilf0)).split(' ');
2336
2401
  /** session id */
2337
2402
  const sid = parts[0];
2338
2403
  if (sid !== this.sid) {
@@ -2340,34 +2405,141 @@ export class DDB {
2340
2405
  this.sid = sid;
2341
2406
  }
2342
2407
  /** 返回对象的数量 */
2343
- const n_obj = Number(parts[1]);
2408
+ const nobj = Number(parts[1]);
2344
2409
  /** 大小端: 协议中大端为 0, 小端为 1 */
2345
2410
  this.le = Number(parts[2]) !== 0;
2346
- const i_ls_1 = i_lf_0 + 1;
2347
- const i_lf_1 = buf.indexOf(0x0a, i_ls_1); // '\n'
2411
+ const ils1 = ilf0 + 1;
2412
+ const ilf1 = buf.indexOf(0x0a, ils1); // '\n'
2348
2413
  /** 'OK' 表示成功,其它文本表示失败 */
2349
- const message = this.dec.decode(buf.subarray(i_ls_1, i_lf_1));
2414
+ const message = this.dec.decode(buf.subarray(ils1, ilf1));
2350
2415
  if (message !== 'OK')
2351
2416
  return {
2352
2417
  type: 'error',
2353
2418
  data: new Error(message)
2354
2419
  };
2355
- const buf_obj = buf.subarray(i_lf_1 + 1);
2420
+ const bufobj = buf.subarray(ilf1 + 1);
2356
2421
  if (this.print_object_buffer)
2357
- console.log(typed_array_to_buffer(buf_obj));
2422
+ console.log(typed_array_to_buffer(bufobj));
2358
2423
  return {
2359
2424
  type: 'object',
2360
2425
  data: parse_object ?
2361
- DdbObj.parse(buf_obj, this.le)
2426
+ DdbObj.parse(bufobj, this.le)
2362
2427
  :
2363
2428
  new DdbObj({
2364
2429
  form: DdbForm.scalar,
2365
2430
  type: DdbType.void,
2366
2431
  length: 0,
2367
2432
  le: this.le,
2368
- buffer: buf_obj,
2433
+ buffer: bufobj,
2369
2434
  })
2370
2435
  };
2371
2436
  }
2437
+ /** Internal stream subscription method */
2438
+ async subscribe() {
2439
+ const args = DdbObj.to_ddbobjs([
2440
+ new DdbVectorString(this.autologin ?
2441
+ [this.username, this.password]
2442
+ :
2443
+ // 不自动登录也要传空的 string vector, 否则 server 无法解析
2444
+ []),
2445
+ // todo: 还有用吗?需要获取客户端 IP 吗
2446
+ 'localhost',
2447
+ // todo: 端口还有用吗?
2448
+ new DdbInt(0),
2449
+ this.streaming.table,
2450
+ (this.streaming.action ||= `api_js_${new Date().getTime()}`),
2451
+ // new DdbInt(-1), // offset
2452
+ // filter
2453
+ // allow exists
2454
+ ]);
2455
+ return new Promise((resolve, reject) => {
2456
+ let first = true;
2457
+ let second = true;
2458
+ // 先准备好收到 websocket message 的 callback
2459
+ this.on_message = ({ data: buffer }) => {
2460
+ if (first) {
2461
+ first = false;
2462
+ try {
2463
+ const dv = new DataView(buffer);
2464
+ const status = dv.getUint8(1);
2465
+ this.le = Boolean(dv.getUint8(2));
2466
+ const nobjs = dv.getUint32(3, this.le);
2467
+ // 解析返回数据,从 server 实现看其实只有一个 DdbObj
2468
+ let offset = 7;
2469
+ let objs = new Array(nobjs);
2470
+ const buf = new Uint8Array(buffer);
2471
+ for (let i = 0; i < nobjs; i++) {
2472
+ const obj = DdbObj.parse(buf.subarray(offset), this.le);
2473
+ offset += obj.length;
2474
+ objs[i] = obj;
2475
+ console.log(`订阅流表的响应数据: objs[${i}]:`, obj);
2476
+ }
2477
+ this.streaming.colnames = objs[0].value;
2478
+ if (status === StreamingStatusCode.ok)
2479
+ console.log('Subscribed to streaming table:', { status: StreamingStatusCode[status], result: objs[0] });
2480
+ else
2481
+ reject(new Error(`Failed to subscribe to streaming table: { status: ${StreamingStatusCode[status]}, error: ${objs[0]?.value} }`));
2482
+ }
2483
+ catch (error) {
2484
+ reject(error);
2485
+ }
2486
+ }
2487
+ else {
2488
+ try {
2489
+ const dv = new DataView(buffer);
2490
+ const buf = new Uint8Array(buffer);
2491
+ let { streaming } = this;
2492
+ streaming.time = dv.getBigInt64(1, this.le);
2493
+ streaming.id = dv.getBigInt64(9, this.le);
2494
+ const i_topic_end = buf.indexOf(0, 17);
2495
+ streaming.topic = this.dec.decode(buf.subarray(17, i_topic_end));
2496
+ if (second) {
2497
+ second = false;
2498
+ // 是 table
2499
+ streaming.schema = DdbObj.parse(buf.subarray(i_topic_end + 1), this.le);
2500
+ console.log('Received streaming table schema:', streaming.schema);
2501
+ streaming.window = {
2502
+ offset: 0,
2503
+ rows: 0,
2504
+ segments: [],
2505
+ };
2506
+ assert(streaming.schema.rows === 0, 'schema.rows === 0');
2507
+ resolve(streaming.schema);
2508
+ }
2509
+ else {
2510
+ // 是 column 片段组成的 any vector
2511
+ const data = streaming.data = DdbObj.parse(buf.subarray(i_topic_end + 1), this.le);
2512
+ let { window: win } = streaming;
2513
+ win.rows += data.value[0].rows;
2514
+ win.segments.push(data);
2515
+ if (win.rows >= winsize * 2 && win.segments.length >= 2) {
2516
+ let winsize_ = 0;
2517
+ let i = win.segments.length - 1;
2518
+ // 往前移动至首个累计 winsize_ 超过 winsize 的位置
2519
+ for (; winsize_ < winsize; i--)
2520
+ winsize_ += win.segments[i].rows;
2521
+ win.segments = win.segments.slice(i);
2522
+ win.offset += win.rows - winsize_;
2523
+ win.rows = winsize_;
2524
+ console.log('exceeds winsize, shrink window.segments', win);
2525
+ }
2526
+ streaming.handler(streaming);
2527
+ }
2528
+ }
2529
+ catch (error) {
2530
+ reject(error);
2531
+ }
2532
+ }
2533
+ };
2534
+ // 发送订阅 websocket message
2535
+ this.websocket.send(concat([
2536
+ Uint8Array.of(1, // from = SubscriberFromType::FROM_API
2537
+ 1, // type = SubscriberRPCType::RPC_START
2538
+ Number(DDB.le_client)),
2539
+ Uint32Array.of(args.length),
2540
+ ...args.map(obj => obj.pack())
2541
+ ]));
2542
+ });
2543
+ }
2372
2544
  }
2373
2545
  //# sourceMappingURL=index.js.map