protobuf-fastdsl 0.1.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 +146 -0
- package/dist/index.d.ts +86 -0
- package/dist/index.js +1111 -0
- package/package.json +38 -0
- package/protobuf.d.ts +25 -0
package/README.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# protobuf-dsl
|
|
2
|
+
|
|
3
|
+
一个 Vite 插件,将 TypeScript protobuf 接口在构建时编译为**完全内联**、零依赖的编解码函数。
|
|
4
|
+
|
|
5
|
+
无 `.proto` 文件。无运行时库。无函数调用开销。只需 TypeScript 接口 → 确定化的二进制编解码代码。
|
|
6
|
+
|
|
7
|
+
## 特性
|
|
8
|
+
|
|
9
|
+
- **零运行时** — 所有 wire-format 逻辑在编译时内联
|
|
10
|
+
- **TypeScript 原生** — 用 `pb<N, Type>` 标记接口字段即可定义 schema
|
|
11
|
+
- **泛型单态化** — `Wrapper<Wrapper<string>>` → 自动生成具体编解码函数
|
|
12
|
+
- **重复字段** — `pb_repeated<N, Type>` 编译为 `Type[]`
|
|
13
|
+
- **编译期预计算 Tag** — 字段标签在编译时折叠为字面量字节
|
|
14
|
+
|
|
15
|
+
## 安装
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install protobuf-dsl
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 快速开始
|
|
22
|
+
|
|
23
|
+
**1. 在 `vite.config.ts` 中添加插件:**
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import protobufVite from 'protobuf-dsl';
|
|
27
|
+
|
|
28
|
+
export default defineConfig({
|
|
29
|
+
plugins: [protobufVite()],
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**2. 在 `tsconfig.json` 中添加类型声明:**
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"compilerOptions": {
|
|
38
|
+
"types": ["protobuf-dsl/types"]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**3. 用 TypeScript 接口定义 schema:**
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
interface UserProfile {
|
|
47
|
+
id: pb<1, uint_32>;
|
|
48
|
+
username: pb<2, string>;
|
|
49
|
+
active: pb<3, bool>;
|
|
50
|
+
tags: pb_repeated<4, string>;
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**4. 使用 `protobuf_encode` / `protobuf_decode`:**
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
const bytes = protobuf_encode<UserProfile>({
|
|
58
|
+
id: 42,
|
|
59
|
+
username: 'alice',
|
|
60
|
+
active: true,
|
|
61
|
+
tags: ['admin', 'dev'],
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const user = protobuf_decode<UserProfile>(bytes);
|
|
65
|
+
// user.id === 42, user.tags === ['admin', 'dev']
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
构建时,插件会将上述代码转换为:
|
|
69
|
+
|
|
70
|
+
```js
|
|
71
|
+
// 预计算的 tag 字面量,内联 varint 循环,零函数调用
|
|
72
|
+
const bytes = protobuf_encode_UserProfile({ id: 42, ... });
|
|
73
|
+
const user = protobuf_decode_UserProfile(bytes);
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## 泛型单态化
|
|
77
|
+
|
|
78
|
+
定义泛型 protobuf 模板,使用具体类型实例化:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
interface Wrapper<T> {
|
|
82
|
+
value?: pb<1, T>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 插件自动生成以下具体类型的编解码函数:
|
|
86
|
+
// Wrapper__string 和 Wrapper__Wrapper__string
|
|
87
|
+
const data = protobuf_encode<Wrapper<Wrapper<string>>>({
|
|
88
|
+
value: { value: 'hello' },
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## 支持的类型
|
|
93
|
+
|
|
94
|
+
| Protobuf 类型 | TypeScript 类型 | Wire 类型 |
|
|
95
|
+
|---------------|-----------------|-----------|
|
|
96
|
+
| `uint_32`, `int_32` | `number` | Varint |
|
|
97
|
+
| `uint_64`, `int_64` | `bigint` | Varint |
|
|
98
|
+
| `sint_32` | `number` | Varint |
|
|
99
|
+
| `sint_64` | `bigint` | Varint |
|
|
100
|
+
| `bool` | `boolean` | Varint |
|
|
101
|
+
| `string` | `string` | LengthDelimited |
|
|
102
|
+
| `bytes` | `Uint8Array` | LengthDelimited |
|
|
103
|
+
| `float`, `fixed_32`, `sfixed_32` | `number` | 32-bit |
|
|
104
|
+
| `double` | `number` | 64-bit |
|
|
105
|
+
| `fixed_64`, `sfixed_64` | `bigint` | 64-bit |
|
|
106
|
+
| 嵌套消息 | interface | LengthDelimited |
|
|
107
|
+
|
|
108
|
+
字段标记:
|
|
109
|
+
- `pb<fieldNumber, Type>` — 单值字段
|
|
110
|
+
- `pb_repeated<fieldNumber, Type>` — 重复字段(→ `Type[]`)
|
|
111
|
+
|
|
112
|
+
说明:
|
|
113
|
+
- 所有 64 位整数类型在 TypeScript 中统一映射为 `bigint`
|
|
114
|
+
|
|
115
|
+
## ⚡ 性能测试
|
|
116
|
+
|
|
117
|
+
benchmark 的 `.proto` 定义位于 `bench/proto/bench.proto`,生成入口是 `npm run bench:gen`。脚本会先校验所有实现产出的 wire bytes 完全一致,再统计绝对吞吐率。下表统一以 `protobuf-dsl = 1x` 为基线,其他实现显示相对它慢了多少。
|
|
118
|
+
|
|
119
|
+
参与对比的实现:
|
|
120
|
+
- `protobuf-ts(protoc)` — `@protobuf-ts/plugin + protoc` 生成代码
|
|
121
|
+
- `protobuf-ts` — 手写 `MessageType` 反射运行时
|
|
122
|
+
- `protobufjs(static)` — `pbjs static-module` 从同一份 `.proto` 生成代码
|
|
123
|
+
- `protobufjs` — 反射 API
|
|
124
|
+
- `protobuf` — `google-protobuf + protoc-gen-js` 生成代码
|
|
125
|
+
|
|
126
|
+
> Node v22.11.0 | Windows x64 | 每项测试 50 万次迭代
|
|
127
|
+
|
|
128
|
+
### 编码性能(ops/sec — 越高越好)
|
|
129
|
+
|
|
130
|
+
| 消息类型 | protobuf-dsl | protobuf-ts(protoc) | protobuf-ts | protobufjs(static) | protobufjs | protobuf |
|
|
131
|
+
|---------|:-----------:|:-------------------:|:-----------:|:------------------:|:----------:|:--------:|
|
|
132
|
+
| 简单消息(1 个字段) | **35,782,016 (1x)** | 8,238,128 (4.34x slower) | 5,828,158 (6.14x slower) | 16,466,923 (2.17x slower) | 16,819,500 (2.13x slower) | 5,794,260 (6.18x slower) |
|
|
133
|
+
| 多字段消息(3 个字段) | **11,361,700 (1x)** | 1,767,940 (6.43x slower) | 1,394,561 (8.15x slower) | 4,481,491 (2.54x slower) | 4,240,911 (2.68x slower) | 1,609,704 (7.06x slower) |
|
|
134
|
+
| 嵌套消息 | **21,264,923 (1x)** | 2,815,065 (7.55x slower) | 2,019,035 (10.53x slower) | 10,287,198 (2.07x slower) | 9,902,049 (2.15x slower) | 2,365,296 (8.99x slower) |
|
|
135
|
+
|
|
136
|
+
### 解码性能(ops/sec — 越高越好)
|
|
137
|
+
|
|
138
|
+
| 消息类型 | protobuf-dsl | protobuf-ts(protoc) | protobuf-ts | protobufjs(static) | protobufjs | protobuf |
|
|
139
|
+
|---------|:-----------:|:-------------------:|:-----------:|:------------------:|:----------:|:--------:|
|
|
140
|
+
| 简单消息(1 个字段) | **99,577,790 (1x)** | 7,586,436 (13.13x slower) | 9,032,575 (11.02x slower) | 27,215,772 (3.66x slower) | 19,002,736 (5.24x slower) | 11,497,372 (8.66x slower) |
|
|
141
|
+
| 多字段消息(3 个字段) | **10,190,107 (1x)** | 3,891,478 (2.62x slower) | 3,219,789 (3.16x slower) | 5,141,816 (1.98x slower) | 4,893,718 (2.08x slower) | 3,606,205 (2.83x slower) |
|
|
142
|
+
| 嵌套消息 | **51,491,185 (1x)** | 8,376,444 (6.15x slower) | 6,033,801 (8.53x slower) | 11,922,077 (4.32x slower) | 13,326,794 (3.86x slower) | 3,148,311 (16.36x slower) |
|
|
143
|
+
|
|
144
|
+
## 许可证
|
|
145
|
+
|
|
146
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
import ts from 'typescript';
|
|
3
|
+
|
|
4
|
+
declare enum WireType {
|
|
5
|
+
Varint = 0,
|
|
6
|
+
Bit64 = 1,
|
|
7
|
+
LengthDelim = 2,
|
|
8
|
+
Bit32 = 5
|
|
9
|
+
}
|
|
10
|
+
interface ProtobufField {
|
|
11
|
+
name: string;
|
|
12
|
+
fieldNumber: number;
|
|
13
|
+
typeName: string;
|
|
14
|
+
wireType: WireType;
|
|
15
|
+
isMessage: boolean;
|
|
16
|
+
isOptional: boolean;
|
|
17
|
+
isRepeated: boolean;
|
|
18
|
+
}
|
|
19
|
+
interface ProtobufMessage {
|
|
20
|
+
name: string;
|
|
21
|
+
fields: ProtobufField[];
|
|
22
|
+
}
|
|
23
|
+
type MessageRegistry = Map<string, ProtobufMessage>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Deterministic mangled name for a type node.
|
|
27
|
+
* `Foo` → `Foo`
|
|
28
|
+
* `Foo<string>` → `Foo__string`
|
|
29
|
+
* `Foo<Bar<uint_32>>` → `Foo__Bar__uint_32`
|
|
30
|
+
*/
|
|
31
|
+
declare function typeNodeToMangledName(typeNode: ts.TypeNode, sf: ts.SourceFile): string;
|
|
32
|
+
|
|
33
|
+
/** A recorded call-site for later replacement. */
|
|
34
|
+
interface CallSiteRecord {
|
|
35
|
+
fnName: string;
|
|
36
|
+
exprStart: number;
|
|
37
|
+
typeArgsEnd: number;
|
|
38
|
+
firstTypeArg: ts.TypeNode;
|
|
39
|
+
}
|
|
40
|
+
interface AnalysisResult {
|
|
41
|
+
registry: MessageRegistry;
|
|
42
|
+
callSites: CallSiteRecord[];
|
|
43
|
+
sourceFile: ts.SourceFile;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Analyze TypeScript source in a **single parse + single walk**.
|
|
47
|
+
*
|
|
48
|
+
* One walk handles both:
|
|
49
|
+
* - Collecting concrete + generic interfaces
|
|
50
|
+
* - Recording protobuf_encode/decode call-sites
|
|
51
|
+
*
|
|
52
|
+
* Then post-processes: monomorphize → resolve wire types → topo sort.
|
|
53
|
+
*/
|
|
54
|
+
declare function analyze(code: string, filePath: string): AnalysisResult;
|
|
55
|
+
/**
|
|
56
|
+
* Backward-compatible wrapper: returns only the MessageRegistry.
|
|
57
|
+
* Uses the same single-walk analysis internally.
|
|
58
|
+
*/
|
|
59
|
+
declare function analyzeSource(code: string, filePath: string): MessageRegistry;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Generate fully self-contained encode/decode source code.
|
|
63
|
+
* No runtime imports needed — all wire-format logic is inlined.
|
|
64
|
+
*/
|
|
65
|
+
declare function generateCode(registry: MessageRegistry): string;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Apply replacements using pre-recorded call-sites from analyze().
|
|
69
|
+
* No parsing or AST walking — just position-based string edits.
|
|
70
|
+
*/
|
|
71
|
+
declare function applyReplacements(code: string, sf: ts.SourceFile, callSites: CallSiteRecord[], registry: MessageRegistry): {
|
|
72
|
+
transformedCode: string;
|
|
73
|
+
hasReplacements: boolean;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Backward-compatible: parses + walks on its own.
|
|
77
|
+
* Prefer applyReplacements() with pre-recorded call-sites from analyze().
|
|
78
|
+
*/
|
|
79
|
+
declare function replaceCallSites(code: string, registry: MessageRegistry): {
|
|
80
|
+
transformedCode: string;
|
|
81
|
+
hasReplacements: boolean;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
declare function protobufVitePlugin(): Plugin;
|
|
85
|
+
|
|
86
|
+
export { analyze, analyzeSource, applyReplacements, protobufVitePlugin as default, generateCode, replaceCallSites, typeNodeToMangledName };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,1111 @@
|
|
|
1
|
+
// src/ast/analyzer.ts
|
|
2
|
+
import ts4 from "typescript";
|
|
3
|
+
|
|
4
|
+
// src/ast/types.ts
|
|
5
|
+
var PRIMITIVE_TYPE_MAP = {
|
|
6
|
+
uint_32: { wireType: 0 /* Varint */, defaultValue: "0" },
|
|
7
|
+
int_32: { wireType: 0 /* Varint */, defaultValue: "0" },
|
|
8
|
+
uint_64: { wireType: 0 /* Varint */, defaultValue: "0" },
|
|
9
|
+
int_64: { wireType: 0 /* Varint */, defaultValue: "0" },
|
|
10
|
+
sint_32: { wireType: 0 /* Varint */, defaultValue: "0" },
|
|
11
|
+
sint_64: { wireType: 0 /* Varint */, defaultValue: "0" },
|
|
12
|
+
bool: { wireType: 0 /* Varint */, defaultValue: "false" },
|
|
13
|
+
string: { wireType: 2 /* LengthDelim */, defaultValue: '""' },
|
|
14
|
+
bytes: { wireType: 2 /* LengthDelim */, defaultValue: "new Uint8Array(0)" },
|
|
15
|
+
float: { wireType: 5 /* Bit32 */, defaultValue: "0" },
|
|
16
|
+
double: { wireType: 1 /* Bit64 */, defaultValue: "0" },
|
|
17
|
+
fixed_32: { wireType: 5 /* Bit32 */, defaultValue: "0" },
|
|
18
|
+
fixed_64: { wireType: 1 /* Bit64 */, defaultValue: "0" },
|
|
19
|
+
sfixed_32: { wireType: 5 /* Bit32 */, defaultValue: "0" },
|
|
20
|
+
sfixed_64: { wireType: 1 /* Bit64 */, defaultValue: "0" }
|
|
21
|
+
};
|
|
22
|
+
var PB_MARKER = "pb";
|
|
23
|
+
var PB_REPEATED_MARKER = "pb_repeated";
|
|
24
|
+
|
|
25
|
+
// src/ast/collector.ts
|
|
26
|
+
import ts2 from "typescript";
|
|
27
|
+
|
|
28
|
+
// src/ast/utils.ts
|
|
29
|
+
import ts from "typescript";
|
|
30
|
+
var KEYWORD_TYPE_KINDS = /* @__PURE__ */ new Set([
|
|
31
|
+
ts.SyntaxKind.StringKeyword,
|
|
32
|
+
ts.SyntaxKind.NumberKeyword,
|
|
33
|
+
ts.SyntaxKind.BooleanKeyword,
|
|
34
|
+
ts.SyntaxKind.AnyKeyword,
|
|
35
|
+
ts.SyntaxKind.VoidKeyword,
|
|
36
|
+
ts.SyntaxKind.UndefinedKeyword,
|
|
37
|
+
ts.SyntaxKind.NullKeyword,
|
|
38
|
+
ts.SyntaxKind.NeverKeyword,
|
|
39
|
+
ts.SyntaxKind.UnknownKeyword,
|
|
40
|
+
ts.SyntaxKind.BigIntKeyword,
|
|
41
|
+
ts.SyntaxKind.SymbolKeyword,
|
|
42
|
+
ts.SyntaxKind.ObjectKeyword
|
|
43
|
+
]);
|
|
44
|
+
function isKeywordTypeNode(node) {
|
|
45
|
+
return KEYWORD_TYPE_KINDS.has(node.kind);
|
|
46
|
+
}
|
|
47
|
+
function typeNodeToMangledName(typeNode, sf) {
|
|
48
|
+
if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {
|
|
49
|
+
const base = typeNode.typeName.text;
|
|
50
|
+
if (typeNode.typeArguments && typeNode.typeArguments.length > 0) {
|
|
51
|
+
const args = typeNode.typeArguments.map((a) => typeNodeToMangledName(a, sf));
|
|
52
|
+
return base + "__" + args.join("__");
|
|
53
|
+
}
|
|
54
|
+
return base;
|
|
55
|
+
}
|
|
56
|
+
if (isKeywordTypeNode(typeNode)) return typeNode.getText(sf);
|
|
57
|
+
return typeNode.getText(sf);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/ast/collector.ts
|
|
61
|
+
function collectInterface(node, sf) {
|
|
62
|
+
const fields = [];
|
|
63
|
+
for (const m of node.members) {
|
|
64
|
+
if (!ts2.isPropertySignature(m) || !m.type) continue;
|
|
65
|
+
const f = extractField(m, sf);
|
|
66
|
+
if (f) fields.push(f);
|
|
67
|
+
}
|
|
68
|
+
return fields.length ? { name: node.name.text, fields } : null;
|
|
69
|
+
}
|
|
70
|
+
function collectGenericInterface(node, sf) {
|
|
71
|
+
const typeParams = node.typeParameters.map((p) => p.name.text);
|
|
72
|
+
const tpSet = new Set(typeParams);
|
|
73
|
+
const fields = [];
|
|
74
|
+
for (const m of node.members) {
|
|
75
|
+
if (!ts2.isPropertySignature(m) || !m.type) continue;
|
|
76
|
+
const f = extractGenericField(m, sf, tpSet);
|
|
77
|
+
if (f) fields.push(f);
|
|
78
|
+
}
|
|
79
|
+
return fields.length ? { name: node.name.text, typeParams, fields } : null;
|
|
80
|
+
}
|
|
81
|
+
function parsePbTypeRef(member) {
|
|
82
|
+
const t = member.type;
|
|
83
|
+
if (!ts2.isTypeReferenceNode(t) || !ts2.isIdentifier(t.typeName)) return null;
|
|
84
|
+
const marker = t.typeName.text;
|
|
85
|
+
if (marker !== PB_MARKER && marker !== PB_REPEATED_MARKER) return null;
|
|
86
|
+
const ta = t.typeArguments;
|
|
87
|
+
if (!ta || ta.length !== 2) return null;
|
|
88
|
+
const fnNode = ta[0];
|
|
89
|
+
if (!ts2.isLiteralTypeNode(fnNode) || !ts2.isNumericLiteral(fnNode.literal)) return null;
|
|
90
|
+
return { marker, fieldNumber: Number(fnNode.literal.text), typeArgNode: ta[1] };
|
|
91
|
+
}
|
|
92
|
+
function resolveTypeName(node, sf) {
|
|
93
|
+
if (ts2.isTypeReferenceNode(node) && ts2.isIdentifier(node.typeName)) return node.typeName.text;
|
|
94
|
+
if (isKeywordTypeNode(node)) return node.getText(sf);
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
function extractField(member, sf) {
|
|
98
|
+
const parsed = parsePbTypeRef(member);
|
|
99
|
+
if (!parsed) return null;
|
|
100
|
+
const typeName = resolveTypeName(parsed.typeArgNode, sf);
|
|
101
|
+
if (!typeName || !ts2.isIdentifier(member.name)) return null;
|
|
102
|
+
return {
|
|
103
|
+
name: member.name.text,
|
|
104
|
+
fieldNumber: parsed.fieldNumber,
|
|
105
|
+
typeName,
|
|
106
|
+
wireType: 0 /* Varint */,
|
|
107
|
+
// placeholder
|
|
108
|
+
isMessage: false,
|
|
109
|
+
// placeholder
|
|
110
|
+
isOptional: member.questionToken != null,
|
|
111
|
+
isRepeated: parsed.marker === PB_REPEATED_MARKER
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function extractGenericField(member, sf, tpSet) {
|
|
115
|
+
const parsed = parsePbTypeRef(member);
|
|
116
|
+
if (!parsed) return null;
|
|
117
|
+
const raw = resolveTypeName(parsed.typeArgNode, sf);
|
|
118
|
+
if (!raw || !ts2.isIdentifier(member.name)) return null;
|
|
119
|
+
return {
|
|
120
|
+
name: member.name.text,
|
|
121
|
+
fieldNumber: parsed.fieldNumber,
|
|
122
|
+
rawTypeName: raw,
|
|
123
|
+
isTypeParam: tpSet.has(raw),
|
|
124
|
+
isOptional: member.questionToken != null,
|
|
125
|
+
isRepeated: parsed.marker === PB_REPEATED_MARKER
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/ast/monomorphizer.ts
|
|
130
|
+
import ts3 from "typescript";
|
|
131
|
+
function monomorphizeTypeNode(typeNode, sf, templates, out) {
|
|
132
|
+
if (!ts3.isTypeReferenceNode(typeNode) || !ts3.isIdentifier(typeNode.typeName)) return null;
|
|
133
|
+
const baseName = typeNode.typeName.text;
|
|
134
|
+
const typeArgs = typeNode.typeArguments;
|
|
135
|
+
if (!typeArgs || typeArgs.length === 0) return baseName;
|
|
136
|
+
const tpl = templates.get(baseName);
|
|
137
|
+
if (!tpl || typeArgs.length !== tpl.typeParams.length) return null;
|
|
138
|
+
const mangledName = typeNodeToMangledName(typeNode, sf);
|
|
139
|
+
if (out.has(mangledName)) return mangledName;
|
|
140
|
+
const paramMap = /* @__PURE__ */ new Map();
|
|
141
|
+
for (let i = 0; i < tpl.typeParams.length; i++) {
|
|
142
|
+
const resolved = resolveTypeArg(typeArgs[i], sf, templates, out);
|
|
143
|
+
if (!resolved) return null;
|
|
144
|
+
paramMap.set(tpl.typeParams[i], resolved);
|
|
145
|
+
}
|
|
146
|
+
const fields = tpl.fields.map((f) => ({
|
|
147
|
+
name: f.name,
|
|
148
|
+
fieldNumber: f.fieldNumber,
|
|
149
|
+
typeName: f.isTypeParam ? paramMap.get(f.rawTypeName) ?? f.rawTypeName : f.rawTypeName,
|
|
150
|
+
wireType: 0 /* Varint */,
|
|
151
|
+
isMessage: false,
|
|
152
|
+
isOptional: f.isOptional,
|
|
153
|
+
isRepeated: f.isRepeated
|
|
154
|
+
}));
|
|
155
|
+
out.set(mangledName, { name: mangledName, fields });
|
|
156
|
+
return mangledName;
|
|
157
|
+
}
|
|
158
|
+
function resolveTypeArg(node, sf, templates, out) {
|
|
159
|
+
const mono = monomorphizeTypeNode(node, sf, templates, out);
|
|
160
|
+
if (mono) return mono;
|
|
161
|
+
if (isKeywordTypeNode(node)) return node.getText(sf);
|
|
162
|
+
if (ts3.isTypeReferenceNode(node) && ts3.isIdentifier(node.typeName) && !node.typeArguments) {
|
|
163
|
+
return node.typeName.text;
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/ast/analyzer.ts
|
|
169
|
+
function analyze(code, filePath) {
|
|
170
|
+
const sf = ts4.createSourceFile(filePath, code, ts4.ScriptTarget.Latest, true);
|
|
171
|
+
const concrete = [];
|
|
172
|
+
const templates = /* @__PURE__ */ new Map();
|
|
173
|
+
const mono = /* @__PURE__ */ new Map();
|
|
174
|
+
const callSites = [];
|
|
175
|
+
const deferredTypeArgs = [];
|
|
176
|
+
ts4.forEachChild(sf, function visit(node) {
|
|
177
|
+
if (ts4.isInterfaceDeclaration(node)) {
|
|
178
|
+
if (node.typeParameters?.length) {
|
|
179
|
+
const tpl = collectGenericInterface(node, sf);
|
|
180
|
+
if (tpl) templates.set(tpl.name, tpl);
|
|
181
|
+
} else {
|
|
182
|
+
const msg = collectInterface(node, sf);
|
|
183
|
+
if (msg) concrete.push(msg);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (ts4.isCallExpression(node)) {
|
|
187
|
+
const e = node.expression;
|
|
188
|
+
if (ts4.isIdentifier(e) && (e.text === "protobuf_encode" || e.text === "protobuf_decode")) {
|
|
189
|
+
const ta = node.typeArguments;
|
|
190
|
+
if (ta?.length) {
|
|
191
|
+
deferredTypeArgs.push(ta[0]);
|
|
192
|
+
callSites.push({
|
|
193
|
+
fnName: e.text,
|
|
194
|
+
exprStart: e.getStart(sf),
|
|
195
|
+
typeArgsEnd: ta.end + 1,
|
|
196
|
+
firstTypeArg: ta[0]
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
ts4.forEachChild(node, visit);
|
|
202
|
+
});
|
|
203
|
+
for (const typeArg of deferredTypeArgs) {
|
|
204
|
+
monomorphizeTypeNode(typeArg, sf, templates, mono);
|
|
205
|
+
}
|
|
206
|
+
for (const m of mono.values()) concrete.push(m);
|
|
207
|
+
const names = new Set(concrete.map((m) => m.name));
|
|
208
|
+
for (const msg of concrete) {
|
|
209
|
+
for (const f of msg.fields) {
|
|
210
|
+
const prim = PRIMITIVE_TYPE_MAP[f.typeName];
|
|
211
|
+
if (prim) {
|
|
212
|
+
f.wireType = prim.wireType;
|
|
213
|
+
f.isMessage = false;
|
|
214
|
+
} else if (names.has(f.typeName)) {
|
|
215
|
+
f.wireType = 2 /* LengthDelim */;
|
|
216
|
+
f.isMessage = true;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return { registry: topoSort(concrete), callSites, sourceFile: sf };
|
|
221
|
+
}
|
|
222
|
+
function analyzeSource(code, filePath) {
|
|
223
|
+
return analyze(code, filePath).registry;
|
|
224
|
+
}
|
|
225
|
+
function topoSort(messages) {
|
|
226
|
+
const map = new Map(messages.map((m) => [m.name, m]));
|
|
227
|
+
const deps = new Map(messages.map((m) => [
|
|
228
|
+
m.name,
|
|
229
|
+
new Set(m.fields.filter((f) => map.has(f.typeName)).map((f) => f.typeName))
|
|
230
|
+
]));
|
|
231
|
+
const sorted = [];
|
|
232
|
+
const visited = /* @__PURE__ */ new Set();
|
|
233
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
234
|
+
function dfs(name) {
|
|
235
|
+
if (visited.has(name)) return;
|
|
236
|
+
if (visiting.has(name)) throw new Error(`Circular dependency: ${name}`);
|
|
237
|
+
visiting.add(name);
|
|
238
|
+
for (const d of deps.get(name) || []) dfs(d);
|
|
239
|
+
visiting.delete(name);
|
|
240
|
+
visited.add(name);
|
|
241
|
+
sorted.push(map.get(name));
|
|
242
|
+
}
|
|
243
|
+
for (const m of messages) dfs(m.name);
|
|
244
|
+
return new Map(sorted.map((m) => [m.name, m]));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// src/codegen/encoder.ts
|
|
248
|
+
function computeTagBytes(fieldNumber, wireType) {
|
|
249
|
+
let value = (fieldNumber << 3 | wireType) >>> 0;
|
|
250
|
+
const bytes = [];
|
|
251
|
+
while (value > 127) {
|
|
252
|
+
bytes.push(value & 127 | 128);
|
|
253
|
+
value >>>= 7;
|
|
254
|
+
}
|
|
255
|
+
bytes.push(value & 127);
|
|
256
|
+
return bytes;
|
|
257
|
+
}
|
|
258
|
+
function writeTag(fieldNumber, wireType, ind) {
|
|
259
|
+
return computeTagBytes(fieldNumber, wireType).map((byte) => `${ind}buf[offset++] = ${byte};`).join("\n");
|
|
260
|
+
}
|
|
261
|
+
function varintSize(varName, ind) {
|
|
262
|
+
return `${ind}size += ${varName} < 0x80 ? 1 : ${varName} < 0x4000 ? 2 : ${varName} < 0x200000 ? 3 : ${varName} < 0x10000000 ? 4 : 5;`;
|
|
263
|
+
}
|
|
264
|
+
function writeVarint(expr, ind) {
|
|
265
|
+
return [
|
|
266
|
+
`${ind}let _v = ${expr};`,
|
|
267
|
+
`${ind}if (_v < 0x80) {`,
|
|
268
|
+
`${ind} buf[offset++] = _v;`,
|
|
269
|
+
`${ind}} else if (_v < 0x4000) {`,
|
|
270
|
+
`${ind} buf[offset++] = (_v & 0x7f) | 0x80;`,
|
|
271
|
+
`${ind} buf[offset++] = _v >>> 7;`,
|
|
272
|
+
`${ind}} else if (_v < 0x200000) {`,
|
|
273
|
+
`${ind} buf[offset++] = (_v & 0x7f) | 0x80;`,
|
|
274
|
+
`${ind} buf[offset++] = ((_v >>> 7) & 0x7f) | 0x80;`,
|
|
275
|
+
`${ind} buf[offset++] = _v >>> 14;`,
|
|
276
|
+
`${ind}} else if (_v < 0x10000000) {`,
|
|
277
|
+
`${ind} buf[offset++] = (_v & 0x7f) | 0x80;`,
|
|
278
|
+
`${ind} buf[offset++] = ((_v >>> 7) & 0x7f) | 0x80;`,
|
|
279
|
+
`${ind} buf[offset++] = ((_v >>> 14) & 0x7f) | 0x80;`,
|
|
280
|
+
`${ind} buf[offset++] = _v >>> 21;`,
|
|
281
|
+
`${ind}} else {`,
|
|
282
|
+
`${ind} buf[offset++] = (_v & 0x7f) | 0x80;`,
|
|
283
|
+
`${ind} buf[offset++] = ((_v >>> 7) & 0x7f) | 0x80;`,
|
|
284
|
+
`${ind} buf[offset++] = ((_v >>> 14) & 0x7f) | 0x80;`,
|
|
285
|
+
`${ind} buf[offset++] = ((_v >>> 21) & 0x7f) | 0x80;`,
|
|
286
|
+
`${ind} buf[offset++] = _v >>> 28;`,
|
|
287
|
+
`${ind}}`
|
|
288
|
+
].join("\n");
|
|
289
|
+
}
|
|
290
|
+
function isVarint64(typeName) {
|
|
291
|
+
return typeName === "uint_64" || typeName === "int_64" || typeName === "sint_64";
|
|
292
|
+
}
|
|
293
|
+
function isFixed64BigInt(typeName) {
|
|
294
|
+
return typeName === "fixed_64" || typeName === "sfixed_64";
|
|
295
|
+
}
|
|
296
|
+
function bigintVarintExpr(typeName, expr) {
|
|
297
|
+
if (typeName === "uint_64") return `BigInt.asUintN(64, ${expr})`;
|
|
298
|
+
if (typeName === "int_64") return `BigInt.asUintN(64, ${expr})`;
|
|
299
|
+
return `__zigZagEncode64(${expr})`;
|
|
300
|
+
}
|
|
301
|
+
function generateEncoder(msg, _registry) {
|
|
302
|
+
const blocks = msg.fields.map((field, index) => field.isRepeated ? buildRepeatedBlock(field, index) : buildSingularBlock(field, index));
|
|
303
|
+
const L = [
|
|
304
|
+
`function protobuf_encode_${msg.name}(obj) {`,
|
|
305
|
+
` let size = 0;`,
|
|
306
|
+
...blocks.flatMap((block) => block.declare),
|
|
307
|
+
...blocks.flatMap((block) => block.size),
|
|
308
|
+
` const buf = new Uint8Array(size);`,
|
|
309
|
+
` let offset = 0;`,
|
|
310
|
+
...blocks.flatMap((block) => block.write),
|
|
311
|
+
` return buf;`,
|
|
312
|
+
`}`
|
|
313
|
+
];
|
|
314
|
+
return L.join("\n");
|
|
315
|
+
}
|
|
316
|
+
function buildSingularBlock(field, index) {
|
|
317
|
+
const { name, fieldNumber, typeName, wireType, isMessage } = field;
|
|
318
|
+
const valueVar = `_f${index}`;
|
|
319
|
+
const cacheVar = `_c${index}`;
|
|
320
|
+
const tagLength = computeTagBytes(fieldNumber, isMessage || typeName === "string" || typeName === "bytes" ? 2 : wireType).length;
|
|
321
|
+
if (isMessage) {
|
|
322
|
+
return {
|
|
323
|
+
declare: [` const ${valueVar} = obj.${name};`, ` let ${cacheVar};`],
|
|
324
|
+
size: [
|
|
325
|
+
` if (${valueVar} != null) {`,
|
|
326
|
+
` ${cacheVar} = protobuf_encode_${typeName}(${valueVar});`,
|
|
327
|
+
` const _len = ${cacheVar}.length;`,
|
|
328
|
+
` size += ${tagLength};`,
|
|
329
|
+
varintSize("_len", " "),
|
|
330
|
+
` size += _len;`,
|
|
331
|
+
` }`
|
|
332
|
+
],
|
|
333
|
+
write: [
|
|
334
|
+
` if (${valueVar} != null) {`,
|
|
335
|
+
` const _data = ${cacheVar};`,
|
|
336
|
+
writeTag(fieldNumber, 2, " "),
|
|
337
|
+
` const _len = _data.length;`,
|
|
338
|
+
writeVarint("_len", " "),
|
|
339
|
+
` buf.set(_data, offset);`,
|
|
340
|
+
` offset += _len;`,
|
|
341
|
+
` }`
|
|
342
|
+
]
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
if (typeName === "string") {
|
|
346
|
+
return {
|
|
347
|
+
declare: [` const ${valueVar} = obj.${name};`, ` let ${cacheVar};`],
|
|
348
|
+
size: [
|
|
349
|
+
` if (${valueVar} != null && ${valueVar} !== "") {`,
|
|
350
|
+
` ${cacheVar} = __utf8Len(${valueVar});`,
|
|
351
|
+
` const _len = ${cacheVar};`,
|
|
352
|
+
` size += ${tagLength};`,
|
|
353
|
+
varintSize("_len", " "),
|
|
354
|
+
` size += _len;`,
|
|
355
|
+
` }`
|
|
356
|
+
],
|
|
357
|
+
write: [
|
|
358
|
+
` if (${valueVar} != null && ${valueVar} !== "") {`,
|
|
359
|
+
writeTag(fieldNumber, 2, " "),
|
|
360
|
+
` const _len = ${cacheVar};`,
|
|
361
|
+
writeVarint("_len", " "),
|
|
362
|
+
` offset = __utf8Write(buf, offset, ${valueVar});`,
|
|
363
|
+
` }`
|
|
364
|
+
]
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
if (typeName === "bytes") {
|
|
368
|
+
return {
|
|
369
|
+
declare: [` const ${valueVar} = obj.${name};`],
|
|
370
|
+
size: [
|
|
371
|
+
` if (${valueVar} != null && ${valueVar}.length > 0) {`,
|
|
372
|
+
` const _len = ${valueVar}.length;`,
|
|
373
|
+
` size += ${tagLength};`,
|
|
374
|
+
varintSize("_len", " "),
|
|
375
|
+
` size += _len;`,
|
|
376
|
+
` }`
|
|
377
|
+
],
|
|
378
|
+
write: [
|
|
379
|
+
` if (${valueVar} != null && ${valueVar}.length > 0) {`,
|
|
380
|
+
writeTag(fieldNumber, 2, " "),
|
|
381
|
+
` const _len = ${valueVar}.length;`,
|
|
382
|
+
writeVarint("_len", " "),
|
|
383
|
+
` buf.set(${valueVar}, offset);`,
|
|
384
|
+
` offset += _len;`,
|
|
385
|
+
` }`
|
|
386
|
+
]
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
if (typeName === "bool") {
|
|
390
|
+
return {
|
|
391
|
+
declare: [` const ${valueVar} = obj.${name};`],
|
|
392
|
+
size: [` if (${valueVar} === true) size += ${tagLength + 1};`],
|
|
393
|
+
write: [
|
|
394
|
+
` if (${valueVar} === true) {`,
|
|
395
|
+
writeTag(fieldNumber, 0, " "),
|
|
396
|
+
` buf[offset++] = 1;`,
|
|
397
|
+
` }`
|
|
398
|
+
]
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
if (isVarint64(typeName)) {
|
|
402
|
+
const bigintExpr = bigintVarintExpr(typeName, valueVar);
|
|
403
|
+
return {
|
|
404
|
+
declare: [` const ${valueVar} = obj.${name};`],
|
|
405
|
+
size: [
|
|
406
|
+
` if (${valueVar} != null && ${valueVar} !== 0n) {`,
|
|
407
|
+
` const _val = ${bigintExpr};`,
|
|
408
|
+
` size += ${tagLength};`,
|
|
409
|
+
` size += __varint64Size(_val);`,
|
|
410
|
+
` }`
|
|
411
|
+
],
|
|
412
|
+
write: [
|
|
413
|
+
` if (${valueVar} != null && ${valueVar} !== 0n) {`,
|
|
414
|
+
` const _val = ${bigintExpr};`,
|
|
415
|
+
writeTag(fieldNumber, 0, " "),
|
|
416
|
+
` offset = __writeVarint64(buf, offset, _val);`,
|
|
417
|
+
` }`
|
|
418
|
+
]
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
if (wireType === 0 /* Varint */) {
|
|
422
|
+
return {
|
|
423
|
+
declare: [` const ${valueVar} = obj.${name};`],
|
|
424
|
+
size: [
|
|
425
|
+
` if (${valueVar} != null && ${valueVar} !== 0) {`,
|
|
426
|
+
` const _val = ${valueVar} >>> 0;`,
|
|
427
|
+
` size += ${tagLength};`,
|
|
428
|
+
varintSize("_val", " "),
|
|
429
|
+
` }`
|
|
430
|
+
],
|
|
431
|
+
write: [
|
|
432
|
+
` if (${valueVar} != null && ${valueVar} !== 0) {`,
|
|
433
|
+
writeTag(fieldNumber, 0, " "),
|
|
434
|
+
writeVarint(`${valueVar} >>> 0`, " "),
|
|
435
|
+
` }`
|
|
436
|
+
]
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
if (typeName === "float") {
|
|
440
|
+
return {
|
|
441
|
+
declare: [` const ${valueVar} = obj.${name};`],
|
|
442
|
+
size: [` if (${valueVar} != null && ${valueVar} !== 0) size += ${tagLength + 4};`],
|
|
443
|
+
write: [
|
|
444
|
+
` if (${valueVar} != null && ${valueVar} !== 0) {`,
|
|
445
|
+
writeTag(fieldNumber, 5, " "),
|
|
446
|
+
` offset = __writeFloat32(buf, offset, ${valueVar});`,
|
|
447
|
+
` }`
|
|
448
|
+
]
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
if (wireType === 5 /* Bit32 */) {
|
|
452
|
+
return {
|
|
453
|
+
declare: [` const ${valueVar} = obj.${name};`],
|
|
454
|
+
size: [` if (${valueVar} != null && ${valueVar} !== 0) size += ${tagLength + 4};`],
|
|
455
|
+
write: [
|
|
456
|
+
` if (${valueVar} != null && ${valueVar} !== 0) {`,
|
|
457
|
+
writeTag(fieldNumber, 5, " "),
|
|
458
|
+
` const _val = ${valueVar};`,
|
|
459
|
+
` buf[offset++] = _val & 0xff;`,
|
|
460
|
+
` buf[offset++] = (_val >> 8) & 0xff;`,
|
|
461
|
+
` buf[offset++] = (_val >> 16) & 0xff;`,
|
|
462
|
+
` buf[offset++] = (_val >> 24) & 0xff;`,
|
|
463
|
+
` }`
|
|
464
|
+
]
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
if (typeName === "double") {
|
|
468
|
+
return {
|
|
469
|
+
declare: [` const ${valueVar} = obj.${name};`],
|
|
470
|
+
size: [` if (${valueVar} != null && ${valueVar} !== 0) size += ${tagLength + 8};`],
|
|
471
|
+
write: [
|
|
472
|
+
` if (${valueVar} != null && ${valueVar} !== 0) {`,
|
|
473
|
+
writeTag(fieldNumber, 1, " "),
|
|
474
|
+
` offset = __writeFloat64(buf, offset, ${valueVar});`,
|
|
475
|
+
` }`
|
|
476
|
+
]
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
if (isFixed64BigInt(typeName)) {
|
|
480
|
+
return {
|
|
481
|
+
declare: [` const ${valueVar} = obj.${name};`],
|
|
482
|
+
size: [` if (${valueVar} != null && ${valueVar} !== 0n) size += ${tagLength + 8};`],
|
|
483
|
+
write: [
|
|
484
|
+
` if (${valueVar} != null && ${valueVar} !== 0n) {`,
|
|
485
|
+
writeTag(fieldNumber, 1, " "),
|
|
486
|
+
` offset = __writeFixed64(buf, offset, ${valueVar});`,
|
|
487
|
+
` }`
|
|
488
|
+
]
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
if (wireType === 1 /* Bit64 */) {
|
|
492
|
+
return {
|
|
493
|
+
declare: [` const ${valueVar} = obj.${name};`],
|
|
494
|
+
size: [` if (${valueVar} != null && ${valueVar} !== 0) size += ${tagLength + 8};`],
|
|
495
|
+
write: [
|
|
496
|
+
` if (${valueVar} != null && ${valueVar} !== 0) {`,
|
|
497
|
+
writeTag(fieldNumber, 1, " "),
|
|
498
|
+
` const _val = ${valueVar};`,
|
|
499
|
+
` buf[offset++] = _val & 0xff;`,
|
|
500
|
+
` buf[offset++] = (_val >> 8) & 0xff;`,
|
|
501
|
+
` buf[offset++] = (_val >> 16) & 0xff;`,
|
|
502
|
+
` buf[offset++] = (_val >> 24) & 0xff;`,
|
|
503
|
+
` buf[offset++] = 0;`,
|
|
504
|
+
` buf[offset++] = 0;`,
|
|
505
|
+
` buf[offset++] = 0;`,
|
|
506
|
+
` buf[offset++] = 0;`,
|
|
507
|
+
` }`
|
|
508
|
+
]
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
return { declare: [], size: [], write: [] };
|
|
512
|
+
}
|
|
513
|
+
function buildRepeatedBlock(field, index) {
|
|
514
|
+
const { name, fieldNumber, typeName, wireType, isMessage } = field;
|
|
515
|
+
const arrayVar = `_f${index}`;
|
|
516
|
+
const cacheVar = `_c${index}`;
|
|
517
|
+
const tagWireType = isMessage || typeName === "string" || typeName === "bytes" ? 2 : wireType;
|
|
518
|
+
const tagLength = computeTagBytes(fieldNumber, tagWireType).length;
|
|
519
|
+
if (isMessage) {
|
|
520
|
+
return {
|
|
521
|
+
declare: [` const ${arrayVar} = obj.${name};`, ` let ${cacheVar};`],
|
|
522
|
+
size: [
|
|
523
|
+
` if (${arrayVar} != null && ${arrayVar}.length > 0) {`,
|
|
524
|
+
` ${cacheVar} = new Array(${arrayVar}.length);`,
|
|
525
|
+
` for (let _i = 0; _i < ${arrayVar}.length; _i++) {`,
|
|
526
|
+
` const _data = protobuf_encode_${typeName}(${arrayVar}[_i]);`,
|
|
527
|
+
` ${cacheVar}[_i] = _data;`,
|
|
528
|
+
` const _len = _data.length;`,
|
|
529
|
+
` size += ${tagLength};`,
|
|
530
|
+
varintSize("_len", " "),
|
|
531
|
+
` size += _len;`,
|
|
532
|
+
` }`,
|
|
533
|
+
` }`
|
|
534
|
+
],
|
|
535
|
+
write: [
|
|
536
|
+
` if (${arrayVar} != null && ${arrayVar}.length > 0) {`,
|
|
537
|
+
` for (let _i = 0; _i < ${arrayVar}.length; _i++) {`,
|
|
538
|
+
` const _data = ${cacheVar}[_i];`,
|
|
539
|
+
writeTag(fieldNumber, 2, " "),
|
|
540
|
+
` const _len = _data.length;`,
|
|
541
|
+
writeVarint("_len", " "),
|
|
542
|
+
` buf.set(_data, offset);`,
|
|
543
|
+
` offset += _len;`,
|
|
544
|
+
` }`,
|
|
545
|
+
` }`
|
|
546
|
+
]
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
if (typeName === "string") {
|
|
550
|
+
return {
|
|
551
|
+
declare: [` const ${arrayVar} = obj.${name};`, ` let ${cacheVar};`],
|
|
552
|
+
size: [
|
|
553
|
+
` if (${arrayVar} != null && ${arrayVar}.length > 0) {`,
|
|
554
|
+
` ${cacheVar} = new Array(${arrayVar}.length);`,
|
|
555
|
+
` for (let _i = 0; _i < ${arrayVar}.length; _i++) {`,
|
|
556
|
+
` const _len = __utf8Len(${arrayVar}[_i]);`,
|
|
557
|
+
` ${cacheVar}[_i] = _len;`,
|
|
558
|
+
` size += ${tagLength};`,
|
|
559
|
+
varintSize("_len", " "),
|
|
560
|
+
` size += _len;`,
|
|
561
|
+
` }`,
|
|
562
|
+
` }`
|
|
563
|
+
],
|
|
564
|
+
write: [
|
|
565
|
+
` if (${arrayVar} != null && ${arrayVar}.length > 0) {`,
|
|
566
|
+
` for (let _i = 0; _i < ${arrayVar}.length; _i++) {`,
|
|
567
|
+
` const _len = ${cacheVar}[_i];`,
|
|
568
|
+
writeTag(fieldNumber, 2, " "),
|
|
569
|
+
writeVarint("_len", " "),
|
|
570
|
+
` offset = __utf8Write(buf, offset, ${arrayVar}[_i]);`,
|
|
571
|
+
` }`,
|
|
572
|
+
` }`
|
|
573
|
+
]
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
if (typeName === "bytes") {
|
|
577
|
+
return {
|
|
578
|
+
declare: [` const ${arrayVar} = obj.${name};`],
|
|
579
|
+
size: [
|
|
580
|
+
` if (${arrayVar} != null && ${arrayVar}.length > 0) {`,
|
|
581
|
+
` for (let _i = 0; _i < ${arrayVar}.length; _i++) {`,
|
|
582
|
+
` const _data = ${arrayVar}[_i];`,
|
|
583
|
+
` const _len = _data.length;`,
|
|
584
|
+
` size += ${tagLength};`,
|
|
585
|
+
varintSize("_len", " "),
|
|
586
|
+
` size += _len;`,
|
|
587
|
+
` }`,
|
|
588
|
+
` }`
|
|
589
|
+
],
|
|
590
|
+
write: [
|
|
591
|
+
` if (${arrayVar} != null && ${arrayVar}.length > 0) {`,
|
|
592
|
+
` for (let _i = 0; _i < ${arrayVar}.length; _i++) {`,
|
|
593
|
+
` const _data = ${arrayVar}[_i];`,
|
|
594
|
+
writeTag(fieldNumber, 2, " "),
|
|
595
|
+
` const _len = _data.length;`,
|
|
596
|
+
writeVarint("_len", " "),
|
|
597
|
+
` buf.set(_data, offset);`,
|
|
598
|
+
` offset += _len;`,
|
|
599
|
+
` }`,
|
|
600
|
+
` }`
|
|
601
|
+
]
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
if (typeName === "bool") {
|
|
605
|
+
return {
|
|
606
|
+
declare: [` const ${arrayVar} = obj.${name};`],
|
|
607
|
+
size: [` if (${arrayVar} != null && ${arrayVar}.length > 0) size += ${arrayVar}.length * ${tagLength + 1};`],
|
|
608
|
+
write: [
|
|
609
|
+
` if (${arrayVar} != null && ${arrayVar}.length > 0) {`,
|
|
610
|
+
` for (let _i = 0; _i < ${arrayVar}.length; _i++) {`,
|
|
611
|
+
writeTag(fieldNumber, 0, " "),
|
|
612
|
+
` buf[offset++] = ${arrayVar}[_i] ? 1 : 0;`,
|
|
613
|
+
` }`,
|
|
614
|
+
` }`
|
|
615
|
+
]
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
if (isVarint64(typeName)) {
|
|
619
|
+
const bigintExpr = bigintVarintExpr(typeName, `${arrayVar}[_i]`);
|
|
620
|
+
return {
|
|
621
|
+
declare: [` const ${arrayVar} = obj.${name};`],
|
|
622
|
+
size: [
|
|
623
|
+
` if (${arrayVar} != null && ${arrayVar}.length > 0) {`,
|
|
624
|
+
` for (let _i = 0; _i < ${arrayVar}.length; _i++) {`,
|
|
625
|
+
` const _val = ${bigintExpr};`,
|
|
626
|
+
` size += ${tagLength};`,
|
|
627
|
+
` size += __varint64Size(_val);`,
|
|
628
|
+
` }`,
|
|
629
|
+
` }`
|
|
630
|
+
],
|
|
631
|
+
write: [
|
|
632
|
+
` if (${arrayVar} != null && ${arrayVar}.length > 0) {`,
|
|
633
|
+
` for (let _i = 0; _i < ${arrayVar}.length; _i++) {`,
|
|
634
|
+
` const _val = ${bigintExpr};`,
|
|
635
|
+
writeTag(fieldNumber, 0, " "),
|
|
636
|
+
` offset = __writeVarint64(buf, offset, _val);`,
|
|
637
|
+
` }`,
|
|
638
|
+
` }`
|
|
639
|
+
]
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
if (wireType === 0 /* Varint */) {
|
|
643
|
+
return {
|
|
644
|
+
declare: [` const ${arrayVar} = obj.${name};`],
|
|
645
|
+
size: [
|
|
646
|
+
` if (${arrayVar} != null && ${arrayVar}.length > 0) {`,
|
|
647
|
+
` for (let _i = 0; _i < ${arrayVar}.length; _i++) {`,
|
|
648
|
+
` const _val = ${arrayVar}[_i] >>> 0;`,
|
|
649
|
+
` size += ${tagLength};`,
|
|
650
|
+
varintSize("_val", " "),
|
|
651
|
+
` }`,
|
|
652
|
+
` }`
|
|
653
|
+
],
|
|
654
|
+
write: [
|
|
655
|
+
` if (${arrayVar} != null && ${arrayVar}.length > 0) {`,
|
|
656
|
+
` for (let _i = 0; _i < ${arrayVar}.length; _i++) {`,
|
|
657
|
+
writeTag(fieldNumber, 0, " "),
|
|
658
|
+
writeVarint(`${arrayVar}[_i] >>> 0`, " "),
|
|
659
|
+
` }`,
|
|
660
|
+
` }`
|
|
661
|
+
]
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
if (typeName === "float") {
|
|
665
|
+
return {
|
|
666
|
+
declare: [` const ${arrayVar} = obj.${name};`],
|
|
667
|
+
size: [` if (${arrayVar} != null && ${arrayVar}.length > 0) size += ${arrayVar}.length * ${tagLength + 4};`],
|
|
668
|
+
write: [
|
|
669
|
+
` if (${arrayVar} != null && ${arrayVar}.length > 0) {`,
|
|
670
|
+
` for (let _i = 0; _i < ${arrayVar}.length; _i++) {`,
|
|
671
|
+
writeTag(fieldNumber, 5, " "),
|
|
672
|
+
` offset = __writeFloat32(buf, offset, ${arrayVar}[_i]);`,
|
|
673
|
+
` }`,
|
|
674
|
+
` }`
|
|
675
|
+
]
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
if (wireType === 5 /* Bit32 */) {
|
|
679
|
+
return {
|
|
680
|
+
declare: [` const ${arrayVar} = obj.${name};`],
|
|
681
|
+
size: [` if (${arrayVar} != null && ${arrayVar}.length > 0) size += ${arrayVar}.length * ${tagLength + 4};`],
|
|
682
|
+
write: [
|
|
683
|
+
` if (${arrayVar} != null && ${arrayVar}.length > 0) {`,
|
|
684
|
+
` for (let _i = 0; _i < ${arrayVar}.length; _i++) {`,
|
|
685
|
+
writeTag(fieldNumber, 5, " "),
|
|
686
|
+
` const _val = ${arrayVar}[_i];`,
|
|
687
|
+
` buf[offset++] = _val & 0xff;`,
|
|
688
|
+
` buf[offset++] = (_val >> 8) & 0xff;`,
|
|
689
|
+
` buf[offset++] = (_val >> 16) & 0xff;`,
|
|
690
|
+
` buf[offset++] = (_val >> 24) & 0xff;`,
|
|
691
|
+
` }`,
|
|
692
|
+
` }`
|
|
693
|
+
]
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
if (typeName === "double") {
|
|
697
|
+
return {
|
|
698
|
+
declare: [` const ${arrayVar} = obj.${name};`],
|
|
699
|
+
size: [` if (${arrayVar} != null && ${arrayVar}.length > 0) size += ${arrayVar}.length * ${tagLength + 8};`],
|
|
700
|
+
write: [
|
|
701
|
+
` if (${arrayVar} != null && ${arrayVar}.length > 0) {`,
|
|
702
|
+
` for (let _i = 0; _i < ${arrayVar}.length; _i++) {`,
|
|
703
|
+
writeTag(fieldNumber, 1, " "),
|
|
704
|
+
` offset = __writeFloat64(buf, offset, ${arrayVar}[_i]);`,
|
|
705
|
+
` }`,
|
|
706
|
+
` }`
|
|
707
|
+
]
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
if (isFixed64BigInt(typeName)) {
|
|
711
|
+
return {
|
|
712
|
+
declare: [` const ${arrayVar} = obj.${name};`],
|
|
713
|
+
size: [` if (${arrayVar} != null && ${arrayVar}.length > 0) size += ${arrayVar}.length * ${tagLength + 8};`],
|
|
714
|
+
write: [
|
|
715
|
+
` if (${arrayVar} != null && ${arrayVar}.length > 0) {`,
|
|
716
|
+
` for (let _i = 0; _i < ${arrayVar}.length; _i++) {`,
|
|
717
|
+
writeTag(fieldNumber, 1, " "),
|
|
718
|
+
` offset = __writeFixed64(buf, offset, ${arrayVar}[_i]);`,
|
|
719
|
+
` }`,
|
|
720
|
+
` }`
|
|
721
|
+
]
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
if (wireType === 1 /* Bit64 */) {
|
|
725
|
+
return {
|
|
726
|
+
declare: [` const ${arrayVar} = obj.${name};`],
|
|
727
|
+
size: [` if (${arrayVar} != null && ${arrayVar}.length > 0) size += ${arrayVar}.length * ${tagLength + 8};`],
|
|
728
|
+
write: [
|
|
729
|
+
` if (${arrayVar} != null && ${arrayVar}.length > 0) {`,
|
|
730
|
+
` for (let _i = 0; _i < ${arrayVar}.length; _i++) {`,
|
|
731
|
+
writeTag(fieldNumber, 1, " "),
|
|
732
|
+
` const _val = ${arrayVar}[_i];`,
|
|
733
|
+
` buf[offset++] = _val & 0xff;`,
|
|
734
|
+
` buf[offset++] = (_val >> 8) & 0xff;`,
|
|
735
|
+
` buf[offset++] = (_val >> 16) & 0xff;`,
|
|
736
|
+
` buf[offset++] = (_val >> 24) & 0xff;`,
|
|
737
|
+
` buf[offset++] = 0;`,
|
|
738
|
+
` buf[offset++] = 0;`,
|
|
739
|
+
` buf[offset++] = 0;`,
|
|
740
|
+
` buf[offset++] = 0;`,
|
|
741
|
+
` }`,
|
|
742
|
+
` }`
|
|
743
|
+
]
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
return { declare: [], size: [], write: [] };
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// src/codegen/decoder.ts
|
|
750
|
+
function tagValue(field) {
|
|
751
|
+
const wireType = field.isMessage || field.typeName === "string" || field.typeName === "bytes" ? 2 /* LengthDelim */ : field.wireType;
|
|
752
|
+
return (field.fieldNumber << 3 | wireType) >>> 0;
|
|
753
|
+
}
|
|
754
|
+
function varintDec(varName, ind) {
|
|
755
|
+
return [
|
|
756
|
+
`${ind}let ${varName} = data[offset++];`,
|
|
757
|
+
`${ind}if (${varName} & 0x80) {`,
|
|
758
|
+
`${ind} let _s = 7, _b;`,
|
|
759
|
+
`${ind} ${varName} &= 0x7f;`,
|
|
760
|
+
`${ind} do { _b = data[offset++]; ${varName} |= (_b & 0x7f) << _s; _s += 7; } while (_b & 0x80);`,
|
|
761
|
+
`${ind}}`
|
|
762
|
+
].join("\n");
|
|
763
|
+
}
|
|
764
|
+
function varintDec64(varName, ind) {
|
|
765
|
+
return [
|
|
766
|
+
`${ind}let ${varName} = 0n, _s = 0n, _b;`,
|
|
767
|
+
`${ind}do { _b = data[offset++]; ${varName} |= BigInt(_b & 0x7f) << _s; _s += 7n; } while (_b & 0x80);`
|
|
768
|
+
].join("\n");
|
|
769
|
+
}
|
|
770
|
+
function isVarint642(typeName) {
|
|
771
|
+
return typeName === "uint_64" || typeName === "int_64" || typeName === "sint_64";
|
|
772
|
+
}
|
|
773
|
+
function isFixed64BigInt2(typeName) {
|
|
774
|
+
return typeName === "fixed_64" || typeName === "sfixed_64";
|
|
775
|
+
}
|
|
776
|
+
var INLINE_SKIP = [
|
|
777
|
+
` const wireType = _tag & 0x7;`,
|
|
778
|
+
` if (wireType === 0) { while (data[offset] & 0x80) offset++; offset++; }`,
|
|
779
|
+
` else if (wireType === 1) offset += 8;`,
|
|
780
|
+
` else if (wireType === 2) { let _l = data[offset++]; if (_l & 0x80) { let _s = 7, _b; _l &= 0x7f; do { _b = data[offset++]; _l |= (_b & 0x7f) << _s; _s += 7; } while (_b & 0x80); } offset += _l; }`,
|
|
781
|
+
` else if (wireType === 5) offset += 4;`
|
|
782
|
+
].join("\n");
|
|
783
|
+
function generateDecoder(msg, _registry) {
|
|
784
|
+
const locals = msg.fields.map((field, index) => {
|
|
785
|
+
const keyword = field.isRepeated ? "const" : "let";
|
|
786
|
+
return ` ${keyword} _f${index} = ${getDefault(field)};`;
|
|
787
|
+
});
|
|
788
|
+
const result = msg.fields.map((field, index) => `${field.name}: _f${index}`).join(", ");
|
|
789
|
+
const L = [
|
|
790
|
+
`function protobuf_decode_${msg.name}(data, offset = 0, end = data.length) {`,
|
|
791
|
+
...locals,
|
|
792
|
+
` while (offset < end) {`,
|
|
793
|
+
` let _tag = data[offset++];`,
|
|
794
|
+
` if (_tag & 0x80) {`,
|
|
795
|
+
` let _ts = 7, _tb;`,
|
|
796
|
+
` _tag &= 0x7f;`,
|
|
797
|
+
` do { _tb = data[offset++]; _tag |= (_tb & 0x7f) << _ts; _ts += 7; } while (_tb & 0x80);`,
|
|
798
|
+
` }`,
|
|
799
|
+
` switch (_tag) {`
|
|
800
|
+
];
|
|
801
|
+
msg.fields.forEach((field, index) => {
|
|
802
|
+
L.push(decodeField(field, index));
|
|
803
|
+
});
|
|
804
|
+
L.push(
|
|
805
|
+
` default: {`,
|
|
806
|
+
INLINE_SKIP,
|
|
807
|
+
` break;`,
|
|
808
|
+
` }`,
|
|
809
|
+
` }`,
|
|
810
|
+
` }`,
|
|
811
|
+
` return { ${result} };`,
|
|
812
|
+
`}`
|
|
813
|
+
);
|
|
814
|
+
return L.join("\n");
|
|
815
|
+
}
|
|
816
|
+
function decodeField(field, index) {
|
|
817
|
+
const { typeName, wireType, isMessage, isRepeated } = field;
|
|
818
|
+
const I = " ";
|
|
819
|
+
const local = `_f${index}`;
|
|
820
|
+
const assign = (expr) => isRepeated ? `${I}${local}.push(${expr});` : `${I}${local} = ${expr};`;
|
|
821
|
+
const L = [` case ${tagValue(field)}: {`];
|
|
822
|
+
if (isMessage) {
|
|
823
|
+
L.push(varintDec("_len", I));
|
|
824
|
+
L.push(assign(`protobuf_decode_${typeName}(data, offset, offset + _len)`));
|
|
825
|
+
L.push(`${I}offset += _len;`);
|
|
826
|
+
} else if (typeName === "string") {
|
|
827
|
+
L.push(varintDec("_len", I));
|
|
828
|
+
L.push(`${I}const _end = offset + _len;`);
|
|
829
|
+
L.push(assign(`__td.decode(data.subarray(offset, _end))`));
|
|
830
|
+
L.push(`${I}offset = _end;`);
|
|
831
|
+
} else if (typeName === "bytes") {
|
|
832
|
+
L.push(varintDec("_len", I));
|
|
833
|
+
L.push(`${I}const _end = offset + _len;`);
|
|
834
|
+
L.push(assign(`data.slice(offset, _end)`));
|
|
835
|
+
L.push(`${I}offset = _end;`);
|
|
836
|
+
} else if (typeName === "bool") {
|
|
837
|
+
L.push(varintDec("_val", I));
|
|
838
|
+
L.push(assign(`_val !== 0`));
|
|
839
|
+
} else if (isVarint642(typeName)) {
|
|
840
|
+
L.push(varintDec64("_val", I));
|
|
841
|
+
if (typeName === "uint_64") {
|
|
842
|
+
L.push(assign(`_val`));
|
|
843
|
+
} else if (typeName === "int_64") {
|
|
844
|
+
L.push(assign(`BigInt.asIntN(64, _val)`));
|
|
845
|
+
} else {
|
|
846
|
+
L.push(assign(`__zigZagDecode64(_val)`));
|
|
847
|
+
}
|
|
848
|
+
} else if (wireType === 0 /* Varint */) {
|
|
849
|
+
L.push(varintDec("_val", I));
|
|
850
|
+
L.push(assign(`_val >>> 0`));
|
|
851
|
+
} else if (typeName === "float") {
|
|
852
|
+
L.push(assign(`__readFloat32(data, offset)`));
|
|
853
|
+
L.push(`${I}offset += 4;`);
|
|
854
|
+
} else if (wireType === 5 /* Bit32 */) {
|
|
855
|
+
L.push(assign(`data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24)`));
|
|
856
|
+
L.push(`${I}offset += 4;`);
|
|
857
|
+
} else if (typeName === "double") {
|
|
858
|
+
L.push(assign(`__readFloat64(data, offset)`));
|
|
859
|
+
L.push(`${I}offset += 8;`);
|
|
860
|
+
} else if (isFixed64BigInt2(typeName)) {
|
|
861
|
+
if (typeName === "fixed_64") {
|
|
862
|
+
L.push(assign(`__readFixed64(data, offset)`));
|
|
863
|
+
} else {
|
|
864
|
+
L.push(assign(`BigInt.asIntN(64, __readFixed64(data, offset))`));
|
|
865
|
+
}
|
|
866
|
+
L.push(`${I}offset += 8;`);
|
|
867
|
+
} else if (wireType === 1 /* Bit64 */) {
|
|
868
|
+
L.push(assign(`data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24)`));
|
|
869
|
+
L.push(`${I}offset += 8;`);
|
|
870
|
+
}
|
|
871
|
+
L.push(`${I}break;`, ` }`);
|
|
872
|
+
return L.join("\n");
|
|
873
|
+
}
|
|
874
|
+
function getDefault(field) {
|
|
875
|
+
if (field.isRepeated) return "[]";
|
|
876
|
+
if (field.isOptional || field.isMessage) return "null";
|
|
877
|
+
const primitive = PRIMITIVE_TYPE_MAP[field.typeName];
|
|
878
|
+
return primitive ? primitive.defaultValue : "null";
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// src/codegen/generator.ts
|
|
882
|
+
var CODEGEN_PREAMBLE = `const __td = new TextDecoder();
|
|
883
|
+
const __scratch = new DataView(new ArrayBuffer(8));
|
|
884
|
+
function __utf8Len(value) {
|
|
885
|
+
let length = 0;
|
|
886
|
+
for (let i = 0; i < value.length; i++) {
|
|
887
|
+
const code = value.charCodeAt(i);
|
|
888
|
+
if (code < 0x80) {
|
|
889
|
+
length++;
|
|
890
|
+
continue;
|
|
891
|
+
}
|
|
892
|
+
if (code < 0x800) {
|
|
893
|
+
length += 2;
|
|
894
|
+
continue;
|
|
895
|
+
}
|
|
896
|
+
if ((code & 0xfc00) === 0xd800) {
|
|
897
|
+
if (i + 1 < value.length) {
|
|
898
|
+
const next = value.charCodeAt(i + 1);
|
|
899
|
+
if ((next & 0xfc00) === 0xdc00) {
|
|
900
|
+
length += 4;
|
|
901
|
+
i++;
|
|
902
|
+
continue;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
length += 3;
|
|
906
|
+
continue;
|
|
907
|
+
}
|
|
908
|
+
if ((code & 0xfc00) === 0xdc00) {
|
|
909
|
+
length += 3;
|
|
910
|
+
continue;
|
|
911
|
+
}
|
|
912
|
+
length += 3;
|
|
913
|
+
}
|
|
914
|
+
return length;
|
|
915
|
+
}
|
|
916
|
+
function __utf8Write(buf, offset, value) {
|
|
917
|
+
for (let i = 0; i < value.length; i++) {
|
|
918
|
+
let code = value.charCodeAt(i);
|
|
919
|
+
if (code < 0x80) {
|
|
920
|
+
buf[offset++] = code;
|
|
921
|
+
continue;
|
|
922
|
+
}
|
|
923
|
+
if (code < 0x800) {
|
|
924
|
+
buf[offset++] = 0xc0 | (code >> 6);
|
|
925
|
+
buf[offset++] = 0x80 | (code & 0x3f);
|
|
926
|
+
continue;
|
|
927
|
+
}
|
|
928
|
+
if ((code & 0xfc00) === 0xd800) {
|
|
929
|
+
if (i + 1 < value.length) {
|
|
930
|
+
const next = value.charCodeAt(i + 1);
|
|
931
|
+
if ((next & 0xfc00) === 0xdc00) {
|
|
932
|
+
const point = ((code - 0xd800) << 10) + (next - 0xdc00) + 0x10000;
|
|
933
|
+
buf[offset++] = 0xf0 | (point >> 18);
|
|
934
|
+
buf[offset++] = 0x80 | ((point >> 12) & 0x3f);
|
|
935
|
+
buf[offset++] = 0x80 | ((point >> 6) & 0x3f);
|
|
936
|
+
buf[offset++] = 0x80 | (point & 0x3f);
|
|
937
|
+
i++;
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
code = 0xfffd;
|
|
942
|
+
} else if ((code & 0xfc00) === 0xdc00) {
|
|
943
|
+
code = 0xfffd;
|
|
944
|
+
}
|
|
945
|
+
buf[offset++] = 0xe0 | (code >> 12);
|
|
946
|
+
buf[offset++] = 0x80 | ((code >> 6) & 0x3f);
|
|
947
|
+
buf[offset++] = 0x80 | (code & 0x3f);
|
|
948
|
+
}
|
|
949
|
+
return offset;
|
|
950
|
+
}
|
|
951
|
+
function __varint64Size(value) {
|
|
952
|
+
let size = 1;
|
|
953
|
+
while (value > 0x7fn) {
|
|
954
|
+
value >>= 7n;
|
|
955
|
+
size++;
|
|
956
|
+
}
|
|
957
|
+
return size;
|
|
958
|
+
}
|
|
959
|
+
function __writeVarint64(buf, offset, value) {
|
|
960
|
+
while (value > 0x7fn) {
|
|
961
|
+
buf[offset++] = Number((value & 0x7fn) | 0x80n);
|
|
962
|
+
value >>= 7n;
|
|
963
|
+
}
|
|
964
|
+
buf[offset++] = Number(value);
|
|
965
|
+
return offset;
|
|
966
|
+
}
|
|
967
|
+
function __zigZagEncode64(value) {
|
|
968
|
+
value = BigInt.asIntN(64, value);
|
|
969
|
+
return BigInt.asUintN(64, (value << 1n) ^ (value >> 63n));
|
|
970
|
+
}
|
|
971
|
+
function __zigZagDecode64(value) {
|
|
972
|
+
return BigInt.asIntN(64, (value >> 1n) ^ -(value & 1n));
|
|
973
|
+
}
|
|
974
|
+
function __writeFloat32(buf, offset, value) {
|
|
975
|
+
__scratch.setFloat32(0, value, true);
|
|
976
|
+
buf[offset++] = __scratch.getUint8(0);
|
|
977
|
+
buf[offset++] = __scratch.getUint8(1);
|
|
978
|
+
buf[offset++] = __scratch.getUint8(2);
|
|
979
|
+
buf[offset++] = __scratch.getUint8(3);
|
|
980
|
+
return offset;
|
|
981
|
+
}
|
|
982
|
+
function __readFloat32(data, offset) {
|
|
983
|
+
__scratch.setUint8(0, data[offset]);
|
|
984
|
+
__scratch.setUint8(1, data[offset + 1]);
|
|
985
|
+
__scratch.setUint8(2, data[offset + 2]);
|
|
986
|
+
__scratch.setUint8(3, data[offset + 3]);
|
|
987
|
+
return __scratch.getFloat32(0, true);
|
|
988
|
+
}
|
|
989
|
+
function __writeFloat64(buf, offset, value) {
|
|
990
|
+
__scratch.setFloat64(0, value, true);
|
|
991
|
+
buf[offset++] = __scratch.getUint8(0);
|
|
992
|
+
buf[offset++] = __scratch.getUint8(1);
|
|
993
|
+
buf[offset++] = __scratch.getUint8(2);
|
|
994
|
+
buf[offset++] = __scratch.getUint8(3);
|
|
995
|
+
buf[offset++] = __scratch.getUint8(4);
|
|
996
|
+
buf[offset++] = __scratch.getUint8(5);
|
|
997
|
+
buf[offset++] = __scratch.getUint8(6);
|
|
998
|
+
buf[offset++] = __scratch.getUint8(7);
|
|
999
|
+
return offset;
|
|
1000
|
+
}
|
|
1001
|
+
function __readFloat64(data, offset) {
|
|
1002
|
+
__scratch.setUint8(0, data[offset]);
|
|
1003
|
+
__scratch.setUint8(1, data[offset + 1]);
|
|
1004
|
+
__scratch.setUint8(2, data[offset + 2]);
|
|
1005
|
+
__scratch.setUint8(3, data[offset + 3]);
|
|
1006
|
+
__scratch.setUint8(4, data[offset + 4]);
|
|
1007
|
+
__scratch.setUint8(5, data[offset + 5]);
|
|
1008
|
+
__scratch.setUint8(6, data[offset + 6]);
|
|
1009
|
+
__scratch.setUint8(7, data[offset + 7]);
|
|
1010
|
+
return __scratch.getFloat64(0, true);
|
|
1011
|
+
}
|
|
1012
|
+
function __writeFixed64(buf, offset, value) {
|
|
1013
|
+
value = BigInt.asUintN(64, value);
|
|
1014
|
+
buf[offset++] = Number(value & 0xffn);
|
|
1015
|
+
buf[offset++] = Number((value >> 8n) & 0xffn);
|
|
1016
|
+
buf[offset++] = Number((value >> 16n) & 0xffn);
|
|
1017
|
+
buf[offset++] = Number((value >> 24n) & 0xffn);
|
|
1018
|
+
buf[offset++] = Number((value >> 32n) & 0xffn);
|
|
1019
|
+
buf[offset++] = Number((value >> 40n) & 0xffn);
|
|
1020
|
+
buf[offset++] = Number((value >> 48n) & 0xffn);
|
|
1021
|
+
buf[offset++] = Number((value >> 56n) & 0xffn);
|
|
1022
|
+
return offset;
|
|
1023
|
+
}
|
|
1024
|
+
function __readFixed64(data, offset) {
|
|
1025
|
+
return BigInt(data[offset])
|
|
1026
|
+
| (BigInt(data[offset + 1]) << 8n)
|
|
1027
|
+
| (BigInt(data[offset + 2]) << 16n)
|
|
1028
|
+
| (BigInt(data[offset + 3]) << 24n)
|
|
1029
|
+
| (BigInt(data[offset + 4]) << 32n)
|
|
1030
|
+
| (BigInt(data[offset + 5]) << 40n)
|
|
1031
|
+
| (BigInt(data[offset + 6]) << 48n)
|
|
1032
|
+
| (BigInt(data[offset + 7]) << 56n);
|
|
1033
|
+
}`;
|
|
1034
|
+
function generateCode(registry) {
|
|
1035
|
+
if (registry.size === 0) return "";
|
|
1036
|
+
const parts = [CODEGEN_PREAMBLE];
|
|
1037
|
+
for (const msg of registry.values()) {
|
|
1038
|
+
parts.push(generateEncoder(msg, registry));
|
|
1039
|
+
parts.push(generateDecoder(msg, registry));
|
|
1040
|
+
}
|
|
1041
|
+
return parts.join("\n");
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
// src/transform/replacer.ts
|
|
1045
|
+
import ts5 from "typescript";
|
|
1046
|
+
function applyReplacements(code, sf, callSites, registry) {
|
|
1047
|
+
const edits = [];
|
|
1048
|
+
for (const cs of callSites) {
|
|
1049
|
+
const mangled = typeNodeToMangledName(cs.firstTypeArg, sf);
|
|
1050
|
+
if (registry.has(mangled)) {
|
|
1051
|
+
edits.push({
|
|
1052
|
+
start: cs.exprStart,
|
|
1053
|
+
end: cs.typeArgsEnd,
|
|
1054
|
+
replacement: `${cs.fnName}_${mangled}`
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
if (!edits.length) return { transformedCode: code, hasReplacements: false };
|
|
1059
|
+
edits.sort((a, b) => b.start - a.start);
|
|
1060
|
+
let result = code;
|
|
1061
|
+
for (const ed of edits) result = result.slice(0, ed.start) + ed.replacement + result.slice(ed.end);
|
|
1062
|
+
return { transformedCode: result, hasReplacements: true };
|
|
1063
|
+
}
|
|
1064
|
+
function replaceCallSites(code, registry) {
|
|
1065
|
+
const sf = ts5.createSourceFile("input.ts", code, ts5.ScriptTarget.Latest, true);
|
|
1066
|
+
const callSites = [];
|
|
1067
|
+
ts5.forEachChild(sf, function visit(node) {
|
|
1068
|
+
if (ts5.isCallExpression(node)) {
|
|
1069
|
+
const e = node.expression;
|
|
1070
|
+
if (ts5.isIdentifier(e) && (e.text === "protobuf_encode" || e.text === "protobuf_decode")) {
|
|
1071
|
+
const ta = node.typeArguments;
|
|
1072
|
+
if (ta?.length) {
|
|
1073
|
+
callSites.push({
|
|
1074
|
+
fnName: e.text,
|
|
1075
|
+
exprStart: e.getStart(sf),
|
|
1076
|
+
typeArgsEnd: ta.end + 1,
|
|
1077
|
+
firstTypeArg: ta[0]
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
ts5.forEachChild(node, visit);
|
|
1083
|
+
});
|
|
1084
|
+
return applyReplacements(code, sf, callSites, registry);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// src/index.ts
|
|
1088
|
+
function protobufVitePlugin() {
|
|
1089
|
+
return {
|
|
1090
|
+
name: "vite-plugin-protobuf",
|
|
1091
|
+
enforce: "pre",
|
|
1092
|
+
transform(code, id) {
|
|
1093
|
+
if (!id.endsWith(".ts") || id.endsWith(".d.ts")) return null;
|
|
1094
|
+
const { registry, callSites, sourceFile } = analyze(code, id);
|
|
1095
|
+
if (registry.size === 0) return null;
|
|
1096
|
+
const generatedCode = generateCode(registry);
|
|
1097
|
+
const { transformedCode, hasReplacements } = applyReplacements(code, sourceFile, callSites, registry);
|
|
1098
|
+
if (!hasReplacements && generatedCode === "") return null;
|
|
1099
|
+
return { code: generatedCode + "\n" + transformedCode, map: null };
|
|
1100
|
+
}
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
export {
|
|
1104
|
+
analyze,
|
|
1105
|
+
analyzeSource,
|
|
1106
|
+
applyReplacements,
|
|
1107
|
+
protobufVitePlugin as default,
|
|
1108
|
+
generateCode,
|
|
1109
|
+
replaceCallSites,
|
|
1110
|
+
typeNodeToMangledName
|
|
1111
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "protobuf-fastdsl",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"protobuf.d.ts"
|
|
10
|
+
],
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./types": {
|
|
17
|
+
"types": "./protobuf.d.ts"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup src/index.ts --format esm --dts",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"test:watch": "vitest",
|
|
24
|
+
"bench": "npx tsx bench/index.ts"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"typescript": "^5.5.0"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"vite": "^5.0.0 || ^6.0.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^25.4.0",
|
|
34
|
+
"tsup": "^8.0.0",
|
|
35
|
+
"vite": "^6.0.0",
|
|
36
|
+
"vitest": "^3.0.0"
|
|
37
|
+
}
|
|
38
|
+
}
|
package/protobuf.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// ── Protobuf field markers ────────────────────────────────────────────
|
|
2
|
+
/** Marks a singular protobuf field: `name: pb<fieldNumber, Type>` */
|
|
3
|
+
type pb<_ProtoNumber extends number, Type> = Type;
|
|
4
|
+
/** Marks a repeated protobuf field: `ids: pb_repeated<fieldNumber, Type>` → Type[] */
|
|
5
|
+
type pb_repeated<_ProtoNumber extends number, Type> = Type[];
|
|
6
|
+
|
|
7
|
+
// ── Protobuf primitive types ──────────────────────────────────────────
|
|
8
|
+
type uint_32 = number;
|
|
9
|
+
type int_32 = number;
|
|
10
|
+
type uint_64 = bigint;
|
|
11
|
+
type int_64 = bigint;
|
|
12
|
+
type sint_32 = number;
|
|
13
|
+
type sint_64 = bigint;
|
|
14
|
+
type bool = boolean;
|
|
15
|
+
type float = number;
|
|
16
|
+
type double = number;
|
|
17
|
+
type fixed_32 = number;
|
|
18
|
+
type fixed_64 = bigint;
|
|
19
|
+
type sfixed_32 = number;
|
|
20
|
+
type sfixed_64 = bigint;
|
|
21
|
+
type bytes = Uint8Array;
|
|
22
|
+
|
|
23
|
+
// ── Encode / decode stubs (replaced at compile-time by the vite plugin) ──
|
|
24
|
+
declare function protobuf_encode<T>(params: T): Uint8Array;
|
|
25
|
+
declare function protobuf_decode<T>(data: Uint8Array): T;
|