protobuf-fastdsl 0.1.2 → 0.1.5

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,6 +1,6 @@
1
1
  # protobuf-fastdsl
2
2
 
3
- 一个 Vite 插件,将 TypeScript protobuf 接口在构建时编译为**完全内联**、零依赖的编解码函数。
3
+ TypeScript 接口在构建时编译为**完全内联**、零依赖的 protobuf 编解码函数。
4
4
 
5
5
  无 `.proto` 文件。无运行时库。无函数调用开销。只需 TypeScript 接口 → 确定化的二进制编解码代码。
6
6
 
@@ -8,9 +8,11 @@
8
8
 
9
9
  - **零运行时** — 所有 wire-format 逻辑在编译时内联
10
10
  - **TypeScript 原生** — 用 `pb<N, Type>` 标记接口字段即可定义 schema
11
+ - **跨文件类型** — 支持 `import type` / `import` 引用其他文件的接口定义
11
12
  - **泛型单态化** — `Wrapper<Wrapper<string>>` → 自动生成具体编解码函数
12
13
  - **重复字段** — `pb_repeated<N, Type>` 编译为 `Type[]`
13
14
  - **编译期预计算 Tag** — 字段标签在编译时折叠为字面量字节
15
+ - **安全的 Fallback** — 未经插件转换时运行会直接抛错,不会静默产生错误结果
14
16
 
15
17
  ## 安装
16
18
 
@@ -20,30 +22,24 @@ npm install protobuf-fastdsl
20
22
 
21
23
  ## 快速开始
22
24
 
23
- **1. 在 `vite.config.ts` 中添加插件:**
25
+ **1. 配置构建工具插件:**
24
26
 
25
27
  ```ts
26
- import protobufVite from 'protobuf-fastdsl';
28
+ // vite.config.ts
29
+ import protobufPlugin from 'protobuf-fastdsl/vite';
27
30
 
28
31
  export default defineConfig({
29
- plugins: [protobufVite()],
32
+ plugins: [protobufPlugin()],
30
33
  });
31
34
  ```
32
35
 
33
- **2. `tsconfig.json` 中添加类型声明:**
34
-
35
- ```json
36
- {
37
- "compilerOptions": {
38
- "types": ["protobuf-fastdsl/types"]
39
- }
40
- }
41
- ```
42
-
43
- **3. 用 TypeScript 接口定义 schema:**
36
+ **2. 定义 protobuf schema(TypeScript 接口):**
44
37
 
45
38
  ```ts
46
- interface UserProfile {
39
+ // schema/user.ts
40
+ import type { pb, pb_repeated, uint_32, bool } from 'protobuf-fastdsl';
41
+
42
+ export interface UserProfile {
47
43
  id: pb<1, uint_32>;
48
44
  username: pb<2, string>;
49
45
  active: pb<3, bool>;
@@ -51,9 +47,12 @@ interface UserProfile {
51
47
  }
52
48
  ```
53
49
 
54
- **4. 使用 `protobuf_encode` / `protobuf_decode`:**
50
+ **3. 编解码:**
55
51
 
56
52
  ```ts
53
+ import { protobuf_encode, protobuf_decode } from 'protobuf-fastdsl';
54
+ import type { UserProfile } from './schema/user';
55
+
57
56
  const bytes = protobuf_encode<UserProfile>({
58
57
  id: 42,
59
58
  username: 'alice',
@@ -65,7 +64,7 @@ const user = protobuf_decode<UserProfile>(bytes);
65
64
  // user.id === 42, user.tags === ['admin', 'dev']
66
65
  ```
67
66
 
68
- 构建时,插件会将上述代码转换为:
67
+ 构建时,插件将上述代码转换为:
69
68
 
70
69
  ```js
71
70
  // 预计算的 tag 字面量,内联 varint 循环,零函数调用
@@ -73,6 +72,43 @@ const bytes = protobuf_encode_UserProfile({ id: 42, ... });
73
72
  const user = protobuf_decode_UserProfile(bytes);
74
73
  ```
75
74
 
75
+ 如果忘记配置插件,`protobuf_encode` / `protobuf_decode` 会在运行时抛出错误提示。
76
+
77
+ ## 跨文件类型引用
78
+
79
+ 接口定义和编解码调用可以在不同文件中,插件会自动跟踪 import 链:
80
+
81
+ ```ts
82
+ // inner.ts
83
+ import type { pb, uint_32 } from 'protobuf-fastdsl';
84
+
85
+ export interface Inner {
86
+ value: pb<1, uint_32>;
87
+ }
88
+
89
+ // outer.ts
90
+ import { protobuf_encode } from 'protobuf-fastdsl';
91
+ import type { Inner } from './inner';
92
+ import type { pb } from 'protobuf-fastdsl';
93
+
94
+ interface Outer {
95
+ inner: pb<1, Inner>;
96
+ }
97
+
98
+ const bytes = protobuf_encode<Outer>({ inner: { value: 42 } });
99
+ ```
100
+
101
+ 支持传递性导入(A → B → C),插件会递归解析所有依赖。
102
+
103
+ ## 别名导入
104
+
105
+ ```ts
106
+ import { protobuf_encode as encode, protobuf_decode as decode } from 'protobuf-fastdsl';
107
+
108
+ const bytes = encode<UserProfile>(data); // → protobuf_encode_UserProfile(data)
109
+ const user = decode<UserProfile>(bytes); // → protobuf_decode_UserProfile(bytes)
110
+ ```
111
+
76
112
  ## 泛型单态化
77
113
 
78
114
  定义泛型 protobuf 模板,使用具体类型实例化:
@@ -95,8 +131,8 @@ const data = protobuf_encode<Wrapper<Wrapper<string>>>({
95
131
  |---------------|-----------------|-----------|
96
132
  | `uint_32`, `int_32` | `number` | Varint |
97
133
  | `uint_64`, `int_64` | `bigint` | Varint |
98
- | `sint_32` | `number` | Varint |
99
- | `sint_64` | `bigint` | Varint |
134
+ | `sint_32` | `number` | Varint (ZigZag) |
135
+ | `sint_64` | `bigint` | Varint (ZigZag) |
100
136
  | `bool` | `boolean` | Varint |
101
137
  | `string` | `string` | LengthDelimited |
102
138
  | `bytes` | `Uint8Array` | LengthDelimited |
@@ -111,6 +147,14 @@ const data = protobuf_encode<Wrapper<Wrapper<string>>>({
111
147
 
112
148
  说明:
113
149
  - 所有 64 位整数类型在 TypeScript 中统一映射为 `bigint`
150
+ - `sint_32` / `sint_64` 使用 ZigZag 编码,适用于频繁出现负数的场景
151
+
152
+ ## 包入口
153
+
154
+ | 路径 | 用途 |
155
+ |------|------|
156
+ | `protobuf-fastdsl` | 用户代码 — `protobuf_encode`、`protobuf_decode`、所有类型 |
157
+ | `protobuf-fastdsl/vite` | Vite 插件 |
114
158
 
115
159
  ## ⚡ 性能测试
116
160
 
package/dist/index.d.ts CHANGED
@@ -20,8 +20,36 @@ interface ProtobufMessage {
20
20
  name: string;
21
21
  fields: ProtobufField[];
22
22
  }
23
+ /** Generic interface template, e.g. `interface Wrapper<T> { val: pb<1, T>; }` */
24
+ interface GenericProtobufTemplate {
25
+ name: string;
26
+ typeParams: string[];
27
+ fields: GenericFieldTemplate[];
28
+ }
29
+ interface GenericFieldTemplate {
30
+ name: string;
31
+ fieldNumber: number;
32
+ rawTypeName: string;
33
+ isTypeParam: boolean;
34
+ isOptional: boolean;
35
+ isRepeated: boolean;
36
+ }
23
37
  type MessageRegistry = Map<string, ProtobufMessage>;
24
38
 
39
+ interface ParsedFileEntry {
40
+ concrete: ProtobufMessage[];
41
+ templates: Map<string, GenericProtobufTemplate>;
42
+ }
43
+ interface ImportedDefinitions {
44
+ concrete: ProtobufMessage[];
45
+ templates: Map<string, GenericProtobufTemplate>;
46
+ }
47
+ /**
48
+ * Recursively resolve import-type declarations from a source file.
49
+ * Returns all protobuf interfaces and templates reachable through imports.
50
+ */
51
+ declare function resolveImports(code: string, importerPath: string, cache: Map<string, ParsedFileEntry>): ImportedDefinitions;
52
+
25
53
  /**
26
54
  * Deterministic mangled name for a type node.
27
55
  * `Foo` → `Foo`
@@ -51,12 +79,12 @@ interface AnalysisResult {
51
79
  *
52
80
  * Then post-processes: monomorphize → resolve wire types → topo sort.
53
81
  */
54
- declare function analyze(code: string, filePath: string): AnalysisResult;
82
+ declare function analyze(code: string, filePath: string, imported?: ImportedDefinitions): AnalysisResult;
55
83
  /**
56
84
  * Backward-compatible wrapper: returns only the MessageRegistry.
57
85
  * Uses the same single-walk analysis internally.
58
86
  */
59
- declare function analyzeSource(code: string, filePath: string): MessageRegistry;
87
+ declare function analyzeSource(code: string, filePath: string, imported?: ImportedDefinitions): MessageRegistry;
60
88
 
61
89
  /**
62
90
  * Generate fully self-contained encode/decode source code.
@@ -83,4 +111,4 @@ declare function replaceCallSites(code: string, registry: MessageRegistry): {
83
111
 
84
112
  declare function protobufVitePlugin(): Plugin;
85
113
 
86
- export { analyze, analyzeSource, applyReplacements, protobufVitePlugin as default, generateCode, replaceCallSites, typeNodeToMangledName };
114
+ export { analyze, analyzeSource, applyReplacements, protobufVitePlugin as default, generateCode, replaceCallSites, resolveImports, typeNodeToMangledName };
package/dist/index.js CHANGED
@@ -166,13 +166,30 @@ function resolveTypeArg(node, sf, templates, out) {
166
166
  }
167
167
 
168
168
  // src/ast/analyzer.ts
169
- function analyze(code, filePath) {
169
+ function analyze(code, filePath, imported) {
170
170
  const sf = ts4.createSourceFile(filePath, code, ts4.ScriptTarget.Latest, true);
171
171
  const concrete = [];
172
172
  const templates = /* @__PURE__ */ new Map();
173
173
  const mono = /* @__PURE__ */ new Map();
174
174
  const callSites = [];
175
175
  const deferredTypeArgs = [];
176
+ if (imported) {
177
+ concrete.push(...imported.concrete);
178
+ for (const [k, v] of imported.templates) templates.set(k, v);
179
+ }
180
+ const CANONICAL = /* @__PURE__ */ new Set(["protobuf_encode", "protobuf_decode"]);
181
+ const aliasToCanonical = /* @__PURE__ */ new Map();
182
+ for (const stmt of sf.statements) {
183
+ if (!ts4.isImportDeclaration(stmt) || !stmt.importClause) continue;
184
+ const bindings = stmt.importClause.namedBindings;
185
+ if (!bindings || !ts4.isNamedImports(bindings)) continue;
186
+ for (const el of bindings.elements) {
187
+ const originalName = (el.propertyName ?? el.name).text;
188
+ if (CANONICAL.has(originalName)) {
189
+ aliasToCanonical.set(el.name.text, originalName);
190
+ }
191
+ }
192
+ }
176
193
  ts4.forEachChild(sf, function visit(node) {
177
194
  if (ts4.isInterfaceDeclaration(node)) {
178
195
  if (node.typeParameters?.length) {
@@ -185,16 +202,19 @@ function analyze(code, filePath) {
185
202
  }
186
203
  if (ts4.isCallExpression(node)) {
187
204
  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
- });
205
+ if (ts4.isIdentifier(e)) {
206
+ const canonical = CANONICAL.has(e.text) ? e.text : aliasToCanonical.get(e.text);
207
+ if (canonical) {
208
+ const ta = node.typeArguments;
209
+ if (ta?.length) {
210
+ deferredTypeArgs.push(ta[0]);
211
+ callSites.push({
212
+ fnName: canonical,
213
+ exprStart: e.getStart(sf),
214
+ typeArgsEnd: ta.end + 1,
215
+ firstTypeArg: ta[0]
216
+ });
217
+ }
198
218
  }
199
219
  }
200
220
  }
@@ -219,8 +239,8 @@ function analyze(code, filePath) {
219
239
  }
220
240
  return { registry: topoSort(concrete), callSites, sourceFile: sf };
221
241
  }
222
- function analyzeSource(code, filePath) {
223
- return analyze(code, filePath).registry;
242
+ function analyzeSource(code, filePath, imported) {
243
+ return analyze(code, filePath, imported).registry;
224
244
  }
225
245
  function topoSort(messages) {
226
246
  const map = new Map(messages.map((m) => [m.name, m]));
@@ -1107,18 +1127,34 @@ function applyReplacements(code, sf, callSites, registry) {
1107
1127
  function replaceCallSites(code, registry) {
1108
1128
  const sf = ts5.createSourceFile("input.ts", code, ts5.ScriptTarget.Latest, true);
1109
1129
  const callSites = [];
1130
+ const CANONICAL = /* @__PURE__ */ new Set(["protobuf_encode", "protobuf_decode"]);
1131
+ const aliasToCanonical = /* @__PURE__ */ new Map();
1132
+ for (const stmt of sf.statements) {
1133
+ if (!ts5.isImportDeclaration(stmt) || !stmt.importClause) continue;
1134
+ const bindings = stmt.importClause.namedBindings;
1135
+ if (!bindings || !ts5.isNamedImports(bindings)) continue;
1136
+ for (const el of bindings.elements) {
1137
+ const originalName = (el.propertyName ?? el.name).text;
1138
+ if (CANONICAL.has(originalName)) {
1139
+ aliasToCanonical.set(el.name.text, originalName);
1140
+ }
1141
+ }
1142
+ }
1110
1143
  ts5.forEachChild(sf, function visit(node) {
1111
1144
  if (ts5.isCallExpression(node)) {
1112
1145
  const e = node.expression;
1113
- if (ts5.isIdentifier(e) && (e.text === "protobuf_encode" || e.text === "protobuf_decode")) {
1114
- const ta = node.typeArguments;
1115
- if (ta?.length) {
1116
- callSites.push({
1117
- fnName: e.text,
1118
- exprStart: e.getStart(sf),
1119
- typeArgsEnd: ta.end + 1,
1120
- firstTypeArg: ta[0]
1121
- });
1146
+ if (ts5.isIdentifier(e)) {
1147
+ const canonical = CANONICAL.has(e.text) ? e.text : aliasToCanonical.get(e.text);
1148
+ if (canonical) {
1149
+ const ta = node.typeArguments;
1150
+ if (ta?.length) {
1151
+ callSites.push({
1152
+ fnName: canonical,
1153
+ exprStart: e.getStart(sf),
1154
+ typeArgsEnd: ta.end + 1,
1155
+ firstTypeArg: ta[0]
1156
+ });
1157
+ }
1122
1158
  }
1123
1159
  }
1124
1160
  }
@@ -1127,19 +1163,110 @@ function replaceCallSites(code, registry) {
1127
1163
  return applyReplacements(code, sf, callSites, registry);
1128
1164
  }
1129
1165
 
1166
+ // src/ast/import-resolver.ts
1167
+ import ts6 from "typescript";
1168
+ import { readFileSync, existsSync } from "fs";
1169
+ import { dirname, resolve } from "path";
1170
+ function extractImports(sf) {
1171
+ const result = [];
1172
+ for (const stmt of sf.statements) {
1173
+ if (!ts6.isImportDeclaration(stmt) || !stmt.importClause) continue;
1174
+ const spec = stmt.moduleSpecifier;
1175
+ if (!ts6.isStringLiteral(spec)) continue;
1176
+ const specifier = spec.text;
1177
+ const clause = stmt.importClause;
1178
+ const names = [];
1179
+ if (clause.namedBindings && ts6.isNamedImports(clause.namedBindings)) {
1180
+ for (const el of clause.namedBindings.elements) {
1181
+ names.push(el.name.text);
1182
+ }
1183
+ }
1184
+ if (names.length > 0) result.push({ names, specifier });
1185
+ }
1186
+ return result;
1187
+ }
1188
+ function resolveModulePath(specifier, importerPath) {
1189
+ if (!specifier.startsWith(".")) return null;
1190
+ const base = resolve(dirname(importerPath), specifier);
1191
+ if (existsSync(base) && !base.endsWith(".ts") === false) return base;
1192
+ if (base.endsWith(".ts") && existsSync(base)) return base;
1193
+ const withTs = base + ".ts";
1194
+ if (existsSync(withTs)) return withTs;
1195
+ const indexTs = resolve(base, "index.ts");
1196
+ if (existsSync(indexTs)) return indexTs;
1197
+ return null;
1198
+ }
1199
+ function parseFileForDefinitions(absolutePath) {
1200
+ const code = readFileSync(absolutePath, "utf-8");
1201
+ const sf = ts6.createSourceFile(absolutePath, code, ts6.ScriptTarget.Latest, true);
1202
+ const concrete = [];
1203
+ const templates = /* @__PURE__ */ new Map();
1204
+ for (const stmt of sf.statements) {
1205
+ if (!ts6.isInterfaceDeclaration(stmt)) continue;
1206
+ if (stmt.typeParameters?.length) {
1207
+ const tpl = collectGenericInterface(stmt, sf);
1208
+ if (tpl) templates.set(tpl.name, tpl);
1209
+ } else {
1210
+ const msg = collectInterface(stmt, sf);
1211
+ if (msg) concrete.push(msg);
1212
+ }
1213
+ }
1214
+ return { concrete, templates };
1215
+ }
1216
+ function resolveImports(code, importerPath, cache) {
1217
+ const concrete = [];
1218
+ const templates = /* @__PURE__ */ new Map();
1219
+ const visiting = /* @__PURE__ */ new Set();
1220
+ function walk(filePath, fileCode) {
1221
+ const abs = resolve(filePath);
1222
+ if (visiting.has(abs)) return;
1223
+ visiting.add(abs);
1224
+ const src = fileCode ?? readFileSync(abs, "utf-8");
1225
+ const sf = ts6.createSourceFile(abs, src, ts6.ScriptTarget.Latest, true);
1226
+ const imports = extractImports(sf);
1227
+ for (const imp of imports) {
1228
+ const resolved = resolveModulePath(imp.specifier, abs);
1229
+ if (!resolved) continue;
1230
+ let entry = cache.get(resolved);
1231
+ if (!entry) {
1232
+ entry = parseFileForDefinitions(resolved);
1233
+ cache.set(resolved, entry);
1234
+ }
1235
+ for (const msg of entry.concrete) {
1236
+ if (!concrete.some((m) => m.name === msg.name)) {
1237
+ concrete.push(msg);
1238
+ }
1239
+ }
1240
+ for (const [name, tpl] of entry.templates) {
1241
+ if (!templates.has(name)) templates.set(name, tpl);
1242
+ }
1243
+ walk(resolved);
1244
+ }
1245
+ }
1246
+ walk(importerPath, code);
1247
+ return { concrete, templates };
1248
+ }
1249
+
1130
1250
  // src/index.ts
1131
1251
  function protobufVitePlugin() {
1252
+ const fileCache = /* @__PURE__ */ new Map();
1132
1253
  return {
1133
1254
  name: "vite-plugin-protobuf",
1134
1255
  enforce: "pre",
1135
1256
  transform(code, id) {
1136
1257
  if (!id.endsWith(".ts") || id.endsWith(".d.ts")) return null;
1137
- const { registry, callSites, sourceFile } = analyze(code, id);
1138
- if (registry.size === 0) return null;
1258
+ const imported = resolveImports(code, id, fileCache);
1259
+ const { registry, callSites, sourceFile } = analyze(code, id, imported);
1260
+ if (registry.size === 0 && callSites.length === 0) return null;
1139
1261
  const generatedCode = generateCode(registry);
1140
1262
  const { transformedCode, hasReplacements } = applyReplacements(code, sourceFile, callSites, registry);
1141
1263
  if (!hasReplacements && generatedCode === "") return null;
1142
1264
  return { code: generatedCode + "\n" + transformedCode, map: null };
1265
+ },
1266
+ handleHotUpdate({ file }) {
1267
+ if (file.endsWith(".ts")) {
1268
+ fileCache.delete(file);
1269
+ }
1143
1270
  }
1144
1271
  };
1145
1272
  }
@@ -1150,5 +1277,6 @@ export {
1150
1277
  protobufVitePlugin as default,
1151
1278
  generateCode,
1152
1279
  replaceCallSites,
1280
+ resolveImports,
1153
1281
  typeNodeToMangledName
1154
1282
  };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Runtime fallback functions.
3
+ * If the Vite plugin is active, calls to protobuf_encode/decode are replaced
4
+ * at compile-time with generated type-specific functions.
5
+ * If NOT transformed, these fallbacks throw to alert the developer.
6
+ */
7
+ declare function protobuf_encode<T>(_params: T): Uint8Array;
8
+ declare function protobuf_decode<T>(_data: Uint8Array): T;
9
+
10
+ export { protobuf_decode, protobuf_encode };
@@ -0,0 +1,15 @@
1
+ // src/runtime.ts
2
+ function protobuf_encode(_params) {
3
+ throw new Error(
4
+ "protobuf_encode<T>() was not transformed by the protobuf-fastdsl Vite plugin. Make sure protobufVitePlugin() is added to your vite.config.ts plugins array."
5
+ );
6
+ }
7
+ function protobuf_decode(_data) {
8
+ throw new Error(
9
+ "protobuf_decode<T>() was not transformed by the protobuf-fastdsl Vite plugin. Make sure protobufVitePlugin() is added to your vite.config.ts plugins array."
10
+ );
11
+ }
12
+ export {
13
+ protobuf_decode,
14
+ protobuf_encode
15
+ };
package/package.json CHANGED
@@ -1,24 +1,25 @@
1
1
  {
2
2
  "name": "protobuf-fastdsl",
3
- "version": "0.1.2",
3
+ "version": "0.1.5",
4
4
  "type": "module",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
5
+ "main": "dist/runtime.js",
6
+ "types": "protobuf.d.ts",
7
7
  "files": [
8
8
  "dist",
9
9
  "protobuf.d.ts"
10
10
  ],
11
11
  "exports": {
12
12
  ".": {
13
+ "types": "./protobuf.d.ts",
14
+ "import": "./dist/runtime.js"
15
+ },
16
+ "./vite": {
13
17
  "types": "./dist/index.d.ts",
14
18
  "import": "./dist/index.js"
15
- },
16
- "./types": {
17
- "types": "./protobuf.d.ts"
18
19
  }
19
20
  },
20
21
  "scripts": {
21
- "build": "tsup src/index.ts --format esm --dts",
22
+ "build": "tsup src/index.ts src/runtime.ts --format esm --dts",
22
23
  "test": "vitest run",
23
24
  "test:watch": "vitest",
24
25
  "bench": "npx tsx bench/index.ts"
@@ -29,6 +30,9 @@
29
30
  "peerDependencies": {
30
31
  "vite": "^5.0.0 || ^6.0.0 || ^7.0.0"
31
32
  },
33
+ "peerDependenciesMeta": {
34
+ "vite": { "optional": true }
35
+ },
32
36
  "devDependencies": {
33
37
  "@types/node": "^25.4.0",
34
38
  "tsup": "^8.0.0",
package/protobuf.d.ts CHANGED
@@ -1,25 +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;
1
+ // ── Protobuf field markers ────────────────────────────────────────────
2
+ /** Marks a singular protobuf field: `name: pb<fieldNumber, Type>` */
3
+ export type pb<_ProtoNumber extends number, Type> = Type;
4
+ /** Marks a repeated protobuf field: `ids: pb_repeated<fieldNumber, Type>` → Type[] */
5
+ export type pb_repeated<_ProtoNumber extends number, Type> = Type[];
6
+
7
+ // ── Protobuf primitive types ──────────────────────────────────────────
8
+ export type uint_32 = number;
9
+ export type int_32 = number;
10
+ export type uint_64 = bigint;
11
+ export type int_64 = bigint;
12
+ export type sint_32 = number;
13
+ export type sint_64 = bigint;
14
+ export type bool = boolean;
15
+ export type float = number;
16
+ export type double = number;
17
+ export type fixed_32 = number;
18
+ export type fixed_64 = bigint;
19
+ export type sfixed_32 = number;
20
+ export type sfixed_64 = bigint;
21
+ export type bytes = Uint8Array;
22
+
23
+ // ── Encode / decode (replaced at compile-time by the vite plugin) ────
24
+ export function protobuf_encode<T>(params: T): Uint8Array;
25
+ export function protobuf_decode<T>(data: Uint8Array): T;