@naeemo/capnp 0.2.0 → 0.4.0

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/README.md CHANGED
@@ -1,8 +1,11 @@
1
1
  # Cap'n Proto TypeScript
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/@naeemo/capnp.svg)](https://www.npmjs.com/package/@naeemo/capnp)
4
+ [![Documentation](https://img.shields.io/badge/docs-website-blue.svg)](https://naeemo.github.io/capnp/)
5
+
3
6
  A pure TypeScript implementation of Cap'n Proto, featuring zero-copy deserialization and full interoperability with the official C++ implementation.
4
7
 
5
- [中文文档](./README.zh.md)
8
+ [📖 Documentation](https://naeemo.github.io/capnp/) | [中文文档](./README.zh.md)
6
9
 
7
10
  ## Features
8
11
 
@@ -11,6 +14,7 @@ A pure TypeScript implementation of Cap'n Proto, featuring zero-copy deserializa
11
14
  - ✅ **Official Compatible** - Tested against official C++ implementation
12
15
  - 📦 **Schema Code Generation** - Generate TypeScript types from `.capnp` schemas
13
16
  - ⚡ **High Performance** - 1.4μs serialization, 0.6μs deserialization for simple structs
17
+ - 🌐 **Full RPC Stack** - Complete Level 0-4 RPC implementation (Bootstrap, Pipelining, SturdyRefs, 3-way Handoff, Join)
14
18
 
15
19
  ## Installation
16
20
 
@@ -92,6 +96,7 @@ See [PERFORMANCE.md](./PERFORMANCE.md) for detailed benchmarks.
92
96
 
93
97
  ## Documentation
94
98
 
99
+ - [📖 Documentation Website](https://naeemo.github.io/capnp/)
95
100
  - [API Documentation](./docs/API.md)
96
101
  - [Performance Report](./PERFORMANCE.md)
97
102
  - [Test Coverage](./TEST_COVERAGE.md)
package/README.zh.md CHANGED
@@ -1,8 +1,11 @@
1
1
  # Cap'n Proto TypeScript
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/@naeemo/capnp.svg)](https://www.npmjs.com/package/@naeemo/capnp)
4
+ [![Documentation](https://img.shields.io/badge/docs-website-blue.svg)](https://naeemo.github.io/capnp/)
5
+
3
6
  纯 TypeScript 实现的 Cap'n Proto,支持零拷贝反序列化,与官方 C++ 实现完全兼容。
4
7
 
5
- [English Documentation](./README.md)
8
+ [📖 文档网站](https://naeemo.github.io/capnp/) | [English Documentation](./README.md)
6
9
 
7
10
  ## 特性
8
11
 
@@ -72,6 +75,7 @@ npx capnp-ts-codegen schema.capnp -o types.ts -r ../my-runtime
72
75
 
73
76
  ## 文档
74
77
 
78
+ - [📖 文档网站](https://naeemo.github.io/capnp/)
75
79
  - [API 文档](./docs/API.md)
76
80
  - [性能报告](./PERFORMANCE.md)
77
81
  - [测试覆盖](./TEST_COVERAGE.md)
@@ -138,6 +138,12 @@ var Segment = class Segment {
138
138
  return new Uint8Array(this.buffer, 0, this._size);
139
139
  }
140
140
  /**
141
+ * 获取底层 ArrayBuffer
142
+ */
143
+ getArrayBuffer() {
144
+ return this.buffer;
145
+ }
146
+ /**
141
147
  * 获取字数量
142
148
  */
143
149
  get wordCount() {
@@ -586,6 +592,22 @@ var NodeReader = class {
586
592
  }
587
593
  return enumerants;
588
594
  }
595
+ get interfaceMethods() {
596
+ const listReader = this.reader.getList(3, ElementSize.INLINE_COMPOSITE, {
597
+ dataWords: 5,
598
+ pointerCount: 2
599
+ });
600
+ if (!listReader) return [];
601
+ const methods = [];
602
+ for (let i = 0; i < listReader.length; i++) try {
603
+ const method = new MethodReader(listReader.getStruct(i));
604
+ if (!method.name && i > 0) break;
605
+ methods.push(method);
606
+ } catch (_e) {
607
+ break;
608
+ }
609
+ return methods;
610
+ }
589
611
  get nestedNodes() {
590
612
  const listReader = this.reader.getList(1, ElementSize.INLINE_COMPOSITE, {
591
613
  dataWords: 1,
@@ -801,6 +823,23 @@ var ValueReader = class {
801
823
  if (this.isBool) return this.boolValue;
802
824
  }
803
825
  };
826
+ var MethodReader = class {
827
+ constructor(reader) {
828
+ this.reader = reader;
829
+ }
830
+ get name() {
831
+ return this.reader.getText(0);
832
+ }
833
+ get codeOrder() {
834
+ return this.reader.getUint16(0);
835
+ }
836
+ get paramStructType() {
837
+ return this.reader.getUint64(8);
838
+ }
839
+ get resultStructType() {
840
+ return this.reader.getUint64(16);
841
+ }
842
+ };
804
843
  var EnumerantReader = class {
805
844
  constructor(reader) {
806
845
  this.reader = reader;
@@ -924,10 +963,21 @@ function generateFile(fileId, allNodes, options) {
924
963
  lines.push(" return view.getFloat64(0, true);");
925
964
  lines.push("}");
926
965
  lines.push("");
927
- const fileNodes = allNodes.filter((n) => n.scopeId === fileId);
966
+ let filePrefix = "";
967
+ try {
968
+ filePrefix = allNodes.find((n) => n.id === fileId)?.displayName || "";
969
+ } catch (_e) {}
970
+ const fileNodes = allNodes.filter((n) => {
971
+ try {
972
+ if (n.scopeId === fileId) return true;
973
+ if (n.scopeId === 0n && filePrefix && n.displayName.startsWith(`${filePrefix}:`)) return n.displayName.substring(filePrefix.length + 1).includes("$");
974
+ } catch (_e) {}
975
+ return false;
976
+ });
928
977
  for (const node of fileNodes) {
929
978
  if (node.isStruct) lines.push(generateStruct(node, allNodes));
930
979
  else if (node.isEnum) lines.push(generateEnum(node));
980
+ else if (node.isInterface) lines.push(generateInterface(node, allNodes));
931
981
  lines.push("");
932
982
  }
933
983
  return lines.join("\n");
@@ -938,7 +988,28 @@ function generateFile(fileId, allNodes, options) {
938
988
  function generateStruct(node, allNodes) {
939
989
  const lines = [];
940
990
  const structName = getShortName(node.displayName);
941
- const { unionGroups, regularFields, groupFields } = analyzeFields(node, allNodes);
991
+ let unionGroups = [];
992
+ let regularFields = [];
993
+ let groupFields = [];
994
+ try {
995
+ const analysis = analyzeFields(node, allNodes);
996
+ unionGroups = analysis.unionGroups;
997
+ regularFields = analysis.regularFields;
998
+ groupFields = analysis.groupFields;
999
+ } catch (_e) {
1000
+ lines.push(`export interface ${structName} {`);
1001
+ lines.push(" // Note: Could not parse struct fields");
1002
+ lines.push("}");
1003
+ lines.push("");
1004
+ lines.push(`export class ${structName}Reader {`);
1005
+ lines.push(" constructor(private reader: StructReader) {}");
1006
+ lines.push("}");
1007
+ lines.push("");
1008
+ lines.push(`export class ${structName}Builder {`);
1009
+ lines.push(" constructor(private builder: StructBuilder) {}");
1010
+ lines.push("}");
1011
+ return lines.join("\n");
1012
+ }
942
1013
  lines.push(`export interface ${structName} {`);
943
1014
  for (const field of regularFields) {
944
1015
  if (!field.isSlot) continue;
@@ -947,11 +1018,13 @@ function generateStruct(node, allNodes) {
947
1018
  }
948
1019
  for (const { field: groupField, groupNode } of groupFields) {
949
1020
  const groupName = groupField.name;
950
- for (const field of groupNode.structFields) {
951
- if (!field.isSlot) continue;
952
- const tsType = getTypeScriptType(field.slotType, allNodes);
953
- lines.push(` ${groupName}${capitalize(field.name)}: ${tsType};`);
954
- }
1021
+ try {
1022
+ for (const field of groupNode.structFields) {
1023
+ if (!field.isSlot) continue;
1024
+ const tsType = getTypeScriptType(field.slotType, allNodes);
1025
+ lines.push(` ${groupName}${capitalize(field.name)}: ${tsType};`);
1026
+ }
1027
+ } catch (_e) {}
955
1028
  }
956
1029
  for (const [groupIndex, group] of unionGroups.entries()) {
957
1030
  const unionName = group.fields.length > 0 ? `${capitalize(group.fields[0].name)}Union` : `Union${groupIndex}`;
@@ -970,12 +1043,14 @@ function generateStruct(node, allNodes) {
970
1043
  }
971
1044
  for (const { field: groupField, groupNode } of groupFields) {
972
1045
  const groupName = groupField.name;
973
- for (const field of groupNode.structFields) {
974
- if (!field.isSlot) continue;
975
- const getter = generateGroupFieldGetter(field, groupName);
976
- lines.push(` ${getter}`);
977
- lines.push("");
978
- }
1046
+ try {
1047
+ for (const field of groupNode.structFields) {
1048
+ if (!field.isSlot) continue;
1049
+ const getter = generateGroupFieldGetter(field, groupName);
1050
+ lines.push(` ${getter}`);
1051
+ lines.push("");
1052
+ }
1053
+ } catch (_e) {}
979
1054
  }
980
1055
  for (const [groupIndex, group] of unionGroups.entries()) {
981
1056
  const unionName = group.fields.length > 0 ? `${capitalize(group.fields[0].name)}Union` : `Union${groupIndex}`;
@@ -1016,12 +1091,14 @@ function generateStruct(node, allNodes) {
1016
1091
  }
1017
1092
  for (const { field: groupField, groupNode } of groupFields) {
1018
1093
  const groupName = groupField.name;
1019
- for (const field of groupNode.structFields) {
1020
- if (!field.isSlot) continue;
1021
- const setter = generateGroupFieldSetter(field, groupName);
1022
- lines.push(` ${setter}`);
1023
- lines.push("");
1024
- }
1094
+ try {
1095
+ for (const field of groupNode.structFields) {
1096
+ if (!field.isSlot) continue;
1097
+ const setter = generateGroupFieldSetter(field, groupName);
1098
+ lines.push(` ${setter}`);
1099
+ lines.push("");
1100
+ }
1101
+ } catch (_e) {}
1025
1102
  }
1026
1103
  for (const [groupIndex, group] of unionGroups.entries()) {
1027
1104
  const unionName = group.fields.length > 0 ? `${capitalize(group.fields[0].name)}Union` : `Union${groupIndex}`;
@@ -1512,11 +1589,114 @@ function getTypeScriptType(type, allNodes) {
1512
1589
  /**
1513
1590
  * 从 displayName 获取短名称
1514
1591
  * 例如 "test-schema.capnp:Person" -> "Person"
1592
+ * 处理 auto-generated 名称如 "Calculator.evaluate$Params" -> "EvaluateParams"
1515
1593
  */
1516
1594
  function getShortName(displayName) {
1595
+ if (!displayName) return "Unknown";
1517
1596
  const colonIndex = displayName.lastIndexOf(":");
1518
- if (colonIndex >= 0) return displayName.substring(colonIndex + 1);
1519
- return displayName.replace(/\.capnp$/, "").replace(/[^a-zA-Z0-9_]/g, "_");
1597
+ let name = colonIndex >= 0 ? displayName.substring(colonIndex + 1) : displayName;
1598
+ if (name.includes(".")) {
1599
+ const parts = name.split(".");
1600
+ if (parts.length === 2 && parts[1].includes("$")) {
1601
+ const methodPart = parts[1];
1602
+ const dollarIndex = methodPart.indexOf("$");
1603
+ if (dollarIndex > 0) {
1604
+ const methodName = methodPart.substring(0, dollarIndex);
1605
+ const suffix = methodPart.substring(dollarIndex + 1);
1606
+ name = capitalize(methodName) + suffix;
1607
+ } else name = capitalize(parts[1]);
1608
+ } else name = parts[parts.length - 1];
1609
+ }
1610
+ name = name.replace(/[^a-zA-Z0-9_]/g, "_");
1611
+ return name;
1612
+ }
1613
+ /**
1614
+ * 生成 Interface 的 TypeScript 代码
1615
+ * 包括:Method Constants、Client Class、Server Interface、Server Stub
1616
+ */
1617
+ function generateInterface(node, allNodes) {
1618
+ const lines = [];
1619
+ const interfaceName = getShortName(node.displayName);
1620
+ const methods = node.interfaceMethods;
1621
+ if (methods.length === 0) return `// Interface ${interfaceName} has no methods`;
1622
+ lines.push(`// ${interfaceName} Method IDs`);
1623
+ lines.push(`export const ${interfaceName}InterfaceId = ${node.id}n;`);
1624
+ lines.push(`export const ${interfaceName}MethodIds = {`);
1625
+ for (const method of methods) lines.push(` ${method.name}: ${method.codeOrder},`);
1626
+ lines.push("} as const;");
1627
+ lines.push("");
1628
+ lines.push(`// ${interfaceName} Server Interface`);
1629
+ lines.push(`export interface ${interfaceName}Server {`);
1630
+ for (const method of methods) {
1631
+ const paramType = getTypeNameById(method.paramStructType, allNodes, "unknown");
1632
+ const resultType = getTypeNameById(method.resultStructType, allNodes, "unknown");
1633
+ lines.push(` ${method.name}(context: CallContext<${paramType}Reader, ${resultType}Builder>): Promise<void> | void;`);
1634
+ }
1635
+ lines.push("}");
1636
+ lines.push("");
1637
+ lines.push(`// ${interfaceName} Server Stub`);
1638
+ lines.push(`export class ${interfaceName}Stub {`);
1639
+ lines.push(` private server: ${interfaceName}Server;`);
1640
+ lines.push("");
1641
+ lines.push(` constructor(server: ${interfaceName}Server) {`);
1642
+ lines.push(" this.server = server;");
1643
+ lines.push(" }");
1644
+ lines.push("");
1645
+ lines.push(` static readonly interfaceId = ${node.id}n;`);
1646
+ lines.push("");
1647
+ lines.push(" /** Dispatch a method call to the appropriate handler */");
1648
+ lines.push(" async dispatch(methodId: number, context: CallContext<unknown, unknown>): Promise<void> {");
1649
+ lines.push(" switch (methodId) {");
1650
+ for (const method of methods) {
1651
+ const paramType = getTypeNameById(method.paramStructType, allNodes, "unknown");
1652
+ const resultType = getTypeNameById(method.resultStructType, allNodes, "unknown");
1653
+ lines.push(` case ${interfaceName}MethodIds.${method.name}:`);
1654
+ lines.push(` return this.server.${method.name}(context as CallContext<${paramType}Reader, ${resultType}Builder>);`);
1655
+ }
1656
+ lines.push(" default:");
1657
+ lines.push(" throw new Error(`Unknown method ID: ${methodId}`);");
1658
+ lines.push(" }");
1659
+ lines.push(" }");
1660
+ lines.push("");
1661
+ lines.push(" /** Check if a method ID is valid */");
1662
+ lines.push(" isValidMethod(methodId: number): boolean {");
1663
+ lines.push(" return [");
1664
+ for (const method of methods) lines.push(` ${interfaceName}MethodIds.${method.name},`);
1665
+ lines.push(" ].includes(methodId);");
1666
+ lines.push(" }");
1667
+ lines.push("}");
1668
+ lines.push("");
1669
+ lines.push(`// ${interfaceName} Client Class`);
1670
+ lines.push(`export class ${interfaceName}Client extends BaseCapabilityClient {`);
1671
+ lines.push(` static readonly interfaceId = ${node.id}n;`);
1672
+ lines.push("");
1673
+ for (const method of methods) {
1674
+ const paramType = getTypeNameById(method.paramStructType, allNodes, "unknown");
1675
+ const resultType = getTypeNameById(method.resultStructType, allNodes, "unknown");
1676
+ lines.push(" /**");
1677
+ lines.push(` * ${method.name}`);
1678
+ lines.push(` * @param params - ${paramType}`);
1679
+ lines.push(` * @returns PipelineClient<${resultType}Reader>`);
1680
+ lines.push(" */");
1681
+ lines.push(` ${method.name}(params: ${paramType}Builder): PipelineClient<${resultType}Reader> {`);
1682
+ lines.push(" return this._call(");
1683
+ lines.push(` ${interfaceName}Client.interfaceId,`);
1684
+ lines.push(` ${interfaceName}MethodIds.${method.name},`);
1685
+ lines.push(" params");
1686
+ lines.push(` ) as PipelineClient<${resultType}Reader>;`);
1687
+ lines.push(" }");
1688
+ lines.push("");
1689
+ }
1690
+ lines.push("}");
1691
+ return lines.join("\n");
1692
+ }
1693
+ /**
1694
+ * 根据类型 ID 获取类型名称
1695
+ */
1696
+ function getTypeNameById(id, allNodes, fallback) {
1697
+ const node = allNodes.find((n) => n.id === id);
1698
+ if (!node) return fallback;
1699
+ return getShortName(node.displayName);
1520
1700
  }
1521
1701
 
1522
1702
  //#endregion
@@ -1586,11 +1766,11 @@ Features:
1586
1766
  - Group
1587
1767
  - Default values (XOR encoding)
1588
1768
  - Multi-segment messages
1769
+ - Interface (RPC client/server generation)
1589
1770
 
1590
1771
  Not yet supported:
1591
- - Interface
1592
1772
  - Const
1593
- - RPC
1773
+ - Advanced RPC features (Level 2-4)
1594
1774
  `);
1595
1775
  process.exit(0);
1596
1776
  }
@@ -1630,9 +1810,10 @@ async function main() {
1630
1810
  });
1631
1811
  console.error("Reading binary schema...");
1632
1812
  const buffer = readFileSync(binFile);
1633
- const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
1813
+ const arrayBuffer = new Uint8Array(buffer.byteLength);
1814
+ arrayBuffer.set(new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength));
1634
1815
  console.error("Generating TypeScript code...");
1635
- const files = generateFromRequest(CodeGeneratorRequestReader.fromBuffer(arrayBuffer), { runtimeImportPath: values.runtimePath || "@naeemo/capnp" });
1816
+ const files = generateFromRequest(CodeGeneratorRequestReader.fromBuffer(arrayBuffer.buffer), { runtimeImportPath: values.runtimePath || "@naeemo/capnp" });
1636
1817
  if (values.outDir) {
1637
1818
  mkdirSync(values.outDir, { recursive: true });
1638
1819
  for (const [filename, code] of files) {