@naeemo/capnp 0.2.0 → 0.3.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 +5 -1
- package/README.zh.md +5 -1
- package/dist/codegen/cli-v3.js +206 -25
- package/dist/codegen/cli-v3.js.map +1 -1
- package/dist/index.cjs +2204 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2176 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
# Cap'n Proto TypeScript
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@naeemo/capnp)
|
|
4
|
+
[](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
|
|
|
@@ -92,6 +95,7 @@ See [PERFORMANCE.md](./PERFORMANCE.md) for detailed benchmarks.
|
|
|
92
95
|
|
|
93
96
|
## Documentation
|
|
94
97
|
|
|
98
|
+
- [📖 Documentation Website](https://naeemo.github.io/capnp/)
|
|
95
99
|
- [API Documentation](./docs/API.md)
|
|
96
100
|
- [Performance Report](./PERFORMANCE.md)
|
|
97
101
|
- [Test Coverage](./TEST_COVERAGE.md)
|
package/README.zh.md
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
# Cap'n Proto TypeScript
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@naeemo/capnp)
|
|
4
|
+
[](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)
|
package/dist/codegen/cli-v3.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
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
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
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
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
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
|
-
|
|
1519
|
-
|
|
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 =
|
|
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) {
|