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/browser.js CHANGED
@@ -4,8 +4,8 @@ dayjs.extend(DayjsCustomParseFormat);
4
4
  import ipaddrjs from 'ipaddr.js';
5
5
  const { fromByteArray: buf2ipaddr } = ipaddrjs;
6
6
  import 'xshell/prototype.browser.js';
7
- import { blue } from 'xshell/chalk.browser.js';
8
- import { concat } from 'xshell/utils.browser.js';
7
+ import { blue, cyan, green, grey, magenta } from 'xshell/chalk.browser.js';
8
+ import { concat, assert } from 'xshell/utils.browser.js';
9
9
  import { connect_websocket } from 'xshell/net.browser.js';
10
10
  export var DdbForm;
11
11
  (function (DdbForm) {
@@ -63,7 +63,11 @@ export var DdbType;
63
63
  DdbType[DdbType["complex"] = 34] = "complex";
64
64
  DdbType[DdbType["point"] = 35] = "point";
65
65
  DdbType[DdbType["duration"] = 36] = "duration";
66
- DdbType[DdbType["object"] = 37] = "object";
66
+ DdbType[DdbType["decimal32"] = 37] = "decimal32";
67
+ DdbType[DdbType["decimal64"] = 38] = "decimal64";
68
+ DdbType[DdbType["decimal128"] = 39] = "decimal128";
69
+ DdbType[DdbType["object"] = 40] = "object";
70
+ DdbType[DdbType["pynone"] = 41] = "pynone";
67
71
  DdbType[DdbType["symbol_extended"] = 145] = "symbol_extended";
68
72
  })(DdbType = DdbType || (DdbType = {}));
69
73
  export var DdbFunctionType;
@@ -115,6 +119,14 @@ export const nulls = {
115
119
  bytes16: Uint8Array.from(new Array(16).fill(0))
116
120
  };
117
121
  export const timezone_offset = 1000 * 60 * new Date().getTimezoneOffset();
122
+ export var StreamingStatusCode;
123
+ (function (StreamingStatusCode) {
124
+ StreamingStatusCode[StreamingStatusCode["ok"] = 0] = "ok";
125
+ StreamingStatusCode[StreamingStatusCode["connection_existed"] = 1] = "connection_existed";
126
+ StreamingStatusCode[StreamingStatusCode["not_leader"] = 2] = "not_leader";
127
+ StreamingStatusCode[StreamingStatusCode["other"] = 3] = "other";
128
+ StreamingStatusCode[StreamingStatusCode["error"] = 4] = "error";
129
+ })(StreamingStatusCode = StreamingStatusCode || (StreamingStatusCode = {}));
118
130
  /** 可以表示所有 DolphinDB 数据库中的数据类型 */
119
131
  export class DdbObj {
120
132
  static dec = new TextDecoder('utf-8');
@@ -332,8 +344,7 @@ export class DdbObj {
332
344
  col_labels.length += 2;
333
345
  offset += col_labels.length;
334
346
  }
335
- if (buf_data[offset] !== type)
336
- throw new Error('matrix.datatype !== matrix.type');
347
+ assert(buf_data[offset] === type, 'matrix.datatype === matrix.type');
337
348
  const rows = dv.getUint32(offset + 2, le);
338
349
  const cols = dv.getUint32(offset + 6, le);
339
350
  const [len_items, data] = this.parse_vector_items(buf_data.subarray(offset + 10), le, type, rows * cols // 假设小于 2**32
@@ -444,38 +455,30 @@ export class DdbObj {
444
455
  case DdbType.uuid:
445
456
  case DdbType.ipaddr:
446
457
  case DdbType.int128:
447
- return [
448
- 16,
449
- buf.slice(0, 16)
450
- ];
458
+ return [16, buf.slice(0, 16)];
451
459
  case DdbType.blob: {
452
460
  const dv = new DataView(buf.buffer, buf.byteOffset);
453
461
  const length = dv.getUint32(0, le);
454
- return [
455
- 4 + length,
456
- buf.slice(4, 4 + length)
457
- ];
462
+ return [4 + length, buf.slice(4, 4 + length)];
458
463
  }
459
464
  case DdbType.complex:
460
465
  case DdbType.point: {
461
466
  const dv = new DataView(buf.buffer, buf.byteOffset);
462
- return [
463
- 16,
464
- [
465
- dv.getFloat64(0, le),
466
- dv.getFloat64(8, le)
467
- ]
468
- ];
467
+ return [16, [dv.getFloat64(0, le), dv.getFloat64(8, le)]];
469
468
  }
470
469
  case DdbType.duration: {
471
470
  const dv = new DataView(buf.buffer, buf.byteOffset);
472
- return [
473
- 8,
474
- {
475
- unit: dv.getUint32(4, le),
476
- data: dv.getInt32(0, le)
477
- }
478
- ];
471
+ return [8, { unit: dv.getUint32(4, le), data: dv.getInt32(0, le) }];
472
+ }
473
+ case DdbType.decimal32: {
474
+ const dv = new DataView(buf.buffer, buf.byteOffset);
475
+ const data = dv.getInt32(4, le);
476
+ return [8, { scale: dv.getInt32(0, le), data: data === nulls.int32 ? null : data }];
477
+ }
478
+ case DdbType.decimal64: {
479
+ const dv = new DataView(buf.buffer, buf.byteOffset);
480
+ const data = dv.getBigInt64(4, le);
481
+ return [12, { scale: dv.getInt32(0, le), data: data === nulls.int64 ? null : data }];
479
482
  }
480
483
  default:
481
484
  throw new Error(`${DdbType[type] || type} 暂时不支持解析`);
@@ -636,7 +639,7 @@ export class DdbObj {
636
639
  // 02 00 00 00 symbol base size = 2
637
640
  // 00 61 61 00 以 \0 分割的字符串
638
641
  // 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00>
639
- const dv = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
642
+ const dv = new DataView(buf.buffer, buf.byteOffset);
640
643
  const base_id = dv.getUint32(0, le);
641
644
  const base_size = dv.getUint32(4, le);
642
645
  let base_length = 0;
@@ -671,7 +674,7 @@ export class DdbObj {
671
674
  // 04 00 00 00 61 62 63 64
672
675
  // 04 00 00 00 61 62 63 64>
673
676
  let value = new Array(length);
674
- const dv = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
677
+ const dv = new DataView(buf.buffer, buf.byteOffset);
675
678
  let i_head = 0;
676
679
  for (let i = 0; i < length; i++) {
677
680
  const sublen = dv.getUint32(i_head, le);
@@ -711,6 +714,31 @@ export class DdbObj {
711
714
  values
712
715
  ];
713
716
  }
717
+ // 25 01 type = decimal32, form = vector
718
+ // 02 00 00 00 01 00 00 00
719
+ // 00 00 00 00 scale = 0
720
+ // 01 00 00 00 data[0] = 1
721
+ // 3a 01 00 00 data[1] = 0x013a = 314
722
+ case DdbType.decimal32: {
723
+ const dv = new DataView(buf.buffer, buf.byteOffset);
724
+ return [
725
+ 4 + 4 * length,
726
+ {
727
+ scale: dv.getInt32(0, le),
728
+ data: new Int32Array(buf.buffer.slice(buf.byteOffset + 4, buf.byteOffset + 4 + 4 * length))
729
+ }
730
+ ];
731
+ }
732
+ case DdbType.decimal64: {
733
+ const dv = new DataView(buf.buffer, buf.byteOffset);
734
+ return [
735
+ 4 + 8 * length,
736
+ {
737
+ scale: dv.getInt32(0, le),
738
+ data: new BigInt64Array(buf.buffer.slice(buf.byteOffset + 4, buf.byteOffset + 4 + 8 * length))
739
+ }
740
+ ];
741
+ }
714
742
  // case DdbType.duration: -> 实际会返回一个 any vector
715
743
  // [2y, 1M, 3d, 7H, 11m, 12s, 15ms, 16us, 17ns]
716
744
  // <Buffer 19 01 type = any, form = vector
@@ -814,10 +842,7 @@ export class DdbObj {
814
842
  case DdbType.int128:
815
843
  return [this.value];
816
844
  case DdbType.blob:
817
- return [
818
- Uint32Array.of(this.value.byteLength),
819
- this.value
820
- ];
845
+ return [Uint32Array.of(this.value.byteLength), this.value];
821
846
  case DdbType.complex:
822
847
  case DdbType.point:
823
848
  return [Float64Array.from(this.value)];
@@ -825,6 +850,14 @@ export class DdbObj {
825
850
  const { unit, data } = this.value;
826
851
  return [Int32Array.of(data, unit)];
827
852
  }
853
+ case DdbType.decimal32: {
854
+ const { scale, data } = this.value;
855
+ return [Int32Array.of(scale, data === null ? nulls.int32 : data)];
856
+ }
857
+ case DdbType.decimal64: {
858
+ const { scale, data } = this.value;
859
+ return [Int32Array.of(scale), BigInt64Array.of(data === null ? nulls.int64 : data)];
860
+ }
828
861
  default:
829
862
  throw new Error(`${DdbType[type] || type} 暂时不支持序列化`);
830
863
  }
@@ -846,12 +879,7 @@ export class DdbObj {
846
879
  ...DdbObj.pack_vector_body(value, type, this.rows)
847
880
  ];
848
881
  case DdbForm.set:
849
- return [
850
- new DdbObj({
851
- ...this,
852
- form: DdbForm.vector,
853
- }).pack()
854
- ];
882
+ return [new DdbObj({ ...this, form: DdbForm.vector }).pack()];
855
883
  case DdbForm.table:
856
884
  return [
857
885
  Uint32Array.of(this.rows, this.cols),
@@ -863,43 +891,27 @@ export class DdbObj {
863
891
  ...this.value.map(col => col.pack())
864
892
  ];
865
893
  case DdbForm.dict:
866
- return [
867
- value[0].pack(),
868
- value[1].pack(),
869
- ];
894
+ return [value[0].pack(), value[1].pack()];
870
895
  case DdbForm.chart: {
871
- const { type, stacking, bin_start, bin_end, bin_count, titles: { chart, x_axis, y_axis, }, extras, data } = this.value;
896
+ const { type, stacking, bin_start, bin_end, bin_count, titles: { chart, x_axis, y_axis }, extras, data } = this.value;
872
897
  const { value: [keys, values] } = new DdbDict({
873
898
  chartType: new DdbInt(type),
874
899
  stacking,
875
- ...bin_start ? {
876
- binStart: bin_start,
877
- binEnd: bin_end
878
- } : {},
879
- ...bin_count ? {
880
- binCount: bin_count
881
- } : {},
900
+ ...bin_start ? { binStart: bin_start, binEnd: bin_end } : {},
901
+ ...bin_count ? { binCount: bin_count } : {},
882
902
  title: new DdbVectorString([chart, x_axis, y_axis]),
883
903
  ...extras ? (() => {
884
904
  const { multi_y_axes, ...extras_other } = extras;
885
- return {
886
- extras: new DdbDict({
887
- multiYAxes: multi_y_axes,
888
- ...extras_other
889
- })
890
- };
905
+ return { extras: new DdbDict({ multiYAxes: multi_y_axes, ...extras_other }) };
891
906
  })() : {},
892
907
  data,
893
908
  });
894
- return [
895
- keys.pack(),
896
- values.pack(),
897
- ];
909
+ return [keys.pack(), values.pack()];
898
910
  }
899
911
  case DdbForm.matrix: {
900
912
  const { rows, cols, data } = value;
901
913
  return [
902
- Uint8Array.of((rows && 0x01) | (cols && 0x02)),
914
+ Uint8Array.of((rows ? 0x01 : 0x00) | (cols ? 0x02 : 0x00)),
903
915
  ...rows ? [rows.pack()] : [],
904
916
  ...cols ? [cols.pack()] : [],
905
917
  Uint8Array.of(this.type, this.form),
@@ -908,7 +920,7 @@ export class DdbObj {
908
920
  ];
909
921
  }
910
922
  default:
911
- throw new Error(`${DdbForm[form]} Serialization is not currently supported`);
923
+ throw new Error(`${DdbForm[form]} serialization is not currently supported`);
912
924
  }
913
925
  })();
914
926
  if (!body)
@@ -997,16 +1009,21 @@ export class DdbObj {
997
1009
  data
998
1010
  ];
999
1011
  }
1012
+ case DdbType.decimal32:
1013
+ case DdbType.decimal64: {
1014
+ const { scale, data } = value;
1015
+ return [Int32Array.of(scale), data];
1016
+ }
1000
1017
  default:
1001
1018
  throw new Error(`vector ${DdbType[type]} 暂不支持序列化`);
1002
1019
  }
1003
1020
  }
1004
- toString(colors = false) {
1021
+ toString(options = { nullstr: true, quote: true }) {
1005
1022
  const type = this.inspect_type();
1006
1023
  const data = (() => {
1007
1024
  switch (this.form) {
1008
1025
  case DdbForm.scalar:
1009
- return format(this.type, this.value, this.le);
1026
+ return format(this.type, this.value, this.le, options);
1010
1027
  case DdbForm.vector:
1011
1028
  case DdbForm.pair:
1012
1029
  case DdbForm.set: {
@@ -1037,7 +1054,7 @@ export class DdbObj {
1037
1054
  for (const length of lengths) {
1038
1055
  let _items = new Array(Math.min(limit, length));
1039
1056
  for (let i = 0; i < _items.length; i++)
1040
- _items[i] = format(_type, data[acc_len + i], this.le);
1057
+ _items[i] = format(_type, data[acc_len + i], this.le, options);
1041
1058
  items[i_items++] = format_array(_items, length > limit);
1042
1059
  acc_len += length;
1043
1060
  }
@@ -1063,7 +1080,7 @@ export class DdbObj {
1063
1080
  const len_data = value.length / 16;
1064
1081
  let items = new Array(Math.min(limit, len_data));
1065
1082
  for (let i = 0; i < items.length; i++)
1066
- items[i] = format(this.type, value.subarray(16 * i, 16 * (i + 1)), this.le);
1083
+ items[i] = format(this.type, value.subarray(16 * i, 16 * (i + 1)), this.le, options);
1067
1084
  return format_array(items, len_data > limit);
1068
1085
  }
1069
1086
  case DdbType.complex:
@@ -1073,24 +1090,33 @@ export class DdbObj {
1073
1090
  const len_data = value.length / 2;
1074
1091
  let items = new Array(Math.min(limit, len_data));
1075
1092
  for (let i = 0; i < items.length; i++)
1076
- items[i] = format(this.type, value.subarray(2 * i, 2 * (i + 1)), this.le);
1093
+ items[i] = format(this.type, value.subarray(2 * i, 2 * (i + 1)), this.le, options);
1077
1094
  return format_array(items, len_data > limit);
1078
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
+ }
1079
1105
  default: {
1080
1106
  const limit = 50;
1081
1107
  let items = new Array(Math.min(limit, this.value.length));
1082
1108
  for (let i = 0; i < items.length; i++)
1083
- items[i] = format(this.type, this.value[i], this.le);
1109
+ items[i] = format(this.type, this.value[i], this.le, options);
1084
1110
  return format_array(items, this.value.length > limit);
1085
1111
  }
1086
1112
  }
1087
1113
  }
1088
1114
  case DdbForm.chart:
1089
- return JSON.stringify(this.value, (key, value) => key === 'data' ? value.toString() : value, 4);
1115
+ return JSON.stringify(this.value, (key, value) => key === 'data' ? value.toString(options) : value, 4);
1090
1116
  }
1091
1117
  return String(this.value);
1092
1118
  })();
1093
- return `${colors ? blue(type) : type}(${this.name ? `'${this.name}', ` : ''}${data})\n`;
1119
+ return `${options?.colors ? blue(type) : type}(${this.name ? `'${this.name}', ` : ''}${data})\n`;
1094
1120
  }
1095
1121
  inspect_type() {
1096
1122
  const tname = DdbType[this.type];
@@ -1138,28 +1164,15 @@ export class DdbObj {
1138
1164
  return values.map(value => this.to_ddbobj(value));
1139
1165
  }
1140
1166
  to_cols() {
1141
- return this.value.map(col => {
1142
- let col_ = {
1143
- title: col.name,
1144
- dataIndex: col.name,
1145
- };
1146
- switch (col.type) {
1147
- case DdbType.timestamp:
1148
- col_.render = value => timestamp2str(value);
1149
- break;
1150
- case DdbType.date:
1151
- col_.render = value => date2str(value);
1152
- break;
1153
- case DdbType.ipaddr:
1154
- col_.render = value => ipaddr2str(value, this.le);
1155
- break;
1156
- }
1157
- return col_;
1158
- });
1167
+ assert(this.form === DdbForm.table, 'form must be DdbForm.table, otherwise it cannot to_cols');
1168
+ return this.value.map(col => ({
1169
+ title: col.name,
1170
+ dataIndex: col.name,
1171
+ render: (value) => format(col.type, value, col.le, { nullstr: false, quote: false })
1172
+ }));
1159
1173
  }
1160
1174
  to_rows() {
1161
- if (this.form !== DdbForm.table)
1162
- throw new Error('this.form is not DdbForm.table, cannot call to_rows');
1175
+ assert(this.form === DdbForm.table, 'form must be DdbForm.table, otherwise it cannot to_rows');
1163
1176
  let rows = new Array(this.rows);
1164
1177
  for (let i = 0; i < this.rows; i++) {
1165
1178
  let row = {};
@@ -1191,13 +1204,10 @@ export class DdbObj {
1191
1204
  return rows;
1192
1205
  }
1193
1206
  to_dict({ strip, deep, } = {}) {
1194
- if (this.form !== DdbForm.dict)
1195
- throw new Error('this.form is not DdbForm.dict and cannot be converted to js object');
1207
+ assert(this.form === DdbForm.dict, 'this.form must be DdbForm.dict, otherwise to_dict cannot be called to convert to js object');
1196
1208
  const [{ value: keys, type: key_type }, { value: values, type: value_type }] = this.value;
1197
- if (key_type !== DdbType.string || value_type !== DdbType.any)
1198
- throw new Error('Not dict<string, any>, automatic conversion to js object is not supported for the time being');
1199
- if (deep && !strip)
1200
- throw new Error('strip = true must be set when deep = true');
1209
+ assert(key_type === DdbType.string && value_type === DdbType.any, 'Currently only supports automatic conversion of dict<string, any> to js object');
1210
+ assert(!(deep && !strip), 'strip = true must be set when deep = true');
1201
1211
  let obj = {};
1202
1212
  for (let i = 0; i < this.rows; i++) {
1203
1213
  let value = values[i];
@@ -1209,144 +1219,222 @@ export class DdbObj {
1209
1219
  return obj;
1210
1220
  }
1211
1221
  }
1222
+ /** Integer must use this number formatter, InspectOptions.decimals also use this if not passed */
1223
+ let default_formatter = Intl.NumberFormat('en-US', { maximumFractionDigits: 20 });
1224
+ let _decimals = 20;
1225
+ /** Cache, in order to optimize performance, usually options.decimals are unchanged */
1226
+ let _formatter = Intl.NumberFormat('en-US', { maximumFractionDigits: 20 });
1212
1227
  /** Formats a single element (value) as a string according to DdbType, null returns a 'null' string */
1213
1228
  export function format(type, value, le, options = {}) {
1229
+ const { decimals, nullstr = false, colors = false, quote = false } = options;
1230
+ const formatter = (() => {
1231
+ if (decimals === undefined || decimals === null)
1232
+ return default_formatter;
1233
+ if (decimals !== _decimals) {
1234
+ _decimals = decimals;
1235
+ _formatter = Intl.NumberFormat('en-US', { maximumFractionDigits: decimals, minimumFractionDigits: decimals });
1236
+ }
1237
+ return _formatter;
1238
+ })();
1239
+ function get_nullstr() {
1240
+ return nullstr ?
1241
+ colors ? grey('null') : 'null'
1242
+ :
1243
+ '';
1244
+ }
1214
1245
  switch (type) {
1215
1246
  case DdbType.bool:
1216
- return String((value === null || value === nulls.int8) ?
1217
- null
1218
- :
1219
- Boolean(value));
1247
+ if (value === null || value === nulls.int8)
1248
+ return get_nullstr();
1249
+ else {
1250
+ const str = String(Boolean(value));
1251
+ return colors ? blue(str) : str;
1252
+ }
1220
1253
  case DdbType.char:
1221
- return String((value === null || value === nulls.int8) ?
1222
- null
1223
- :
1224
- // ascii printable
1225
- // http://facweb.cs.depaul.edu/sjost/it212/documents/ascii-pr.htm
1226
- (32 <= value && value <= 126) ?
1227
- String.fromCharCode(value).quote()
1228
- :
1229
- value);
1254
+ if (value === null || value === nulls.int8)
1255
+ return get_nullstr();
1256
+ else {
1257
+ let str =
1258
+ // ascii printable
1259
+ // http://facweb.cs.depaul.edu/sjost/it212/documents/ascii-pr.htm
1260
+ (32 <= value && value <= 126) ?
1261
+ String.fromCharCode(value)
1262
+ :
1263
+ '\\' + String(value);
1264
+ if (quote)
1265
+ str = str.quote();
1266
+ return colors ? green(str) : str;
1267
+ }
1230
1268
  case DdbType.short:
1231
- return String((value === null || value === nulls.int16) ?
1232
- null
1233
- :
1234
- value);
1269
+ if (value === null || value === nulls.int16)
1270
+ return get_nullstr();
1271
+ else {
1272
+ const str = default_formatter.format(value);
1273
+ return colors ? green(str) : str;
1274
+ }
1235
1275
  case DdbType.int:
1236
- return String((value === null || value === nulls.int32) ?
1237
- null
1238
- :
1239
- value);
1276
+ if (value === null || value === nulls.int32)
1277
+ return get_nullstr();
1278
+ else {
1279
+ const str = default_formatter.format(value);
1280
+ return colors ? green(str) : str;
1281
+ }
1240
1282
  case DdbType.long:
1241
- return (value === null || value === nulls.int64) ?
1242
- 'null'
1243
- :
1244
- String(value);
1283
+ if (value === null || value === nulls.int64)
1284
+ return get_nullstr();
1285
+ else {
1286
+ const str = default_formatter.format(value);
1287
+ return colors ? green(str) : str;
1288
+ }
1245
1289
  case DdbType.date:
1246
- return (value === null || value === nulls.int32) ?
1247
- 'null'
1248
- :
1249
- date2str(value);
1290
+ if (value === null || value === nulls.int16)
1291
+ return get_nullstr();
1292
+ else {
1293
+ const str = date2str(value);
1294
+ return colors ? magenta(str) : str;
1295
+ }
1250
1296
  case DdbType.month:
1251
- return (value === null || value === nulls.int32) ?
1252
- 'null'
1253
- :
1254
- month2str(value);
1297
+ if (value === null || value === nulls.int32)
1298
+ return get_nullstr();
1299
+ else {
1300
+ const str = month2str(value);
1301
+ return colors ? magenta(str) : str;
1302
+ }
1255
1303
  case DdbType.time:
1256
- return (value === null || value === nulls.int32) ?
1257
- 'null'
1258
- :
1259
- time2str(value);
1304
+ if (value === null || value === nulls.int32)
1305
+ return get_nullstr();
1306
+ else {
1307
+ const str = time2str(value);
1308
+ return colors ? magenta(str) : str;
1309
+ }
1260
1310
  case DdbType.minute:
1261
- return (value === null || value === nulls.int32) ?
1262
- 'null'
1263
- :
1264
- minute2str(value);
1311
+ if (value === null || value === nulls.int16)
1312
+ return get_nullstr();
1313
+ else {
1314
+ const str = minute2str(value);
1315
+ return colors ? magenta(str) : str;
1316
+ }
1265
1317
  case DdbType.second:
1266
- return (value === null || value === nulls.int32) ?
1267
- 'null'
1268
- :
1269
- second2str(value);
1318
+ if (value === null || value === nulls.int32)
1319
+ return get_nullstr();
1320
+ else {
1321
+ const str = second2str(value);
1322
+ return colors ? magenta(str) : str;
1323
+ }
1270
1324
  case DdbType.datetime:
1271
- return (value === null || value === nulls.int32) ?
1272
- 'null'
1273
- :
1274
- datetime2str(value);
1325
+ if (value === null || value === nulls.int32)
1326
+ return get_nullstr();
1327
+ else {
1328
+ const str = datetime2str(value);
1329
+ return colors ? magenta(str) : str;
1330
+ }
1275
1331
  case DdbType.timestamp:
1276
- return (value === null || value === nulls.int64) ?
1277
- 'null'
1278
- :
1279
- timestamp2str(value);
1332
+ if (value === null || value === nulls.int64)
1333
+ return get_nullstr();
1334
+ else {
1335
+ const str = timestamp2str(value);
1336
+ return colors ? magenta(str) : str;
1337
+ }
1280
1338
  case DdbType.nanotime:
1281
- return (value === null || value === nulls.int64) ?
1282
- 'null'
1283
- :
1284
- nanotime2str(value);
1339
+ if (value === null || value === nulls.int64)
1340
+ return get_nullstr();
1341
+ else {
1342
+ const str = nanotime2str(value);
1343
+ return colors ? magenta(str) : str;
1344
+ }
1285
1345
  case DdbType.nanotimestamp:
1286
- return (value === null || value === nulls.int64) ?
1287
- 'null'
1288
- :
1289
- nanotimestamp2str(value);
1346
+ if (value === null || value === nulls.int64)
1347
+ return get_nullstr();
1348
+ else {
1349
+ const str = nanotimestamp2str(value);
1350
+ return colors ? magenta(str) : str;
1351
+ }
1290
1352
  case DdbType.float:
1291
- return (value === null || value === nulls.float32) ?
1292
- 'null'
1293
- :
1294
- String(value);
1353
+ if (value === null || value === nulls.float32)
1354
+ return get_nullstr();
1355
+ else {
1356
+ const str = formatter.format(value);
1357
+ return colors ? green(str) : str;
1358
+ }
1295
1359
  case DdbType.double:
1296
- return (value === null || value === nulls.double) ?
1297
- 'null'
1298
- :
1299
- String(value);
1360
+ if (value === null || value === nulls.double)
1361
+ return get_nullstr();
1362
+ else {
1363
+ const str = formatter.format(value);
1364
+ return colors ? green(str) : str;
1365
+ }
1300
1366
  case DdbType.symbol:
1301
- case DdbType.string:
1302
- return value.quote('single');
1303
- case DdbType.uuid:
1304
- return uuid2str(value, le);
1305
- case DdbType.functiondef:
1306
- return value.name.quote('single');
1367
+ case DdbType.string: {
1368
+ let str = value;
1369
+ if (quote)
1370
+ str = str.quote('single');
1371
+ return colors ? cyan(str) : str;
1372
+ }
1373
+ case DdbType.uuid: {
1374
+ const str = uuid2str(value, le);
1375
+ return colors ? cyan(str) : str;
1376
+ }
1377
+ case DdbType.functiondef: {
1378
+ const str = value.name.quote('single');
1379
+ return colors ? cyan(str) : str;
1380
+ }
1307
1381
  case DdbType.handle:
1308
1382
  case DdbType.code:
1309
- case DdbType.resource:
1310
- return value.quote('single');
1383
+ case DdbType.resource: {
1384
+ const str = value.quote('single');
1385
+ return colors ? cyan(str) : str;
1386
+ }
1311
1387
  case DdbType.datehour:
1312
- return (value === null || value === nulls.int32) ?
1313
- 'null'
1314
- :
1315
- datehour2str(value);
1316
- case DdbType.ipaddr:
1317
- return ipaddr2str(value, le);
1318
- case DdbType.int128:
1319
- return int1282str(value, le);
1320
- case DdbType.blob:
1321
- return (value.length > 100 ?
1388
+ if (value === null || value === nulls.int32)
1389
+ return get_nullstr();
1390
+ else {
1391
+ const str = datehour2str(value);
1392
+ return colors ? magenta(str) : str;
1393
+ }
1394
+ case DdbType.ipaddr: {
1395
+ const str = ipaddr2str(value, le);
1396
+ return colors ? cyan(str) : str;
1397
+ }
1398
+ case DdbType.int128: {
1399
+ const str = int1282str(value, le);
1400
+ return colors ? cyan(str) : str;
1401
+ }
1402
+ case DdbType.blob: {
1403
+ const str = value.length > 100 ?
1322
1404
  DdbObj.dec.decode(value.subarray(0, 98)) + '…'
1323
1405
  :
1324
- DdbObj.dec.decode(value)).quote('single');
1406
+ DdbObj.dec.decode(value);
1407
+ return colors ? cyan(str) : str;
1408
+ }
1325
1409
  case DdbType.point: {
1326
- let [x, y] = value;
1327
- if (x === nulls.double)
1328
- x = null;
1329
- if (y === nulls.double)
1330
- y = null;
1331
- return `(${String(x)}, ${String(y)})`;
1410
+ const [x, y] = value;
1411
+ return `(${format(DdbType.double, x, le, options)}, ${format(DdbType.double, y, le, options)})`;
1332
1412
  }
1333
1413
  case DdbType.complex: {
1334
- let [x, y] = value;
1335
- if (x === nulls.double)
1336
- x = null;
1337
- if (y === nulls.double)
1338
- y = null;
1339
- return `${String(x)}+${String(y)}i`;
1414
+ const [x, y] = value;
1415
+ return `${format(DdbType.double, x, le, options)}+${format(DdbType.double, y, le, options)}i`;
1340
1416
  }
1341
1417
  case DdbType.duration: {
1342
1418
  const { data, unit } = value;
1343
- return `${data}${DdbDurationUnit[unit]}`;
1419
+ const str = `${data}${DdbDurationUnit[unit]}`;
1420
+ return colors ? magenta(str) : str;
1421
+ }
1422
+ case DdbType.decimal32:
1423
+ case DdbType.decimal64: {
1424
+ const { scale, data } = value;
1425
+ if (data === null ||
1426
+ (data === nulls.int64 && type === DdbType.decimal64) ||
1427
+ (data === nulls.int32 && type === DdbType.decimal32))
1428
+ return get_nullstr();
1429
+ const s = String(data < 0 ? -data : data).padStart(scale, '0');
1430
+ const str = (data < 0 ? '-' : '') + (scale ? `${s.slice(0, -scale) || '0'}.${s.slice(-scale)}` : s);
1431
+ return colors ? green(str) : str;
1344
1432
  }
1345
1433
  default:
1346
1434
  return String(value);
1347
1435
  }
1348
1436
  }
1349
- /** Format vector, the index-th item in the collection is a string, a null value returns a 'null' string */
1437
+ /** formatted vector, the index-th item in the collection is a string */
1350
1438
  export function formati(obj, index, options = {}) {
1351
1439
  if (64 <= obj.type && obj.type < 128) { // array vector
1352
1440
  // 因为 array vector 目前只支持:Logical, Integral(不包括 INT128, COMPRESS 类型), Floating, Temporal
@@ -1381,12 +1469,9 @@ export function formati(obj, index, options = {}) {
1381
1469
  }
1382
1470
  }
1383
1471
  switch (obj.type) {
1384
- case DdbType.string:
1385
- case DdbType.symbol:
1386
- return obj.value[index];
1387
1472
  case DdbType.symbol_extended: {
1388
1473
  const { base, data } = obj.value;
1389
- return base[data[index]];
1474
+ return format(DdbType.string, base[data[index]], obj.le, options);
1390
1475
  }
1391
1476
  case DdbType.uuid:
1392
1477
  case DdbType.int128:
@@ -1402,6 +1487,17 @@ export function formati(obj, index, options = {}) {
1402
1487
  case DdbType.complex:
1403
1488
  case DdbType.point:
1404
1489
  return format(obj.type, obj.value.subarray(2 * index, 2 * (index + 1)), obj.le, options);
1490
+ case DdbType.decimal32:
1491
+ case DdbType.decimal64: {
1492
+ const { scale, data } = obj.value;
1493
+ const x = data[index];
1494
+ if (x === nulls.int64 && obj.type === DdbType.decimal64 ||
1495
+ x === nulls.int32 && obj.type === DdbType.decimal32)
1496
+ return '';
1497
+ const s = String(x < 0 ? -x : x).padStart(scale, '0');
1498
+ const str = (x < 0 ? '-' : '') + (scale ? `${s.slice(0, -scale) || '0'}.${s.slice(-scale)}` : s);
1499
+ return options.colors ? green(str) : str;
1500
+ }
1405
1501
  default:
1406
1502
  return format(obj.type, obj.value[index], obj.le, options);
1407
1503
  }
@@ -1816,8 +1912,7 @@ export function datehour2str(datehour, format = 'YYYY.MM.DDTHH') {
1816
1912
  export function str2timestamp(str, format = 'YYYY.MM.DD HH:mm:ss.SSS') {
1817
1913
  if (!str || str === 'null')
1818
1914
  return nulls.int64;
1819
- if (str.length !== format.length)
1820
- throw new Error('The length of the timestamp string is not equal to the length of the format string');
1915
+ assert('The length of the timestamp string must be equal to the length of the format string');
1821
1916
  return BigInt(-timezone_offset +
1822
1917
  dayjs(str, format).valueOf());
1823
1918
  }
@@ -1830,12 +1925,10 @@ export function nanotime2str(nanotime, format = 'HH:mm:ss.SSSSSSSSS') {
1830
1925
  if (nanotime < 0n)
1831
1926
  return String(nanotime);
1832
1927
  const i_second_start = format.indexOf('ss');
1833
- if (i_second_start === -1)
1834
- throw new Error('格式串必须包含秒的格式 (ss)');
1928
+ assert(i_second_start !== -1, 'The format string must contain the format for seconds (ss)');
1835
1929
  const i_second_end = i_second_start + 2;
1836
1930
  const i_nanosecond_start = format.indexOf('SSSSSSSSS', i_second_end);
1837
- if (i_nanosecond_start === -1)
1838
- throw new Error('格式串必须包含纳秒的格式 (SSSSSSSSS)');
1931
+ assert(i_nanosecond_start !== -1, 'Format string must contain nanosecond format (SSSSSSSSS)');
1839
1932
  return (dayjs(timezone_offset + (Number(nanotime) / 1000000)).format(format.slice(0, i_second_end)) +
1840
1933
  format.slice(i_second_end, i_nanosecond_start) +
1841
1934
  String(nanotime % 1000000000n).padStart(9, '0'));
@@ -1863,12 +1956,10 @@ export function nanotimestamp2str(nanotimestamp, format = 'YYYY.MM.DD HH:mm:ss.S
1863
1956
  if (nanotimestamp === null || nanotimestamp === nulls.int64)
1864
1957
  return 'null';
1865
1958
  const i_second_start = format.indexOf('ss');
1866
- if (i_second_start === -1)
1867
- throw new Error('The format string must contain the format for seconds (ss)');
1959
+ assert(i_second_start !== -1, 'The format string must contain the format for seconds (ss)');
1868
1960
  const i_second_end = i_second_start + 2;
1869
1961
  const i_nanosecond_start = format.indexOf('SSSSSSSSS', i_second_end);
1870
- if (i_nanosecond_start === -1)
1871
- throw new Error('The format string must contain the format for nanoseconds (SSSSSSSSS)');
1962
+ assert(i_nanosecond_start !== -1, 'Format string must contain nanosecond format (SSSSSSSSS)');
1872
1963
  const remainder = nanotimestamp % 1000000000n;
1873
1964
  const borrow = remainder < 0n;
1874
1965
  return (dayjs(timezone_offset +
@@ -1890,15 +1981,12 @@ export function nanotimestamp2str(nanotimestamp, format = 'YYYY.MM.DD HH:mm:ss.S
1890
1981
  export function str2nanotimestamp(str, format = 'YYYY.MM.DD HH:mm:ss.SSSSSSSSS') {
1891
1982
  if (!str || str === 'null')
1892
1983
  return nulls.int64;
1893
- if (str.length !== format.length)
1894
- throw new Error('nanotimestamp string length is not equal to format string length');
1984
+ assert(str.length === format.length, 'nanotimestamp string length must be equal to format string length');
1895
1985
  const i_second_start = format.indexOf('ss');
1896
- if (i_second_start === -1)
1897
- throw new Error('The format string must contain the format for seconds (ss)');
1986
+ assert(i_second_start !== -1, 'The format string must contain the format for seconds (ss)');
1898
1987
  const i_second_end = i_second_start + 2;
1899
1988
  const i_nanosecond_start = format.indexOf('SSSSSSSSS', i_second_end);
1900
- if (i_nanosecond_start === -1)
1901
- throw new Error('Format string must contain nanosecond format (SSSSSSSSS)');
1989
+ assert(i_nanosecond_start !== -1, 'Format string must contain nanosecond format (SSSSSSSSS)');
1902
1990
  return (BigInt(-timezone_offset +
1903
1991
  dayjs(str.slice(0, i_second_end), format.slice(0, i_second_end)).valueOf()) * 1000000n
1904
1992
  +
@@ -1926,6 +2014,14 @@ export function int1282str(buffer, le = true) {
1926
2014
  return [...buf].map(x => x.toString(16)
1927
2015
  .padStart(2, '0')).join('');
1928
2016
  }
2017
+ export const winsize = 40;
2018
+ export class ConnectionError extends Error {
2019
+ ddb;
2020
+ constructor(ddb) {
2021
+ super(`websocket is disconnected: ${ddb.url}`);
2022
+ this.ddb = ddb;
2023
+ }
2024
+ }
1929
2025
  export class DDB {
1930
2026
  /** 当前的 session id (http 或 tcp) */
1931
2027
  sid = '0';
@@ -1949,7 +2045,9 @@ export class DDB {
1949
2045
  password = '123456';
1950
2046
  /** python session flag (2048) */
1951
2047
  python = false;
1952
- // 内部选项, 状态
2048
+ /** Whether it is a streaming data connection, this field is always null for non-streaming data */
2049
+ streaming = null;
2050
+ // --- 内部选项, 状态
1953
2051
  print_message_buffer = false;
1954
2052
  print_object_buffer = false;
1955
2053
  print_message = true;
@@ -1972,6 +2070,7 @@ export class DDB {
1972
2070
  - username?: DolphinDB username, default `'admin'`
1973
2071
  - password?: DolphinDB password, default `'123456'`
1974
2072
  - python?: set python session flag, default `false`
2073
+ - streaming?: When this option is set, the WebSocket connection is only used for streaming data
1975
2074
 
1976
2075
  @example
1977
2076
  let ddb = new DDB('ws://127.0.0.1:8848')
@@ -1989,7 +2088,7 @@ export class DDB {
1989
2088
  const _url = new URL(location.href);
1990
2089
  const hostname = _url.searchParams.get('hostname') || location.hostname;
1991
2090
  const port = _url.searchParams.get('port') || location.port;
1992
- url = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${hostname}${port ? `:${port}` : ''}/`;
2091
+ url = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${hostname}:${port}/`;
1993
2092
  }
1994
2093
  this.url = url;
1995
2094
  if (options.autologin !== undefined)
@@ -2000,43 +2099,54 @@ export class DDB {
2000
2099
  this.password = options.password;
2001
2100
  if (options.python !== undefined)
2002
2101
  this.python = options.python;
2102
+ if (options.streaming !== undefined)
2103
+ this.streaming = options.streaming;
2003
2104
  }
2004
- on_message(event) { }
2005
- /** Establish the actual WebSocket connection to the DolphinDB corresponding to the URL
2006
- - options?:
2007
- - url?: DolphinDB WebSocket URL. By default, the WebSocket URL passed in when the instance is initialized is used
2008
- - autologin?: Whether to log in automatically after establishing a connection, default `true`
2009
- - username?: DolphinDB username, default `'admin'`
2010
- - password?: DolphinDB password, default `'123456'`
2011
- - python?: set python session flag, default `false`
2012
- */
2013
- async connect(options = {}) {
2014
- if (options.url !== undefined)
2015
- this.url = options.url;
2016
- if (options.autologin !== undefined)
2017
- this.autologin = options.autologin;
2018
- if (options.username !== undefined)
2019
- this.username = options.username;
2020
- if (options.password !== undefined)
2021
- this.password = options.password;
2022
- if (options.python !== undefined)
2023
- this.python = options.python;
2024
- this.disconnect();
2025
- await connect_websocket(this.url, {
2026
- protocols: this.python ? ['python'] : [],
2027
- on_open: async (event, websocket) => {
2028
- this.websocket = websocket;
2029
- await this.rpc('connect', {});
2030
- },
2031
- on_message: (event) => {
2032
- this.on_message(event);
2033
- }
2105
+ on_message(event) {
2106
+ // 这里的实现一定会被 connect 中的实现覆盖
2107
+ }
2108
+ /** Establish a actual websocket connection to the DolphindB corresponding to the URL
2109
+ 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
2110
+ this.autologin automatically log in when it is true */
2111
+ async connect() {
2112
+ if (this.connected)
2113
+ return;
2114
+ const ptail = this.pconnect;
2115
+ let resolve;
2116
+ this.pconnect = new Promise((_resolve, _reject) => {
2117
+ resolve = _resolve;
2034
2118
  });
2035
- if (this.autologin)
2036
- if (this.python)
2037
- await this.eval(`login(${this.username.quote('double')}, ${this.password.quote('double')})`, { urgent: true });
2038
- else
2039
- await this.call('login', [this.username, this.password], { urgent: true });
2119
+ await ptail;
2120
+ try {
2121
+ if (!this.connected) {
2122
+ this.on_message = (event) => {
2123
+ assert(false, 'This is the default on_message before calling this.rpc, and it should not be called. Unless the server pushed message first after the connection is established');
2124
+ };
2125
+ this.presult = Promise.resolve(null);
2126
+ this.pnode_run_defined = false;
2127
+ this.websocket = await connect_websocket(this.url, {
2128
+ protocols: (() => {
2129
+ if (this.streaming)
2130
+ return 'streaming';
2131
+ if (this.python)
2132
+ return 'python';
2133
+ })(),
2134
+ on_message: (event) => {
2135
+ this.on_message(event);
2136
+ }
2137
+ });
2138
+ if (this.streaming)
2139
+ await this.subscribe();
2140
+ else {
2141
+ await this.rpc('connect', {});
2142
+ if (this.autologin)
2143
+ await this.call('login', [this.username, this.password], { urgent: true });
2144
+ }
2145
+ }
2146
+ }
2147
+ finally {
2148
+ resolve();
2149
+ }
2040
2150
  }
2041
2151
  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, } = {}) {
2042
2152
  let flag = 0;
@@ -2072,12 +2182,9 @@ export class DDB {
2072
2182
  disconnect() {
2073
2183
  if (this.connected)
2074
2184
  this.websocket.close(1000);
2075
- this.on_message = () => { };
2076
- this.presult = Promise.resolve(null);
2077
- this.pconnect = Promise.resolve();
2078
- this.pnode_run_defined = false;
2079
2185
  }
2080
2186
  /** rpc through websocket (function/script/variable command)
2187
+ When the DDB is not connected, the call will be automatically connected. When the connection is disconnected, the call will throw the Connectionerror
2081
2188
  - type: API 类型: 'script' | 'function' | 'variable'
2082
2189
  - options:
2083
2190
  - urgent?: 决定 `行为标识` 那一行字符串的取值(只适用于 script 和 function)
@@ -2087,24 +2194,12 @@ export class DDB {
2087
2194
  为 false 时返回的 DdbObj 仅含有 buffer 和 le,不做解析,以便后续转发、序列化
2088
2195
  */
2089
2196
  async rpc(type, { script, func, args = [], vars = [], urgent, listener, parse_object, }) {
2090
- if (!this.websocket) {
2091
- const ptail = this.pconnect;
2092
- let resolve;
2093
- this.pconnect = new Promise((_resolve, _reject) => {
2094
- resolve = _resolve;
2095
- });
2096
- await ptail;
2097
- try {
2098
- if (!this.websocket)
2099
- await this.connect();
2100
- }
2101
- finally {
2102
- resolve();
2103
- }
2104
- }
2197
+ if (!this.websocket)
2198
+ await this.connect();
2105
2199
  if (!this.connected)
2106
- throw new Error(`${this.url} is already disconnected`);
2200
+ throw new ConnectionError(this);
2107
2201
  if (func === 'pnode_run' && !this.pnode_run_defined) {
2202
+ // 保证并发调用 rpc 时只定义一次 pnode_run
2108
2203
  const ptail = this.ppnoderun;
2109
2204
  let resolve;
2110
2205
  this.ppnoderun = new Promise((_resolve, _reject) => {
@@ -2254,8 +2349,7 @@ export class DDB {
2254
2349
  */
2255
2350
  async call(func, args = [], { urgent, node, nodes, func_type, add_node_alias, listener, parse_object, } = {}) {
2256
2351
  if (node) {
2257
- if (typeof func_type === 'undefined')
2258
- throw new Error('指定 node 时必须设置 func_type');
2352
+ assert(func_type in DdbFunctionType, 'func_type must be set when specifying node');
2259
2353
  args = [
2260
2354
  node,
2261
2355
  new DdbFunction(func, func_type),
@@ -2292,10 +2386,21 @@ export class DDB {
2292
2386
  vars,
2293
2387
  /** Uploaded variables' value */
2294
2388
  args, { listener, parse_object, } = {}) {
2295
- if (!args.length || args.length !== vars.length)
2296
- throw new Error('variable 指令参数为空或参数名为空,或数量不匹配');
2389
+ assert(args.length && args.length === vars.length, 'variable command parameter cannot be empty and parameter name cannot be empty, and the number should match');
2297
2390
  return this.rpc('variable', { vars, args, listener, parse_object });
2298
2391
  }
2392
+ /** Cancel all console jobs corresponding to the current session id */
2393
+ async cancel() {
2394
+ let ddb = new DDB(this.url, this);
2395
+ try {
2396
+ await ddb.eval(`jobs = exec rootJobId from getConsoleJobs() where sessionId = ${this.sid}\n` +
2397
+ 'if (size(jobs))\n' +
2398
+ ' cancelConsoleJob(jobs)\n', { urgent: true });
2399
+ }
2400
+ finally {
2401
+ ddb.disconnect();
2402
+ }
2403
+ }
2299
2404
  /** 解析服务端响应报文,返回去掉 header 的 data buf */
2300
2405
  parse_message(buf, parse_object = this.parse_object) {
2301
2406
  // MSG\n
@@ -2310,8 +2415,8 @@ export class DDB {
2310
2415
  // 'OK\n'
2311
2416
  // '\x04\x00\x02\x00\x00\x00'
2312
2417
  /** index of line feed 0 */
2313
- const i_lf_0 = buf.indexOf(0x0a); // '\n'
2314
- const parts = this.dec.decode(buf.subarray(0, i_lf_0)).split(' ');
2418
+ const ilf0 = buf.indexOf(0x0a); // '\n'
2419
+ const parts = this.dec.decode(buf.subarray(0, ilf0)).split(' ');
2315
2420
  /** session id */
2316
2421
  const sid = parts[0];
2317
2422
  if (sid !== this.sid) {
@@ -2319,35 +2424,142 @@ export class DDB {
2319
2424
  this.sid = sid;
2320
2425
  }
2321
2426
  /** 返回对象的数量 */
2322
- const n_obj = Number(parts[1]);
2427
+ const nobj = Number(parts[1]);
2323
2428
  /** 大小端: 协议中大端为 0, 小端为 1 */
2324
2429
  this.le = Number(parts[2]) !== 0;
2325
- const i_ls_1 = i_lf_0 + 1;
2326
- const i_lf_1 = buf.indexOf(0x0a, i_ls_1); // '\n'
2430
+ const ils1 = ilf0 + 1;
2431
+ const ilf1 = buf.indexOf(0x0a, ils1); // '\n'
2327
2432
  /** 'OK' 表示成功,其它文本表示失败 */
2328
- const message = this.dec.decode(buf.subarray(i_ls_1, i_lf_1));
2433
+ const message = this.dec.decode(buf.subarray(ils1, ilf1));
2329
2434
  if (message !== 'OK')
2330
2435
  return {
2331
2436
  type: 'error',
2332
2437
  data: new Error(message)
2333
2438
  };
2334
- const buf_obj = buf.subarray(i_lf_1 + 1);
2439
+ const bufobj = buf.subarray(ilf1 + 1);
2335
2440
  if (this.print_object_buffer)
2336
- console.log(buf_obj);
2441
+ console.log(bufobj);
2337
2442
  return {
2338
2443
  type: 'object',
2339
2444
  data: parse_object ?
2340
- DdbObj.parse(buf_obj, this.le)
2445
+ DdbObj.parse(bufobj, this.le)
2341
2446
  :
2342
2447
  new DdbObj({
2343
2448
  form: DdbForm.scalar,
2344
2449
  type: DdbType.void,
2345
2450
  length: 0,
2346
2451
  le: this.le,
2347
- buffer: buf_obj,
2452
+ buffer: bufobj,
2348
2453
  })
2349
2454
  };
2350
2455
  }
2456
+ /** Internal stream subscription method */
2457
+ async subscribe() {
2458
+ const args = DdbObj.to_ddbobjs([
2459
+ new DdbVectorString(this.autologin ?
2460
+ [this.username, this.password]
2461
+ :
2462
+ // 不自动登录也要传空的 string vector, 否则 server 无法解析
2463
+ []),
2464
+ // todo: 还有用吗?需要获取客户端 IP 吗
2465
+ 'localhost',
2466
+ // todo: 端口还有用吗?
2467
+ new DdbInt(0),
2468
+ this.streaming.table,
2469
+ (this.streaming.action ||= `api_js_${new Date().getTime()}`),
2470
+ // new DdbInt(-1), // offset
2471
+ // filter
2472
+ // allow exists
2473
+ ]);
2474
+ return new Promise((resolve, reject) => {
2475
+ let first = true;
2476
+ let second = true;
2477
+ // 先准备好收到 websocket message 的 callback
2478
+ this.on_message = ({ data: buffer }) => {
2479
+ if (first) {
2480
+ first = false;
2481
+ try {
2482
+ const dv = new DataView(buffer);
2483
+ const status = dv.getUint8(1);
2484
+ this.le = Boolean(dv.getUint8(2));
2485
+ const nobjs = dv.getUint32(3, this.le);
2486
+ // 解析返回数据,从 server 实现看其实只有一个 DdbObj
2487
+ let offset = 7;
2488
+ let objs = new Array(nobjs);
2489
+ const buf = new Uint8Array(buffer);
2490
+ for (let i = 0; i < nobjs; i++) {
2491
+ const obj = DdbObj.parse(buf.subarray(offset), this.le);
2492
+ offset += obj.length;
2493
+ objs[i] = obj;
2494
+ console.log(`订阅流表的响应数据: objs[${i}]: ${obj.toString()}`);
2495
+ }
2496
+ this.streaming.colnames = objs[0].value;
2497
+ if (status === StreamingStatusCode.ok)
2498
+ console.log('Subscribed to streaming table:', { status: StreamingStatusCode[status], result: objs[0] });
2499
+ else
2500
+ reject(new Error(`Failed to subscribe to streaming table: { status: ${StreamingStatusCode[status]}, error: '${objs[0]?.value}' }`));
2501
+ }
2502
+ catch (error) {
2503
+ reject(error);
2504
+ }
2505
+ }
2506
+ else {
2507
+ try {
2508
+ const dv = new DataView(buffer);
2509
+ const buf = new Uint8Array(buffer);
2510
+ let { streaming } = this;
2511
+ streaming.time = dv.getBigInt64(1, this.le);
2512
+ streaming.id = dv.getBigInt64(9, this.le);
2513
+ const i_topic_end = buf.indexOf(0, 17);
2514
+ streaming.topic = this.dec.decode(buf.subarray(17, i_topic_end));
2515
+ if (second) {
2516
+ second = false;
2517
+ // 是 table
2518
+ streaming.schema = DdbObj.parse(buf.subarray(i_topic_end + 1), this.le);
2519
+ console.log('Received streaming table schema:', streaming.schema);
2520
+ streaming.window = {
2521
+ offset: 0,
2522
+ rows: 0,
2523
+ segments: [],
2524
+ };
2525
+ assert(streaming.schema.rows === 0, 'schema.rows === 0');
2526
+ resolve(streaming.schema);
2527
+ }
2528
+ else {
2529
+ // 是 column 片段组成的 any vector
2530
+ const data = streaming.data = DdbObj.parse(buf.subarray(i_topic_end + 1), this.le);
2531
+ let { window: win } = streaming;
2532
+ win.rows += data.value[0].rows;
2533
+ win.segments.push(data);
2534
+ if (win.rows >= winsize * 2 && win.segments.length >= 2) {
2535
+ let winsize_ = 0;
2536
+ let i = win.segments.length - 1;
2537
+ // 往前移动至首个累计 winsize_ 超过 winsize 的位置
2538
+ for (; winsize_ < winsize; i--)
2539
+ winsize_ += win.segments[i].rows;
2540
+ win.segments = win.segments.slice(i);
2541
+ win.offset += win.rows - winsize_;
2542
+ win.rows = winsize_;
2543
+ console.log('exceeds winsize, shrink window.segments', win);
2544
+ }
2545
+ streaming.handler(streaming);
2546
+ }
2547
+ }
2548
+ catch (error) {
2549
+ reject(error);
2550
+ }
2551
+ }
2552
+ };
2553
+ // 发送订阅 websocket message
2554
+ this.websocket.send(concat([
2555
+ Uint8Array.of(1, // from = SubscriberFromType::FROM_API
2556
+ 1, // type = SubscriberRPCType::RPC_START
2557
+ Number(DDB.le_client)),
2558
+ Uint32Array.of(args.length),
2559
+ ...args.map(obj => obj.pack())
2560
+ ]));
2561
+ });
2562
+ }
2351
2563
  }
2352
2564
  export let ddb = window.ddb = new DDB();
2353
2565
  //# sourceMappingURL=browser.js.map