protobuf-fastdsl 0.1.2 → 0.1.3
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/dist/index.d.ts +31 -3
- package/dist/index.js +157 -23
- package/dist/runtime.d.ts +10 -0
- package/dist/runtime.js +15 -0
- package/package.json +6 -2
- package/protobuf.d.ts +25 -25
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)
|
|
189
|
-
const
|
|
190
|
-
if (
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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)
|
|
1114
|
-
const
|
|
1115
|
-
if (
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
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,116 @@ 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 extractTypeImports(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.isTypeOnly) {
|
|
1180
|
+
if (clause.namedBindings && ts6.isNamedImports(clause.namedBindings)) {
|
|
1181
|
+
for (const el of clause.namedBindings.elements) {
|
|
1182
|
+
names.push(el.name.text);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
} else if (clause.namedBindings && ts6.isNamedImports(clause.namedBindings)) {
|
|
1186
|
+
for (const el of clause.namedBindings.elements) {
|
|
1187
|
+
if (el.isTypeOnly) names.push(el.name.text);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
if (names.length > 0) result.push({ names, specifier });
|
|
1191
|
+
}
|
|
1192
|
+
return result;
|
|
1193
|
+
}
|
|
1194
|
+
function resolveModulePath(specifier, importerPath) {
|
|
1195
|
+
if (!specifier.startsWith(".")) return null;
|
|
1196
|
+
const base = resolve(dirname(importerPath), specifier);
|
|
1197
|
+
if (existsSync(base) && !base.endsWith(".ts") === false) return base;
|
|
1198
|
+
if (base.endsWith(".ts") && existsSync(base)) return base;
|
|
1199
|
+
const withTs = base + ".ts";
|
|
1200
|
+
if (existsSync(withTs)) return withTs;
|
|
1201
|
+
const indexTs = resolve(base, "index.ts");
|
|
1202
|
+
if (existsSync(indexTs)) return indexTs;
|
|
1203
|
+
return null;
|
|
1204
|
+
}
|
|
1205
|
+
function parseFileForDefinitions(absolutePath) {
|
|
1206
|
+
const code = readFileSync(absolutePath, "utf-8");
|
|
1207
|
+
const sf = ts6.createSourceFile(absolutePath, code, ts6.ScriptTarget.Latest, true);
|
|
1208
|
+
const concrete = [];
|
|
1209
|
+
const templates = /* @__PURE__ */ new Map();
|
|
1210
|
+
for (const stmt of sf.statements) {
|
|
1211
|
+
if (!ts6.isInterfaceDeclaration(stmt)) continue;
|
|
1212
|
+
if (stmt.typeParameters?.length) {
|
|
1213
|
+
const tpl = collectGenericInterface(stmt, sf);
|
|
1214
|
+
if (tpl) templates.set(tpl.name, tpl);
|
|
1215
|
+
} else {
|
|
1216
|
+
const msg = collectInterface(stmt, sf);
|
|
1217
|
+
if (msg) concrete.push(msg);
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
return { concrete, templates };
|
|
1221
|
+
}
|
|
1222
|
+
function resolveImports(code, importerPath, cache) {
|
|
1223
|
+
const concrete = [];
|
|
1224
|
+
const templates = /* @__PURE__ */ new Map();
|
|
1225
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
1226
|
+
function walk(filePath, fileCode) {
|
|
1227
|
+
const abs = resolve(filePath);
|
|
1228
|
+
if (visiting.has(abs)) return;
|
|
1229
|
+
visiting.add(abs);
|
|
1230
|
+
const src = fileCode ?? readFileSync(abs, "utf-8");
|
|
1231
|
+
const sf = ts6.createSourceFile(abs, src, ts6.ScriptTarget.Latest, true);
|
|
1232
|
+
const imports = extractTypeImports(sf);
|
|
1233
|
+
for (const imp of imports) {
|
|
1234
|
+
const resolved = resolveModulePath(imp.specifier, abs);
|
|
1235
|
+
if (!resolved) continue;
|
|
1236
|
+
let entry = cache.get(resolved);
|
|
1237
|
+
if (!entry) {
|
|
1238
|
+
entry = parseFileForDefinitions(resolved);
|
|
1239
|
+
cache.set(resolved, entry);
|
|
1240
|
+
}
|
|
1241
|
+
for (const msg of entry.concrete) {
|
|
1242
|
+
if (!concrete.some((m) => m.name === msg.name)) {
|
|
1243
|
+
concrete.push(msg);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
for (const [name, tpl] of entry.templates) {
|
|
1247
|
+
if (!templates.has(name)) templates.set(name, tpl);
|
|
1248
|
+
}
|
|
1249
|
+
walk(resolved);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
walk(importerPath, code);
|
|
1253
|
+
return { concrete, templates };
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1130
1256
|
// src/index.ts
|
|
1131
1257
|
function protobufVitePlugin() {
|
|
1258
|
+
const fileCache = /* @__PURE__ */ new Map();
|
|
1132
1259
|
return {
|
|
1133
1260
|
name: "vite-plugin-protobuf",
|
|
1134
1261
|
enforce: "pre",
|
|
1135
1262
|
transform(code, id) {
|
|
1136
1263
|
if (!id.endsWith(".ts") || id.endsWith(".d.ts")) return null;
|
|
1137
|
-
const
|
|
1264
|
+
const imported = resolveImports(code, id, fileCache);
|
|
1265
|
+
const { registry, callSites, sourceFile } = analyze(code, id, imported);
|
|
1138
1266
|
if (registry.size === 0) return null;
|
|
1139
1267
|
const generatedCode = generateCode(registry);
|
|
1140
1268
|
const { transformedCode, hasReplacements } = applyReplacements(code, sourceFile, callSites, registry);
|
|
1141
1269
|
if (!hasReplacements && generatedCode === "") return null;
|
|
1142
1270
|
return { code: generatedCode + "\n" + transformedCode, map: null };
|
|
1271
|
+
},
|
|
1272
|
+
handleHotUpdate({ file }) {
|
|
1273
|
+
if (file.endsWith(".ts")) {
|
|
1274
|
+
fileCache.delete(file);
|
|
1275
|
+
}
|
|
1143
1276
|
}
|
|
1144
1277
|
};
|
|
1145
1278
|
}
|
|
@@ -1150,5 +1283,6 @@ export {
|
|
|
1150
1283
|
protobufVitePlugin as default,
|
|
1151
1284
|
generateCode,
|
|
1152
1285
|
replaceCallSites,
|
|
1286
|
+
resolveImports,
|
|
1153
1287
|
typeNodeToMangledName
|
|
1154
1288
|
};
|
|
@@ -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 };
|
package/dist/runtime.js
ADDED
|
@@ -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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "protobuf-fastdsl",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -13,12 +13,16 @@
|
|
|
13
13
|
"types": "./dist/index.d.ts",
|
|
14
14
|
"import": "./dist/index.js"
|
|
15
15
|
},
|
|
16
|
+
"./runtime": {
|
|
17
|
+
"types": "./protobuf.d.ts",
|
|
18
|
+
"import": "./dist/runtime.js"
|
|
19
|
+
},
|
|
16
20
|
"./types": {
|
|
17
21
|
"types": "./protobuf.d.ts"
|
|
18
22
|
}
|
|
19
23
|
},
|
|
20
24
|
"scripts": {
|
|
21
|
-
"build": "tsup src/index.ts --format esm --dts",
|
|
25
|
+
"build": "tsup src/index.ts src/runtime.ts --format esm --dts",
|
|
22
26
|
"test": "vitest run",
|
|
23
27
|
"test:watch": "vitest",
|
|
24
28
|
"bench": "npx tsx bench/index.ts"
|
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
|
|
24
|
-
|
|
25
|
-
|
|
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;
|