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/browser.js CHANGED
@@ -7,6 +7,7 @@ const customParseFormat_js_1 = tslib_1.__importDefault(require("dayjs/plugin/cus
7
7
  dayjs_1.default.extend(customParseFormat_js_1.default);
8
8
  require("xshell/prototype.browser");
9
9
  const utils_browser_1 = require("xshell/utils.browser");
10
+ const net_browser_js_1 = require("xshell/net.browser.js");
10
11
  var DdbForm;
11
12
  (function (DdbForm) {
12
13
  DdbForm[DdbForm["scalar"] = 0] = "scalar";
@@ -72,8 +73,7 @@ var DdbFunctionType;
72
73
  DdbFunctionType[DdbFunctionType["JitPartialFunc"] = 8] = "JitPartialFunc";
73
74
  })(DdbFunctionType = exports.DdbFunctionType || (exports.DdbFunctionType = {}));
74
75
  exports.nulls = {
75
- bool: 0x80,
76
- char: '\x80',
76
+ int8: -0x80,
77
77
  int16: -32768,
78
78
  int32: -2147483648,
79
79
  int64: -0x8000000000000000n,
@@ -247,12 +247,14 @@ class DdbObj {
247
247
  case DdbType.void:
248
248
  return [1, null];
249
249
  case DdbType.bool: {
250
- const value = buf[0];
251
- return [1, value === exports.nulls.bool ? null : Boolean(value)];
250
+ const dv = new DataView(buf.buffer, buf.byteOffset);
251
+ const value = dv.getInt8(0);
252
+ return [1, value === exports.nulls.int8 ? null : Boolean(value)];
252
253
  }
253
254
  case DdbType.char: {
254
- const value = String.fromCharCode(buf[0]);
255
- return [1, value === exports.nulls.char ? null : value];
255
+ const dv = new DataView(buf.buffer, buf.byteOffset);
256
+ const value = dv.getInt8(0);
257
+ return [1, value === exports.nulls.int8 ? null : value];
256
258
  }
257
259
  case DdbType.short: {
258
260
  const dv = new DataView(buf.buffer, buf.byteOffset);
@@ -437,11 +439,10 @@ class DdbObj {
437
439
  static parse_vector_items(buf, le, type, length) {
438
440
  switch (type) {
439
441
  case DdbType.bool:
440
- return [length, buf.slice(0, length)];
441
442
  case DdbType.char:
442
443
  return [
443
444
  length,
444
- this.dec.decode(buf.subarray(0, length))
445
+ new Int8Array(buf.buffer.slice(buf.byteOffset, buf.byteOffset + length))
445
446
  ];
446
447
  case DdbType.short:
447
448
  return [
@@ -591,17 +592,17 @@ class DdbObj {
591
592
  return [Uint8Array.of(1)];
592
593
  case DdbType.bool:
593
594
  return [
594
- Uint8Array.of(value === null ?
595
- exports.nulls.bool
595
+ Int8Array.of(value === null ?
596
+ exports.nulls.int8
596
597
  :
597
598
  Number(value))
598
599
  ];
599
600
  case DdbType.char:
600
601
  return [
601
- Uint8Array.of((value === null ?
602
- exports.nulls.char
602
+ Int8Array.of((value === null ?
603
+ exports.nulls.int8
603
604
  :
604
- value).charCodeAt(0))
605
+ value))
605
606
  ];
606
607
  case DdbType.short:
607
608
  return [
@@ -743,7 +744,7 @@ class DdbObj {
743
744
  case DdbType.bool:
744
745
  return [value];
745
746
  case DdbType.char:
746
- return [this.enc.encode(value)];
747
+ return [value];
747
748
  case DdbType.short:
748
749
  return [value];
749
750
  case DdbType.int:
@@ -846,10 +847,18 @@ class DdbObj {
846
847
  }
847
848
  })();
848
849
  const data = (() => {
849
- if (this.form === DdbForm.pair)
850
- return `${this.value[0]}, ${this.value[1]}`;
851
- if (this.form === DdbForm.scalar && this.type === DdbType.functiondef)
852
- return `'${this.value.name}'`;
850
+ switch (this.form) {
851
+ case DdbForm.pair:
852
+ return `${this.value[0]}, ${this.value[1]}`;
853
+ case DdbForm.scalar:
854
+ switch (this.type) {
855
+ case DdbType.functiondef:
856
+ return `'${this.value.name}'`;
857
+ case DdbType.string:
858
+ return `'${this.value}'`;
859
+ }
860
+ break;
861
+ }
853
862
  return this.value;
854
863
  })();
855
864
  return `${type}(${this.name ? `'${this.name}', ` : ''}${data})\n`;
@@ -881,21 +890,26 @@ class DdbObj {
881
890
  for (let i = 0; i < this.rows; i++) {
882
891
  let row = {};
883
892
  for (let j = 0; j < this.cols; j++) {
884
- const { type, name, value } = this.value[j]; // column
893
+ const { type, name, value: values } = this.value[j]; // column
885
894
  switch (type) {
886
- case DdbType.bool:
887
- row[name] = Boolean(value[i]);
895
+ case DdbType.bool: {
896
+ const value = values[i];
897
+ row[name] = value === exports.nulls.int8 ?
898
+ null
899
+ :
900
+ Boolean(value);
888
901
  break;
902
+ }
889
903
  case DdbType.ipaddr:
890
- row[name] = value.subarray(16 * i, 16 * (i + 1));
904
+ row[name] = values.subarray(16 * i, 16 * (i + 1));
891
905
  break;
892
906
  case DdbType.symbol_extended: {
893
- const { base, data } = value;
907
+ const { base, data } = values;
894
908
  row[name] = base[data[i]];
895
909
  break;
896
910
  }
897
911
  default:
898
- row[name] = value[i];
912
+ row[name] = values[i];
899
913
  }
900
914
  }
901
915
  rows[i] = row;
@@ -949,7 +963,10 @@ class DdbChar extends DdbObj {
949
963
  form: DdbForm.scalar,
950
964
  type: DdbType.char,
951
965
  length: 1,
952
- value,
966
+ value: typeof value === 'string' ?
967
+ value.charCodeAt(0)
968
+ :
969
+ value
953
970
  });
954
971
  }
955
972
  }
@@ -1330,7 +1347,7 @@ class DDB {
1330
1347
  @example
1331
1348
  let ddb = new DDB('ws://127.0.0.1:8848')
1332
1349
 
1333
- // 使用 HTTPS 加密
1350
+ // Encrypt with HTTPS
1334
1351
  let ddbsecure = new DDB('wss://dolphindb.com', {
1335
1352
  autologin: true,
1336
1353
  username: 'admin',
@@ -1355,8 +1372,13 @@ class DDB {
1355
1372
  this.password = '123456';
1356
1373
  /** python session flag (2048) */
1357
1374
  this.python = false;
1358
- this.message_hook = null;
1359
- this.presult = Promise.resolve(null);
1375
+ this.print_message_buffer = false;
1376
+ this.print_object_buffer = false;
1377
+ this.print_message = true;
1378
+ this.parse_object = true;
1379
+ /** DdbMessage listeners */
1380
+ this.listeners = [];
1381
+ this.promise = Promise.resolve(null);
1360
1382
  if (!url) {
1361
1383
  const _url = new URL(location.href);
1362
1384
  const hostname = _url.searchParams.get('hostname') || location.hostname;
@@ -1373,16 +1395,10 @@ class DDB {
1373
1395
  if (options.python !== undefined)
1374
1396
  this.python = options.python;
1375
1397
  }
1376
- /** print message handler */
1377
- printer(message) {
1378
- console.log(message);
1379
- }
1380
- /** resolver, rejector, promise of last rpc */
1381
- presolver(buf) { }
1382
- prejector(error) { }
1383
1398
  get connected() {
1384
1399
  return this.websocket?.readyState === WebSocket.OPEN;
1385
1400
  }
1401
+ on_message(event) { }
1386
1402
  /** Establish the actual WebSocket connection to the DolphinDB corresponding to the URL
1387
1403
  - options?:
1388
1404
  - url?: DolphinDB WebSocket URL. By default, the WebSocket URL passed in when the instance is initialized is used
@@ -1403,58 +1419,51 @@ class DDB {
1403
1419
  if (options.python !== undefined)
1404
1420
  this.python = options.python;
1405
1421
  this.disconnect();
1406
- let websocket = new WebSocket(this.url, this.python ? ['python'] : []);
1407
- // https://stackoverflow.com/questions/11821096/what-is-the-difference-between-an-arraybuffer-and-a-blob/39951543
1408
- websocket.binaryType = 'arraybuffer';
1409
- this.websocket = websocket;
1410
- await new Promise((resolve, reject) => {
1411
- websocket.addEventListener('open', async (event) => {
1412
- console.log(`${websocket.url} opened`);
1422
+ await (0, net_browser_js_1.connect_websocket)(this.url, {
1423
+ protocols: this.python ? ['python'] : [],
1424
+ on_open: async (event, websocket) => {
1425
+ this.websocket = websocket;
1413
1426
  await this.rpc('connect', {});
1414
- resolve();
1415
- });
1416
- websocket.addEventListener('close', event => {
1417
- console.log(`${websocket.url} closed with code = ${event.code}, reason = '${event.reason}'`);
1418
- });
1419
- websocket.addEventListener('error', event => {
1420
- const message = `${websocket.url} errored`;
1421
- console.error(message, event);
1422
- reject(Object.assign(new Error(message), { event }));
1423
- });
1424
- websocket.addEventListener('message', event => {
1425
- const buf = new Uint8Array(event.data);
1426
- if (this.message_hook)
1427
- this.message_hook(buf);
1428
- try {
1429
- const { type, data } = this.parse_message(buf);
1430
- if (type === 'message') {
1431
- this.printer(data);
1432
- return;
1433
- }
1434
- this.presolver(data);
1435
- }
1436
- catch (error) {
1437
- this.prejector(error);
1438
- }
1439
- });
1427
+ },
1428
+ on_message: event => {
1429
+ this.on_message(event);
1430
+ }
1440
1431
  });
1441
- if (!this.python)
1442
- await this.eval('def pnode_run (nodes, func_name, args, add_node_alias = true) {\n' +
1432
+ await this.eval(this.python ?
1433
+ 'def pnode_run (nodes, func_name, args, add_node_alias):\n' +
1443
1434
  ' nargs = size(args)\n' +
1444
1435
  ' func = funcByName(func_name)\n' +
1445
1436
  ' \n' +
1446
- ' if (!nargs)\n' +
1437
+ ' if not nargs:\n' +
1447
1438
  ' return pnodeRun(func, nodes, add_node_alias)\n' +
1448
1439
  ' \n' +
1449
- ' args_partial = array(any, 1 + nargs, 1 + nargs)\n' +
1450
- ' args_partial[0] = func\n' +
1451
- ' args_partial[1:] = args\n' +
1440
+ ' args_partial = [ ]\n' +
1441
+ ' args_partial.append(func)\n' +
1442
+ ' for a in args:\n' +
1443
+ ' args_partial.append(a)\n' +
1444
+ ' \n' +
1452
1445
  ' return pnodeRun(\n' +
1453
1446
  ' unifiedCall(partial, args_partial),\n' +
1454
1447
  ' nodes,\n' +
1455
1448
  ' add_node_alias\n' +
1456
- ' )\n' +
1457
- '}\n', { urgent: true });
1449
+ ' )\n'
1450
+ :
1451
+ 'def pnode_run (nodes, func_name, args, add_node_alias = true) {\n' +
1452
+ ' nargs = size(args)\n' +
1453
+ ' func = funcByName(func_name)\n' +
1454
+ ' \n' +
1455
+ ' if (!nargs)\n' +
1456
+ ' return pnodeRun(func, nodes, add_node_alias)\n' +
1457
+ ' \n' +
1458
+ ' args_partial = array(any, 1 + nargs, 1 + nargs)\n' +
1459
+ ' args_partial[0] = func\n' +
1460
+ ' args_partial[1:] = args\n' +
1461
+ ' return pnodeRun(\n' +
1462
+ ' unifiedCall(partial, args_partial),\n' +
1463
+ ' nodes,\n' +
1464
+ ' add_node_alias\n' +
1465
+ ' )\n' +
1466
+ '}\n', { urgent: true });
1458
1467
  if (this.autologin)
1459
1468
  if (this.python)
1460
1469
  await this.eval(`login(${this.username.quote('double')}, ${this.password.quote('double')})`, { urgent: true });
@@ -1490,47 +1499,69 @@ class DDB {
1490
1499
  limit,
1491
1500
  ] : [],
1492
1501
  ];
1493
- return `/ ${options.join('_')}`;
1502
+ return options.join('_');
1494
1503
  }
1495
1504
  disconnect() {
1496
1505
  if (this.connected)
1497
1506
  this.websocket.close(1000);
1498
- this.presolver = buf => { };
1499
- this.prejector = error => { };
1500
- this.presult = Promise.resolve(null);
1507
+ this.on_message = () => { };
1508
+ this.promise = Promise.resolve(null);
1501
1509
  }
1502
1510
  /** rpc through websocket (function/script/variable command)
1503
1511
  - type: API 类型: 'script' | 'function' | 'variable'
1504
1512
  - options:
1505
1513
  - urgent?: 决定 `行为标识` 那一行字符串的取值(只适用于 script 和 function)
1506
1514
  - vars?: type === 'variable' 时必传,variable 指令中待上传的变量名
1515
+ - listener?: 处理本次 rpc 期间的消息 (DdbMessage)
1516
+ - parse_object?: 在本次 rpc 期间设置 parse_object, 结束后恢复原有
1517
+ 为 false 时返回的 DdbObj 仅含有 buffer 和 le,不做解析,以便后续转发、序列化
1507
1518
  */
1508
- async rpc(type, { script, func, args = [], vars = [], urgent, }) {
1519
+ async rpc(type, { script, func, args = [], vars = [], urgent, listener, parse_object, }) {
1509
1520
  if (!this.websocket)
1510
1521
  await this.connect();
1511
1522
  if (!this.connected)
1512
1523
  throw new Error(`${this.url} is already disconnected`);
1524
+ // this 上的当前配置需要在 message 到达后使用,先保存起来
1525
+ const _handlers = [...this.listeners].reverse();
1513
1526
  // 临界区:保证多个 rpc 并发时形成 promise 链
1514
1527
  // ddb 世界观:需要等待上一个 rpc 结果从 server 返回之后才能发起下一个调用
1515
1528
  // 违反世界观可能造成:
1516
1529
  // 1. 并发多个请求只返回第一个结果(阻塞,需后续请求疏通)
1517
1530
  // 2. windows 下 ddb server 返回多个相同的结果
1518
- const ptail = this.presult;
1519
- let presolver;
1520
- let prejector;
1521
- const presult = this.presult = new Promise((resolve, reject) => {
1522
- presolver = resolve;
1523
- prejector = reject;
1531
+ const ptail = this.promise;
1532
+ let resolve;
1533
+ let reject;
1534
+ const promise = this.promise = new Promise((_resolve, _reject) => {
1535
+ resolve = _resolve;
1536
+ reject = _reject;
1524
1537
  });
1525
1538
  try {
1526
1539
  await ptail;
1527
1540
  }
1528
1541
  catch { }
1529
- this.presolver = presolver;
1530
- this.prejector = prejector;
1531
- // 临界区结束,只有一个 rpc 请求可以写 WebSocket
1532
- if (urgent && type !== 'script' && type !== 'function')
1533
- throw new Error('urgent 只适用于 script 和 funciton');
1542
+ // 临界区结束,只有一个 rpc 函数调用运行到这里,可以独占 this.on_message 然后写 WebSocket
1543
+ this.on_message = ({ data: buffer }) => {
1544
+ const buf = new Uint8Array(buffer);
1545
+ if (this.print_message_buffer)
1546
+ console.log(buf);
1547
+ const message = this.parse_message(buf, parse_object);
1548
+ listener?.(message, this);
1549
+ for (const listener of _handlers)
1550
+ listener(message, this);
1551
+ const { type, data } = message;
1552
+ switch (type) {
1553
+ case 'print':
1554
+ if (this.print_message)
1555
+ console.log(data);
1556
+ return;
1557
+ case 'object':
1558
+ resolve(data);
1559
+ return;
1560
+ case 'error':
1561
+ reject(data);
1562
+ return;
1563
+ }
1564
+ };
1534
1565
  args = this.to_ddbobjs(args);
1535
1566
  const command = this.enc.encode((() => {
1536
1567
  switch (type) {
@@ -1551,36 +1582,40 @@ class DDB {
1551
1582
  return 'connect\n';
1552
1583
  }
1553
1584
  })());
1554
- const message = (0, utils_browser_1.concat)([
1555
- this.enc.encode(`API2 ${this.sid} ${command.length}${this.get_rpc_options({ urgent })}\n`),
1585
+ this.websocket.send((0, utils_browser_1.concat)([
1586
+ this.enc.encode(`API2 ${this.sid} ${command.length} / ${this.get_rpc_options({ urgent })}\n`),
1556
1587
  command,
1557
1588
  ...args.map((arg) => arg.pack())
1558
- ]);
1559
- this.websocket.send(message);
1560
- return DdbObj.parse(await presult, // data_buf
1561
- this.le);
1589
+ ]));
1590
+ return promise;
1562
1591
  }
1563
- /** eval script through websocket (script command) */
1564
- async eval(
1565
- /** 执行的脚本 */
1566
- script,
1567
- /** 执行选项 */
1568
- { urgent } = {}) {
1569
- return this.rpc('script', { script, urgent });
1592
+ /** eval script through websocket (script command)
1593
+ - script?: Script to execute
1594
+ - options?: execution options
1595
+ - urgent?: Urgent flag to ensure that submitted scripts are processed by urgent workers to prevent being blocked by other jobs
1596
+ - listener?: Process messages during this rpc (DdbMessage)
1597
+ - parse_object?: Set parse_object during this rpc, and restore the original after the end.
1598
+ When it is false, the returned DdbObj only contains buffer and le without parsing,
1599
+ so as to facilitate subsequent forwarding and serialization
1600
+ */
1601
+ async eval(script, { urgent, listener, parse_object, } = {}) {
1602
+ return this.rpc('script', { script, urgent, listener, parse_object });
1570
1603
  }
1571
1604
  /** call function through websocket (function command)
1572
- - func: 函数名
1573
- - args?: `[ ]` 调用参数 (传入的原生 string 和 boolean 会被自动转换为 DdbObj<string> 和 DdbObj<boolean>)
1574
- - options?: 调用选项
1575
- - urgent?: 紧急 flag。使用 urgent worker 执行,防止被其它作业阻塞
1576
- - node?: 设置结点 alias 时发送到集群中对应的结点执行 (使用 DolphinDB 中的 rpc 方法)
1577
- - nodes?: 设置多个结点 alias 时发送到集群中对应的多个结点执行 (使用 DolphinDB 中的 pnodeRun 方法)
1578
- - func_type?: 设置 node 参数时必传,需指定函数类型,其它情况下不传
1579
- - add_node_alias?: 设置 nodes 参数时选传,其它情况不传
1605
+ - func: function name
1606
+ - args?: `[ ]` Call parameters (the incoming native string and boolean will be automatically converted to DdbObj<string> and DdbObj<boolean>)
1607
+ - options?: call options
1608
+ - urgent?: Emergency flag. Use urgent worker execution to prevent being blocked by other jobs
1609
+ - 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)
1610
+ - nodes?: When setting multiple node aliases, send them to the corresponding multiple nodes in the cluster for execution (using the pnodeRun method in DolphinDB)
1611
+ - 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
1612
+ - add_node_alias?: Select to pass when setting the nodes parameter, otherwise not pass
1613
+ - listener?: Process messages during this rpc (DdbMessage)
1614
+ - parse_object?: Set parse_object during this rpc, and restore the original after the end.
1615
+ When it is false, the returned DdbObj only contains buffer and le without parsing,
1616
+ so as to facilitate subsequent forwarding and serialization
1580
1617
  */
1581
- async call(func, args = [], { urgent, node, nodes, func_type, add_node_alias } = {}) {
1582
- if (this.python)
1583
- throw new Error('call doesn\'t support python session yet');
1618
+ async call(func, args = [], { urgent, node, nodes, func_type, add_node_alias, listener, parse_object, } = {}) {
1584
1619
  if (node) {
1585
1620
  if (typeof func_type === 'undefined')
1586
1621
  throw new Error('指定 node 时必须设置 func_type');
@@ -1610,6 +1645,8 @@ class DDB {
1610
1645
  func,
1611
1646
  args,
1612
1647
  urgent,
1648
+ listener,
1649
+ parse_object
1613
1650
  });
1614
1651
  }
1615
1652
  /** upload variable through websocket (variable command) */
@@ -1617,19 +1654,19 @@ class DDB {
1617
1654
  /** Uploaded variables' name */
1618
1655
  vars,
1619
1656
  /** Uploaded variables' value */
1620
- args) {
1657
+ args, { listener, parse_object, } = {}) {
1621
1658
  if (!args.length || args.length !== vars.length)
1622
1659
  throw new Error('variable 指令参数为空或参数名为空,或数量不匹配');
1623
- return this.rpc('variable', { vars, args });
1660
+ return this.rpc('variable', { vars, args, listener, parse_object });
1624
1661
  }
1625
1662
  /** 解析服务端响应报文,返回去掉 header 的 data buf */
1626
- parse_message(buf) {
1663
+ parse_message(buf, parse_object = this.parse_object) {
1627
1664
  // MSG\n
1628
1665
  // <message>\0
1629
1666
  // 'M'.codePointAt(0).to_hex_str()
1630
1667
  if (buf[0] === 0x4d && buf[1] === 0x53 && buf[2] === 0x47 && buf[3] === 0x0a)
1631
1668
  return {
1632
- type: 'message',
1669
+ type: 'print',
1633
1670
  data: this.dec.decode(buf.subarray(4))
1634
1671
  };
1635
1672
  // '1166953221 1 1\n'
@@ -1653,10 +1690,25 @@ class DDB {
1653
1690
  /** 'OK' 表示成功,其它文本表示失败 */
1654
1691
  const message = this.dec.decode(buf.subarray(i_ls_1, i_lf_1));
1655
1692
  if (message !== 'OK')
1656
- throw new Error(message);
1693
+ return {
1694
+ type: 'error',
1695
+ data: new Error(message)
1696
+ };
1697
+ const buf_obj = buf.subarray(i_lf_1 + 1);
1698
+ if (this.print_object_buffer)
1699
+ console.log(buf_obj);
1657
1700
  return {
1658
1701
  type: 'object',
1659
- data: buf.subarray(i_lf_1 + 1)
1702
+ data: parse_object ?
1703
+ DdbObj.parse(buf_obj, this.le)
1704
+ :
1705
+ new DdbObj({
1706
+ form: DdbForm.scalar,
1707
+ type: DdbType.void,
1708
+ length: 0,
1709
+ le: this.le,
1710
+ buffer: buf_obj,
1711
+ })
1660
1712
  };
1661
1713
  }
1662
1714
  /** 自动转换 js string, boolean 为 DdbObj */