dolphindb 2.0.504 → 2.0.507

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
@@ -6,6 +6,7 @@ const ws_1 = require("ws");
6
6
  const dayjs_1 = tslib_1.__importDefault(require("dayjs"));
7
7
  const customParseFormat_js_1 = tslib_1.__importDefault(require("dayjs/plugin/customParseFormat.js"));
8
8
  dayjs_1.default.extend(customParseFormat_js_1.default);
9
+ const net_js_1 = require("xshell/net.js");
9
10
  const utils_js_1 = require("xshell/utils.js");
10
11
  var DdbForm;
11
12
  (function (DdbForm) {
@@ -109,8 +110,7 @@ var DdbFunctionType;
109
110
  DdbFunctionType[DdbFunctionType["JitPartialFunc"] = 8] = "JitPartialFunc";
110
111
  })(DdbFunctionType = exports.DdbFunctionType || (exports.DdbFunctionType = {}));
111
112
  exports.nulls = {
112
- bool: 0x80,
113
- char: '\x80',
113
+ int8: -0x80,
114
114
  int16: -32768,
115
115
  int32: -2147483648,
116
116
  int64: -0x8000000000000000n,
@@ -284,12 +284,14 @@ class DdbObj {
284
284
  case DdbType.void:
285
285
  return [1, null];
286
286
  case DdbType.bool: {
287
- const value = buf[0];
288
- return [1, value === exports.nulls.bool ? null : Boolean(value)];
287
+ const dv = new DataView(buf.buffer, buf.byteOffset);
288
+ const value = dv.getInt8(0);
289
+ return [1, value === exports.nulls.int8 ? null : Boolean(value)];
289
290
  }
290
291
  case DdbType.char: {
291
- const value = String.fromCharCode(buf[0]);
292
- return [1, value === exports.nulls.char ? null : value];
292
+ const dv = new DataView(buf.buffer, buf.byteOffset);
293
+ const value = dv.getInt8(0);
294
+ return [1, value === exports.nulls.int8 ? null : value];
293
295
  }
294
296
  case DdbType.short: {
295
297
  const dv = new DataView(buf.buffer, buf.byteOffset);
@@ -474,11 +476,10 @@ class DdbObj {
474
476
  static parse_vector_items(buf, le, type, length) {
475
477
  switch (type) {
476
478
  case DdbType.bool:
477
- return [length, buf.slice(0, length)];
478
479
  case DdbType.char:
479
480
  return [
480
481
  length,
481
- this.dec.decode(buf.subarray(0, length))
482
+ new Int8Array(buf.buffer.slice(buf.byteOffset, buf.byteOffset + length))
482
483
  ];
483
484
  case DdbType.short:
484
485
  return [
@@ -628,17 +629,17 @@ class DdbObj {
628
629
  return [Uint8Array.of(1)];
629
630
  case DdbType.bool:
630
631
  return [
631
- Uint8Array.of(value === null ?
632
- exports.nulls.bool
632
+ Int8Array.of(value === null ?
633
+ exports.nulls.int8
633
634
  :
634
635
  Number(value))
635
636
  ];
636
637
  case DdbType.char:
637
638
  return [
638
- Uint8Array.of((value === null ?
639
- exports.nulls.char
639
+ Int8Array.of((value === null ?
640
+ exports.nulls.int8
640
641
  :
641
- value).charCodeAt(0))
642
+ value))
642
643
  ];
643
644
  case DdbType.short:
644
645
  return [
@@ -780,7 +781,7 @@ class DdbObj {
780
781
  case DdbType.bool:
781
782
  return [value];
782
783
  case DdbType.char:
783
- return [this.enc.encode(value)];
784
+ return [value];
784
785
  case DdbType.short:
785
786
  return [value];
786
787
  case DdbType.int:
@@ -883,10 +884,16 @@ class DdbObj {
883
884
  }
884
885
  })();
885
886
  const data = (() => {
886
- if (this.form === DdbForm.pair)
887
- return `${(0, utils_js_1.inspect)(this.value[0], options)}, ${(0, utils_js_1.inspect)(this.value[1], options)}`;
888
- if (this.form === DdbForm.scalar && this.type === DdbType.functiondef)
889
- return (0, utils_js_1.inspect)(this.value.name, options);
887
+ switch (this.form) {
888
+ case DdbForm.pair:
889
+ return `${(0, utils_js_1.inspect)(this.value[0], options)}, ${(0, utils_js_1.inspect)(this.value[1], options)}`;
890
+ case DdbForm.scalar:
891
+ switch (this.type) {
892
+ case DdbType.functiondef:
893
+ return (0, utils_js_1.inspect)(this.value.name, options);
894
+ }
895
+ break;
896
+ }
890
897
  if (this.value instanceof Uint8Array)
891
898
  return (0, utils_js_1.inspect)((0, utils_js_1.typed_array_to_buffer)(this.value), options);
892
899
  return (0, utils_js_1.inspect)(this.value, options);
@@ -900,21 +907,26 @@ class DdbObj {
900
907
  for (let i = 0; i < this.rows; i++) {
901
908
  let row = {};
902
909
  for (let j = 0; j < this.cols; j++) {
903
- const { type, name, value } = this.value[j]; // column
910
+ const { type, name, value: values } = this.value[j]; // column
904
911
  switch (type) {
905
- case DdbType.bool:
906
- row[name] = Boolean(value[i]);
912
+ case DdbType.bool: {
913
+ const value = values[i];
914
+ row[name] = value === exports.nulls.int8 ?
915
+ null
916
+ :
917
+ Boolean(value);
907
918
  break;
919
+ }
908
920
  case DdbType.ipaddr:
909
- row[name] = value.subarray(16 * i, 16 * (i + 1));
921
+ row[name] = values.subarray(16 * i, 16 * (i + 1));
910
922
  break;
911
923
  case DdbType.symbol_extended: {
912
- const { base, data } = value;
924
+ const { base, data } = values;
913
925
  row[name] = base[data[i]];
914
926
  break;
915
927
  }
916
928
  default:
917
- row[name] = value[i];
929
+ row[name] = values[i];
918
930
  }
919
931
  }
920
932
  rows[i] = row;
@@ -968,7 +980,10 @@ class DdbChar extends DdbObj {
968
980
  form: DdbForm.scalar,
969
981
  type: DdbType.char,
970
982
  length: 1,
971
- value,
983
+ value: typeof value === 'string' ?
984
+ value.charCodeAt(0)
985
+ :
986
+ value
972
987
  });
973
988
  }
974
989
  }
@@ -1363,7 +1378,6 @@ class DDB {
1363
1378
  /** utf-8 text decoder */
1364
1379
  this.dec = new TextDecoder('utf-8');
1365
1380
  this.enc = new TextEncoder();
1366
- this.print_buffer = false;
1367
1381
  this.websocket = null;
1368
1382
  /** little endian (server) */
1369
1383
  this.le = true;
@@ -1375,8 +1389,13 @@ class DDB {
1375
1389
  this.password = '123456';
1376
1390
  /** python session flag (2048) */
1377
1391
  this.python = false;
1378
- this.message_hook = null;
1379
- this.presult = Promise.resolve(null);
1392
+ this.print_message_buffer = false;
1393
+ this.print_object_buffer = false;
1394
+ this.print_message = true;
1395
+ this.parse_object = true;
1396
+ /** DdbMessage listeners */
1397
+ this.listeners = [];
1398
+ this.promise = Promise.resolve(null);
1380
1399
  this.url = url;
1381
1400
  if (options.autologin !== undefined)
1382
1401
  this.autologin = options.autologin;
@@ -1387,16 +1406,10 @@ class DDB {
1387
1406
  if (options.python !== undefined)
1388
1407
  this.python = options.python;
1389
1408
  }
1390
- /** print message handler */
1391
- printer(message) {
1392
- console.log(message);
1393
- }
1394
- /** resolver, rejector, promise of last rpc */
1395
- presolver(buf) { }
1396
- prejector(error) { }
1397
1409
  get connected() {
1398
1410
  return this.websocket?.readyState === ws_1.WebSocket.OPEN;
1399
1411
  }
1412
+ on_message(event) { }
1400
1413
  /** Establish the actual WebSocket connection to the DolphinDB corresponding to the URL
1401
1414
  - options?:
1402
1415
  - url?: DolphinDB WebSocket URL. By default, the WebSocket URL passed in when the instance is initialized is used
@@ -1417,60 +1430,51 @@ class DDB {
1417
1430
  if (options.python !== undefined)
1418
1431
  this.python = options.python;
1419
1432
  this.disconnect();
1420
- let websocket = new ws_1.WebSocket(this.url, this.python ? ['python'] : [], {
1421
- maxPayload: 2 ** 33 // 8 GB
1422
- });
1423
- // https://stackoverflow.com/questions/11821096/what-is-the-difference-between-an-arraybuffer-and-a-blob/39951543
1424
- websocket.binaryType = 'arraybuffer';
1425
- this.websocket = websocket;
1426
- await new Promise((resolve, reject) => {
1427
- websocket.addEventListener('open', async (event) => {
1428
- console.log(`${websocket.url} opened`);
1433
+ await (0, net_js_1.connect_websocket)(this.url, {
1434
+ protocols: this.python ? ['python'] : [],
1435
+ on_open: async (event, websocket) => {
1436
+ this.websocket = websocket;
1429
1437
  await this.rpc('connect', {});
1430
- resolve();
1431
- });
1432
- websocket.addEventListener('close', event => {
1433
- console.log(`${websocket.url} closed with code = ${event.code}, reason = '${event.reason}'`);
1434
- });
1435
- websocket.addEventListener('error', event => {
1436
- const message = `${websocket.url} errored`;
1437
- console.error(message, event);
1438
- reject(Object.assign(new Error(message), { event }));
1439
- });
1440
- websocket.addEventListener('message', event => {
1441
- const buf = new Uint8Array(event.data);
1442
- if (this.message_hook)
1443
- this.message_hook(buf);
1444
- try {
1445
- const { type, data } = this.parse_message(buf);
1446
- if (type === 'message') {
1447
- this.printer(data);
1448
- return;
1449
- }
1450
- this.presolver(data);
1451
- }
1452
- catch (error) {
1453
- this.prejector(error);
1454
- }
1455
- });
1438
+ },
1439
+ on_message: event => {
1440
+ this.on_message(event);
1441
+ }
1456
1442
  });
1457
- if (!this.python)
1458
- await this.eval('def pnode_run (nodes, func_name, args, add_node_alias = true) {\n' +
1443
+ await this.eval(this.python ?
1444
+ 'def pnode_run (nodes, func_name, args, add_node_alias):\n' +
1459
1445
  ' nargs = size(args)\n' +
1460
1446
  ' func = funcByName(func_name)\n' +
1461
1447
  ' \n' +
1462
- ' if (!nargs)\n' +
1448
+ ' if not nargs:\n' +
1463
1449
  ' return pnodeRun(func, nodes, add_node_alias)\n' +
1464
1450
  ' \n' +
1465
- ' args_partial = array(any, 1 + nargs, 1 + nargs)\n' +
1466
- ' args_partial[0] = func\n' +
1467
- ' args_partial[1:] = args\n' +
1451
+ ' args_partial = [ ]\n' +
1452
+ ' args_partial.append(func)\n' +
1453
+ ' for a in args:\n' +
1454
+ ' args_partial.append(a)\n' +
1455
+ ' \n' +
1468
1456
  ' return pnodeRun(\n' +
1469
1457
  ' unifiedCall(partial, args_partial),\n' +
1470
1458
  ' nodes,\n' +
1471
1459
  ' add_node_alias\n' +
1472
- ' )\n' +
1473
- '}\n', { urgent: true });
1460
+ ' )\n'
1461
+ :
1462
+ 'def pnode_run (nodes, func_name, args, add_node_alias = true) {\n' +
1463
+ ' nargs = size(args)\n' +
1464
+ ' func = funcByName(func_name)\n' +
1465
+ ' \n' +
1466
+ ' if (!nargs)\n' +
1467
+ ' return pnodeRun(func, nodes, add_node_alias)\n' +
1468
+ ' \n' +
1469
+ ' args_partial = array(any, 1 + nargs, 1 + nargs)\n' +
1470
+ ' args_partial[0] = func\n' +
1471
+ ' args_partial[1:] = args\n' +
1472
+ ' return pnodeRun(\n' +
1473
+ ' unifiedCall(partial, args_partial),\n' +
1474
+ ' nodes,\n' +
1475
+ ' add_node_alias\n' +
1476
+ ' )\n' +
1477
+ '}\n', { urgent: true });
1474
1478
  if (this.autologin)
1475
1479
  if (this.python)
1476
1480
  await this.eval(`login(${this.username.quote('double')}, ${this.password.quote('double')})`, { urgent: true });
@@ -1506,47 +1510,69 @@ class DDB {
1506
1510
  limit,
1507
1511
  ] : [],
1508
1512
  ];
1509
- return `/ ${options.join('_')}`;
1513
+ return options.join('_');
1510
1514
  }
1511
1515
  disconnect() {
1512
1516
  if (this.connected)
1513
1517
  this.websocket.close(1000);
1514
- this.presolver = buf => { };
1515
- this.prejector = error => { };
1516
- this.presult = Promise.resolve(null);
1518
+ this.on_message = () => { };
1519
+ this.promise = Promise.resolve(null);
1517
1520
  }
1518
1521
  /** rpc through websocket (function/script/variable command)
1519
1522
  - type: API 类型: 'script' | 'function' | 'variable'
1520
1523
  - options:
1521
1524
  - urgent?: 决定 `行为标识` 那一行字符串的取值(只适用于 script 和 function)
1522
1525
  - vars?: type === 'variable' 时必传,variable 指令中待上传的变量名
1526
+ - handler?: Process messages during this rpc (DdbMessage)
1527
+ - parse_object?: 在该次 rpc 期间设置 parse_object, 结束后恢复原有,为 false 时返回的 DdbObj 仅含有 buffer 和 le,
1528
+ 不做解析,以便后续转发、序列化
1523
1529
  */
1524
- async rpc(type, { script, func, args = [], vars = [], urgent, }) {
1530
+ async rpc(type, { script, func, args = [], vars = [], urgent, listener, parse_object, }) {
1525
1531
  if (!this.websocket)
1526
1532
  await this.connect();
1527
1533
  if (!this.connected)
1528
1534
  throw new Error(`${this.url} is already disconnected`);
1535
+ // this 上的当前配置需要在 message 到达后使用,先保存起来
1536
+ const _handlers = [...this.listeners].reverse();
1529
1537
  // 临界区:保证多个 rpc 并发时形成 promise 链
1530
1538
  // ddb 世界观:需要等待上一个 rpc 结果从 server 返回之后才能发起下一个调用
1531
1539
  // 违反世界观可能造成:
1532
1540
  // 1. 并发多个请求只返回第一个结果(阻塞,需后续请求疏通)
1533
1541
  // 2. windows 下 ddb server 返回多个相同的结果
1534
- const ptail = this.presult;
1535
- let presolver;
1536
- let prejector;
1537
- const presult = this.presult = new Promise((resolve, reject) => {
1538
- presolver = resolve;
1539
- prejector = reject;
1542
+ const ptail = this.promise;
1543
+ let resolve;
1544
+ let reject;
1545
+ const promise = this.promise = new Promise((_resolve, _reject) => {
1546
+ resolve = _resolve;
1547
+ reject = _reject;
1540
1548
  });
1541
1549
  try {
1542
1550
  await ptail;
1543
1551
  }
1544
1552
  catch { }
1545
- this.presolver = presolver;
1546
- this.prejector = prejector;
1547
- // 临界区结束,只有一个 rpc 请求可以写 WebSocket
1548
- if (urgent && type !== 'script' && type !== 'function')
1549
- throw new Error('urgent 只适用于 script 和 funciton');
1553
+ // 临界区结束,只有一个 rpc 函数调用运行到这里,可以独占 this.on_message 然后写 WebSocket
1554
+ this.on_message = ({ data: buffer }) => {
1555
+ const buf = new Uint8Array(buffer);
1556
+ if (this.print_message_buffer)
1557
+ console.log((0, utils_js_1.typed_array_to_buffer)(buf));
1558
+ const message = this.parse_message(buf, parse_object);
1559
+ listener?.(message, this);
1560
+ for (const listener of _handlers)
1561
+ listener(message, this);
1562
+ const { type, data } = message;
1563
+ switch (type) {
1564
+ case 'print':
1565
+ if (this.print_message)
1566
+ console.log(data);
1567
+ return;
1568
+ case 'object':
1569
+ resolve(data);
1570
+ return;
1571
+ case 'error':
1572
+ reject(data);
1573
+ return;
1574
+ }
1575
+ };
1550
1576
  args = this.to_ddbobjs(args);
1551
1577
  const command = this.enc.encode((() => {
1552
1578
  switch (type) {
@@ -1567,40 +1593,40 @@ class DDB {
1567
1593
  return 'connect\n';
1568
1594
  }
1569
1595
  })());
1570
- const message = (0, utils_js_1.concat)([
1571
- this.enc.encode(`API2 ${this.sid} ${command.length}${this.get_rpc_options({ urgent })}\n`),
1596
+ this.websocket.send((0, utils_js_1.concat)([
1597
+ this.enc.encode(`API2 ${this.sid} ${command.length} / ${this.get_rpc_options({ urgent })}\n`),
1572
1598
  command,
1573
1599
  ...args.map((arg) => arg.pack())
1574
- ]);
1575
- this.websocket.send(message);
1576
- const buf_data = await presult;
1577
- if (this.print_buffer)
1578
- console.log((0, utils_js_1.typed_array_to_buffer)(buf_data));
1579
- // LOCAL: 便于 repl
1580
- // return global.DdbObj.parse(data_buf) as DdbObj
1581
- return DdbObj.parse(buf_data, this.le);
1600
+ ]));
1601
+ return promise;
1582
1602
  }
1583
- /** eval script through websocket (script command) */
1584
- async eval(
1585
- /** 执行的脚本 */
1586
- script,
1587
- /** 执行选项 */
1588
- { urgent } = {}) {
1589
- return this.rpc('script', { script, urgent });
1603
+ /** eval script through websocket (script command)
1604
+ - script?: Script to execute
1605
+ - options?: execution options
1606
+ - urgent?: Urgent flag to ensure that submitted scripts are processed by urgent workers to prevent being blocked by other jobs
1607
+ - listener?: Process messages during this rpc (DdbMessage)
1608
+ - parse_object?: Set parse_object during this rpc, and restore the original after the end.
1609
+ When it is false, the returned DdbObj only contains buffer and le without parsing,
1610
+ so as to facilitate subsequent forwarding and serialization
1611
+ */
1612
+ async eval(script, { urgent, listener, parse_object, } = {}) {
1613
+ return this.rpc('script', { script, urgent, listener, parse_object });
1590
1614
  }
1591
1615
  /** call function through websocket (function command)
1592
- - func: 函数名
1593
- - args?: `[ ]` 调用参数 (传入的原生 string 和 boolean 会被自动转换为 DdbObj<string> 和 DdbObj<boolean>)
1594
- - options?: 调用选项
1595
- - urgent?: 紧急 flag。使用 urgent worker 执行,防止被其它作业阻塞
1596
- - node?: 设置结点 alias 时发送到集群中对应的结点执行 (使用 DolphinDB 中的 rpc 方法)
1597
- - nodes?: 设置多个结点 alias 时发送到集群中对应的多个结点执行 (使用 DolphinDB 中的 pnodeRun 方法)
1598
- - func_type?: 设置 node 参数时必传,需指定函数类型,其它情况下不传
1599
- - add_node_alias?: 设置 nodes 参数时选传,其它情况不传
1616
+ - func: function name
1617
+ - args?: `[ ]` Call parameters (the incoming native string and boolean will be automatically converted to DdbObj<string> and DdbObj<boolean>)
1618
+ - options?: call options
1619
+ - urgent?: Emergency flag. Use urgent worker execution to prevent being blocked by other jobs
1620
+ - node?: When the node alias is set, it is sent to the corresponding node in the cluster for execution (using the rpc method in DolphinDB)
1621
+ - nodes?: When setting multiple node aliases, send them to the corresponding multiple nodes in the cluster for execution (using the pnodeRun method in DolphinDB)
1622
+ - func_type?: It must be passed when setting the node parameter, the function type needs to be specified, and it is not passed in other cases
1623
+ - add_node_alias?: Select to pass when setting the nodes parameter, otherwise not pass
1624
+ - listener?: Process messages during this rpc (DdbMessage)
1625
+ - parse_object?: Set parse_object during this rpc, and restore the original after the end.
1626
+ When it is false, the returned DdbObj only contains buffer and le without parsing,
1627
+ so as to facilitate subsequent forwarding and serialization
1600
1628
  */
1601
- async call(func, args = [], { urgent, node, nodes, func_type, add_node_alias } = {}) {
1602
- if (this.python)
1603
- throw new Error('call doesn\'t support python session yet');
1629
+ async call(func, args = [], { urgent, node, nodes, func_type, add_node_alias, listener, parse_object, } = {}) {
1604
1630
  if (node) {
1605
1631
  if (typeof func_type === 'undefined')
1606
1632
  throw new Error('指定 node 时必须设置 func_type');
@@ -1630,6 +1656,8 @@ class DDB {
1630
1656
  func,
1631
1657
  args,
1632
1658
  urgent,
1659
+ listener,
1660
+ parse_object
1633
1661
  });
1634
1662
  }
1635
1663
  /** upload variable through websocket (variable command) */
@@ -1637,19 +1665,19 @@ class DDB {
1637
1665
  /** Uploaded variables' name */
1638
1666
  vars,
1639
1667
  /** Uploaded variables' value */
1640
- args) {
1668
+ args, { listener, parse_object, } = {}) {
1641
1669
  if (!args.length || args.length !== vars.length)
1642
- throw new Error('variable 指令参数为空或参数名为空,或数量不匹配');
1643
- return this.rpc('variable', { vars, args });
1670
+ throw new Error('variable command parameter is empty or parameter name is empty, or the number does not match');
1671
+ return this.rpc('variable', { vars, args, listener, parse_object });
1644
1672
  }
1645
1673
  /** 解析服务端响应报文,返回去掉 header 的 data buf */
1646
- parse_message(buf) {
1674
+ parse_message(buf, parse_object = this.parse_object) {
1647
1675
  // MSG\n
1648
1676
  // <message>\0
1649
1677
  // 'M'.codePointAt(0).to_hex_str()
1650
1678
  if (buf[0] === 0x4d && buf[1] === 0x53 && buf[2] === 0x47 && buf[3] === 0x0a)
1651
1679
  return {
1652
- type: 'message',
1680
+ type: 'print',
1653
1681
  data: this.dec.decode(buf.subarray(4))
1654
1682
  };
1655
1683
  // '1166953221 1 1\n'
@@ -1673,10 +1701,25 @@ class DDB {
1673
1701
  /** 'OK' 表示成功,其它文本表示失败 */
1674
1702
  const message = this.dec.decode(buf.subarray(i_ls_1, i_lf_1));
1675
1703
  if (message !== 'OK')
1676
- throw new Error(message);
1704
+ return {
1705
+ type: 'error',
1706
+ data: new Error(message)
1707
+ };
1708
+ const buf_obj = buf.subarray(i_lf_1 + 1);
1709
+ if (this.print_object_buffer)
1710
+ console.log((0, utils_js_1.typed_array_to_buffer)(buf_obj));
1677
1711
  return {
1678
1712
  type: 'object',
1679
- data: buf.subarray(i_lf_1 + 1)
1713
+ data: parse_object ?
1714
+ DdbObj.parse(buf_obj, this.le)
1715
+ :
1716
+ new DdbObj({
1717
+ form: DdbForm.scalar,
1718
+ type: DdbType.void,
1719
+ length: 0,
1720
+ le: this.le,
1721
+ buffer: buf_obj,
1722
+ })
1680
1723
  };
1681
1724
  }
1682
1725
  /** 自动转换 js string, boolean 为 DdbObj */