Skip to content

Commit e5d5284

Browse files
committed
refactor(ts): moves type patching on message type level
1 parent 55ca705 commit e5d5284

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+362
-1873
lines changed

ts/script/fix-ts-proto-generated-types.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ const helperTypeRegex = new RegExp(
1515
);
1616

1717
const ROOT_DIR = resolvePath(import.meta.dirname, "..", "src");
18+
19+
const typesToPatch = new Set<string>();
20+
for await (const patchFile of fs.glob(`${ROOT_DIR}/generated/patches/*CustomTypePatches.ts`)) {
21+
const { patches } = await import(patchFile);
22+
Object.keys(patches).forEach((key) => typesToPatch.add(key));
23+
}
24+
1825
for await (const path of fs.glob(`${ROOT_DIR}/generated/protos/**/*.ts`)) {
1926
const source = await fs.readFile(path, "utf8");
2027
let newSource = source;
@@ -23,6 +30,8 @@ for await (const path of fs.glob(`${ROOT_DIR}/generated/protos/**/*.ts`)) {
2330
newSource = newSource.replace(/^\s*create\(base\?:\s*DeepPartial<\w+>\):\s*\w+\s*\{\s*return\s*\w+\.fromPartial\(base \?\? \{\}\);\s*\},?\n?/gm, "");
2431
newSource = injectOwnHelpers(newSource, path);
2532

33+
newSource = applyPatching(newSource, path, typesToPatch);
34+
2635
if (newSource !== source) {
2736
await fs.writeFile(path, newSource);
2837
}
@@ -50,3 +59,27 @@ function injectOwnHelpers(source: string, path: string) {
5059

5160
return importHelpers + importTypeHelpers + source;
5261
}
62+
63+
function applyPatching(source: string, filePath: string, typesToPatch: Set<string>) {
64+
const imports = new Set<string>();
65+
const exports: string[] = [];
66+
67+
const newSource = source.replace(
68+
/^export const (\w+)(:\s*MessageFns<[^>]+,\s*["']([^"']+)["']>\s*=)/gm,
69+
(match, symbolName, typeAnnotation, fullName) => {
70+
if (!typesToPatch.has(fullName)) return match;
71+
72+
const namespace = fullName.split(".")[0];
73+
const prefix = namespace === "akash" ? "node" : namespace;
74+
const importPath = relativePath(filePath, `${ROOT_DIR}/generated/protos/patches/${prefix}PatchMessage.ts`);
75+
imports.add(`import { patched } from "${importPath}";`);
76+
exports.push(`export const ${symbolName} = patched(_${symbolName});`);
77+
78+
return `const _${symbolName}${typeAnnotation}`;
79+
},
80+
);
81+
82+
if (!exports.length) return source;
83+
84+
return Array.from(imports).join("\n") + "\n" + newSource + "\n" + exports.join("\n") + "\n";
85+
}

ts/script/protoc-gen-customtype-patches.ts

Lines changed: 58 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,48 @@ import { basename, normalize as normalizePath } from "path";
1212

1313
import { findPathsToCustomField, getCustomType } from "../src/encoding/customTypes/utils.ts";
1414

15-
runNodeJs(
16-
createEcmaScriptPlugin({
17-
name: "protoc-gen-customtype-patches",
18-
version: "v1",
19-
generateTs,
20-
}),
21-
);
15+
export interface PluginOptions {
16+
/**
17+
* if true, we will patch the whole tree of the message type, starting from the custom field type and up to the root
18+
* in case of patching ts-proto generated types which has self-references, we need to patch only leaf level
19+
* @default false
20+
*/
21+
patchWholeTree: boolean;
22+
}
23+
24+
runNodeJs(createEcmaScriptPlugin<PluginOptions>({ name: "protoc-gen-customtype-patches", version: "v1", parseOptions, generateTs }));
2225

2326
const PROTO_PATH = "../protos";
24-
function generateTs(schema: Schema): void {
27+
28+
function parseOptions(rawOptions: Array<{
29+
key: string;
30+
value: string;
31+
}>): PluginOptions {
32+
const options: PluginOptions = {
33+
patchWholeTree: false,
34+
};
35+
36+
for (const { key, value } of rawOptions) {
37+
if (key === "patch_whole_tree") {
38+
options.patchWholeTree = value === "true";
39+
}
40+
}
41+
42+
return options;
43+
}
44+
45+
function generateTs(schema: Schema<PluginOptions>): void {
2546
const allPaths: DescField[][] = [];
2647

2748
schema.files.forEach((file) => {
2849
file.messages.forEach((message) => {
2950
const paths = findPathsToCustomField(message, () => true);
30-
allPaths.push(...paths);
51+
if (schema.options.patchWholeTree) {
52+
allPaths.push(...paths);
53+
} else {
54+
const leaves = paths.map((path) => path.slice(-1));
55+
allPaths.push(...leaves);
56+
}
3157
});
3258
});
3359
if (!allPaths.length) {
@@ -100,6 +126,24 @@ function generateTs(schema: Schema): void {
100126
patchesFile.print(`const p = {\n${indent(patches.join(",\n"))}\n};\n`);
101127
patchesFile.print(`export const patches = p;`);
102128

129+
const patchesTypeFileName = fileName.replace("CustomTypePatches", "PatchMessage");
130+
const patchTypeFile = schema.generateFile(patchesTypeFileName);
131+
patchTypeFile.print(`import { patches } from "./${fileName}";`);
132+
patchTypeFile.print(`import type { MessageDesc } from "../../sdk/client/types.ts";`);
133+
patchTypeFile.print(`export const patched = <T extends MessageDesc>(messageDesc: T): T => {`);
134+
patchTypeFile.print(` const patchMessage = patches[messageDesc.$type as keyof typeof patches] as any;`);
135+
patchTypeFile.print(` if (!patchMessage) return messageDesc;`);
136+
patchTypeFile.print(` return {`);
137+
patchTypeFile.print(` ...messageDesc,`);
138+
patchTypeFile.print(` encode(message, writer) {`);
139+
patchTypeFile.print(` return messageDesc.encode(patchMessage(message, 'encode'), writer);`);
140+
patchTypeFile.print(` },`);
141+
patchTypeFile.print(` decode(input, length) {`);
142+
patchTypeFile.print(` return patchMessage(messageDesc.decode(input, length), 'decode');`);
143+
patchTypeFile.print(` },`);
144+
patchTypeFile.print(` };`);
145+
patchTypeFile.print(`};`);
146+
103147
const testsFile = schema.generateFile(fileName.replace(/\.ts$/, ".spec.ts"));
104148
generateTests(basename(fileName), testsFile, messageToCustomFields);
105149
}
@@ -185,13 +229,13 @@ function generateTests(fileName: string, testsFile: GeneratedFile, messageToCust
185229
testsFile.print(`import { expect, describe, it } from "@jest/globals";`);
186230
testsFile.print(`import { patches } from "./${basename(fileName)}";`);
187231
testsFile.print(`import { generateMessage, type MessageSchema } from "@test/helpers/generateMessage";`);
188-
testsFile.print(`import type { TypePatches } from "../../sdk/client/applyPatches.ts";`);
232+
testsFile.print(`import type { TypePatches } from "../../sdk/client/types.ts";`);
189233
testsFile.print("");
190234
testsFile.print(`const messageTypes: Record<string, MessageSchema> = {`);
191235
for (const [message, fields] of messageToCustomFields.entries()) {
192236
testsFile.print(` "${message.typeName}": {`);
193237
testsFile.print(` type: `, testsFile.import(message.name, `${PROTO_PATH}/${message.file.name}.ts`), `,`);
194-
testsFile.print(` fields: [`, ...Array.from(fields, f => serializeField(f, testsFile)), `],`);
238+
testsFile.print(` fields: [`, ...Array.from(fields, (f) => serializeField(f, testsFile)), `],`);
195239
testsFile.print(` },`);
196240
}
197241
testsFile.print(`};`);
@@ -236,7 +280,7 @@ function serializeField(f: DescField, file: GeneratedFile): Printable {
236280
field.push(`scalarType: ${f.scalar},`);
237281
}
238282
if (f.fieldKind === "enum") {
239-
field.push(`enum: `, JSON.stringify(f.enum.values.map(v => v.localName)), `,`);
283+
field.push(`enum: `, JSON.stringify(f.enum.values.map((v) => v.localName)), `,`);
240284
}
241285
if (getCustomType(f)) {
242286
field.push(`customType: "${getCustomType(f)!.shortName}",`);
@@ -246,10 +290,10 @@ function serializeField(f: DescField, file: GeneratedFile): Printable {
246290
}
247291
if (f.message) {
248292
field.push(`message: {fields: [`,
249-
...f.message.fields.map(nf => serializeField(nf, file)),
293+
...f.message.fields.map((nf) => serializeField(nf, file)),
250294
`],`,
251295
`type: `, file.import(f.message.name, `${PROTO_PATH}/${f.message.file.name}.ts`),
252-
`},`
296+
`},`,
253297
);
254298
}
255299
field.push(`},`);

ts/script/protoc-gen-sdk-object.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,17 +90,17 @@ function generateTs(schema: Schema): void {
9090
f.export("const", "serviceLoader"),
9191
`= `,
9292
f.import("createServiceLoader", `../sdk/client/createServiceLoader${importExtension}`),
93-
`([\n${indent(servicesLoaderDefs.join(",\n"))}\n] as const);`
93+
`([\n${indent(servicesLoaderDefs.join(",\n"))}\n] as const);`,
9494
);
9595

9696
const factoryArgs = hasMsgService
9797
? `queryTransport: Transport, txTransport: Transport`
9898
: `transport: Transport`;
9999
f.print(
100100
f.export("function", "createSDK"),
101-
`(${factoryArgs}, options?: `, f.import("SDKOptions", `../sdk/types${importExtension}`), `) {\n`,
102-
` const getClient = createClientFactory<CallOptions>(${hasMsgService ? "queryTransport" : "transport"}, options?.clientOptions);\n`,
103-
(hasMsgService ? ` const getMsgClient = createClientFactory<TxCallOptions>(txTransport, options?.clientOptions);\n` : ""),
101+
`(${factoryArgs}) {\n`,
102+
` const getClient = createClientFactory<CallOptions>(${hasMsgService ? "queryTransport" : "transport"});\n`,
103+
(hasMsgService ? ` const getMsgClient = createClientFactory<TxCallOptions>(txTransport);\n` : ""),
104104
` return ${indent(stringifyObject(sdkDefs)).trim()};\n`,
105105
`}`,
106106
);
@@ -227,7 +227,7 @@ function findExtension(schema: Schema, typeName: string) {
227227
return extensionsCache[typeName];
228228
}
229229
230-
const serviceFiles: Record<string, GeneratedFile> = {};
230+
const serviceFiles: Record<string, GeneratedFile> = {};
231231
function generateServiceDefs(service: DescService, schema: Schema) {
232232
const importExtension = schema.options.importExtension ? `.${schema.options.importExtension}` : "";
233233
const serviceFilePath = `${service.file.name}_akash.ts`;
@@ -243,10 +243,10 @@ function generateServiceDefs(service: DescService, schema: Schema) {
243243
service.methods.forEach((method) => {
244244
file.print(` ${method.localName}: {`);
245245
file.print(` name: "${method.name}",`);
246-
if (method.methodKind !== "unary") file.print(` kind: "${method.methodKind}",`);
246+
if (method.methodKind !== "unary") file.print(` kind: "${method.methodKind}",`);
247247
if (httpExtension && hasOption(method, httpExtension)) {
248248
const httpOption = getOption(method, httpExtension) as {
249-
pattern: { case: string, value: string };
249+
pattern: { case: string; value: string };
250250
};
251251
if (httpOption.pattern.case !== "get") file.print(` httpMethod: "${httpOption.pattern.case}",`);
252252

ts/script/protoc-gen-type-index-files.ts

Lines changed: 10 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
#!/usr/bin/env -S node --experimental-strip-types --no-warnings
22

3-
import { type DescEnum, type DescField, type DescMessage } from "@bufbuild/protobuf";
3+
import { type DescEnum, type DescMessage } from "@bufbuild/protobuf";
44
import {
55
createEcmaScriptPlugin,
66
type GeneratedFile,
77
runNodeJs,
8-
type Schema
8+
type Schema,
99
} from "@bufbuild/protoplugin";
10-
import { findPathsToCustomField } from "../src/encoding/customTypes/utils.ts";
1110

1211
runNodeJs(
1312
createEcmaScriptPlugin({
@@ -18,92 +17,41 @@ runNodeJs(
1817
);
1918

2019
function generateTs(schema: Schema): void {
21-
const allCustomTypeFieldPaths: DescField[][] = [];
22-
23-
schema.files.forEach((file) => {
24-
file.messages.forEach((message) => {
25-
const paths = findPathsToCustomField(message, () => true);
26-
allCustomTypeFieldPaths.push(...paths);
27-
});
28-
});
29-
30-
const typesNamesToPatch = new Set<string>();
31-
allCustomTypeFieldPaths.forEach((path) => {
32-
path.forEach((field) => {
33-
typesNamesToPatch.add(field.parent.typeName);
34-
});
35-
});
3620
const protoSource = process.env.PROTO_SOURCE;
3721
if (!protoSource) {
3822
throw new Error("PROTO_SOURCE is required and should be set to 'node', 'provider', or 'cosmos'");
3923
}
40-
const patchesFileName = `${protoSource}PatchMessage.ts`;
41-
42-
if (typesNamesToPatch.size > 0) {
43-
const patchesFile = schema.generateFile(patchesFileName);
44-
patchesFile.print(`import { patches } from "../patches/${process.env.PROTO_SOURCE}CustomTypePatches.ts";`);
45-
patchesFile.print(`import type { MessageDesc } from "../../sdk/client/types.ts";`);
46-
patchesFile.print(`export const patched = <T extends MessageDesc>(messageDesc: T): T => {`);
47-
patchesFile.print(` const patchMessage = patches[messageDesc.$type as keyof typeof patches] as any;`);
48-
patchesFile.print(` if (!patchMessage) return messageDesc;`);
49-
patchesFile.print(` return {`);
50-
patchesFile.print(` ...messageDesc,`);
51-
patchesFile.print(` encode(message, writer) {`);
52-
patchesFile.print(` return messageDesc.encode(patchMessage(message, 'encode'), writer);`);
53-
patchesFile.print(` },`);
54-
patchesFile.print(` decode(input, length) {`);
55-
patchesFile.print(` return patchMessage(messageDesc.decode(input, length), 'decode');`);
56-
patchesFile.print(` },`);
57-
patchesFile.print(` };`);
58-
patchesFile.print(`};`);
59-
}
6024

6125
const indexFiles: Record<string, {
6226
file: GeneratedFile;
6327
symbols: Set<string>;
6428
}> = {};
65-
const namespacePrefix = protoSource === 'provider' ? 'provider.' : '';
29+
const namespacePrefix = protoSource === "provider" ? "provider." : "";
6630
schema.files.forEach((file) => {
67-
const packageParts = file.proto.package.split('.');
31+
const packageParts = file.proto.package.split(".");
6832
const namespace = namespacePrefix + packageParts[0];
6933
const version = packageParts.at(-1);
7034
const path = `index.${namespace}.${version}.ts`;
7135
indexFiles[path] ??= {
7236
file: schema.generateFile(path),
7337
symbols: new Set(),
7438
};
75-
const {file: indexFile, symbols: fileSymbols} = indexFiles[path];
39+
const { file: indexFile, symbols: fileSymbols } = indexFiles[path];
7640

77-
const typesToPatch: Array<{ exportedName: string; name: string }> = [];
7841
const typesToExport: Array<{ exportedName: string; name: string }> = [];
7942
for (const type of schema.typesInFile(file)) {
80-
if (type.kind === 'service' || type.kind === 'extension') continue;
43+
if (type.kind === "service" || type.kind === "extension") continue;
8144

8245
const name = genName(type);
8346
const exportedName = fileSymbols.has(name) ? genUniqueName(type, fileSymbols) : name;
8447
fileSymbols.add(exportedName);
85-
86-
if (type.kind === "message" && typesNamesToPatch.has(type.typeName)) {
87-
typesToPatch.push({ exportedName, name });
88-
} else {
89-
typesToExport.push({ exportedName, name });
90-
}
48+
typesToExport.push({ exportedName, name });
9149
}
9250

9351
if (typesToExport.length > 0) {
94-
const symbolsToExport = typesToExport.map(type => type.exportedName === type.name ? type.exportedName : `${type.name} as ${type.exportedName}`).join(", ");
52+
const symbolsToExport = typesToExport.map((type) => type.exportedName === type.name ? type.exportedName : `${type.name} as ${type.exportedName}`).join(", ");
9553
indexFile.print(`export { ${symbolsToExport} } from "./${file.name}.ts";`);
9654
}
97-
98-
if (typesToPatch.length > 0) {
99-
const symbolsToPatch = typesToPatch.map((type) => `${type.name} as _${type.exportedName}`).join(", ");
100-
indexFile.print('');
101-
indexFile.print(`import { ${symbolsToPatch} } from "./${file.name}.ts";`);
102-
for (const type of typesToPatch) {
103-
indexFile.print(`export const ${type.exportedName} = `, indexFile.import('patched', `./${patchesFileName}`),`(_${type.exportedName});`);
104-
indexFile.print(`export type ${type.exportedName} = _${type.exportedName}`);
105-
}
106-
}
10755
});
10856
}
10957

@@ -115,8 +63,8 @@ let uniqueNameCounter = 0;
11563
function genUniqueName(type: DescMessage | DescEnum, allSymbols: Set<string>, attempt = 0): string {
11664
const name = genName(type);
11765
if (allSymbols.has(name)) {
118-
const packageParts = type.file.proto.package.split('.');
119-
const prefix = packageParts.slice(-2 - attempt, -1).map(capitalize).join('_');
66+
const packageParts = type.file.proto.package.split(".");
67+
const prefix = packageParts.slice(-2 - attempt, -1).map(capitalize).join("_");
12068
let newName = `${prefix}_${name}`;
12169
if (newName === name) {
12270
newName = `${prefix}_${name}_${uniqueNameCounter++}`;

ts/src/generated/createCosmosSDK.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { createServiceLoader } from "../sdk/client/createServiceLoader.ts";
2-
import { SDKOptions } from "../sdk/types.ts";
32

43
import type * as cosmos_app_v1alpha1_query from "./protos/cosmos/app/v1alpha1/query.ts";
54
import type * as cosmos_auth_v1beta1_query from "./protos/cosmos/auth/v1beta1/query.ts";
@@ -111,9 +110,9 @@ export const serviceLoader= createServiceLoader([
111110
() => import("./protos/cosmos/upgrade/v1beta1/tx_akash.ts").then(m => m.Msg),
112111
() => import("./protos/cosmos/vesting/v1beta1/tx_akash.ts").then(m => m.Msg)
113112
] as const);
114-
export function createSDK(queryTransport: Transport, txTransport: Transport, options?: SDKOptions) {
115-
const getClient = createClientFactory<CallOptions>(queryTransport, options?.clientOptions);
116-
const getMsgClient = createClientFactory<TxCallOptions>(txTransport, options?.clientOptions);
113+
export function createSDK(queryTransport: Transport, txTransport: Transport) {
114+
const getClient = createClientFactory<CallOptions>(queryTransport);
115+
const getMsgClient = createClientFactory<TxCallOptions>(txTransport);
117116
return {
118117
cosmos: {
119118
app: {

ts/src/generated/createIbc-goSDK.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { createServiceLoader } from "../sdk/client/createServiceLoader.ts";
2-
import { SDKOptions } from "../sdk/types.ts";
32

43
import type * as ibc_applications_interchain_accounts_controller_v1_query from "./protos/ibc/applications/interchain_accounts/controller/v1/query.ts";
54
import type * as ibc_applications_interchain_accounts_controller_v1_tx from "./protos/ibc/applications/interchain_accounts/controller/v1/tx.ts";
@@ -45,9 +44,9 @@ export const serviceLoader= createServiceLoader([
4544
() => import("./protos/ibc/lightclients/wasm/v1/query_akash.ts").then(m => m.Query),
4645
() => import("./protos/ibc/lightclients/wasm/v1/tx_akash.ts").then(m => m.Msg)
4746
] as const);
48-
export function createSDK(queryTransport: Transport, txTransport: Transport, options?: SDKOptions) {
49-
const getClient = createClientFactory<CallOptions>(queryTransport, options?.clientOptions);
50-
const getMsgClient = createClientFactory<TxCallOptions>(txTransport, options?.clientOptions);
47+
export function createSDK(queryTransport: Transport, txTransport: Transport) {
48+
const getClient = createClientFactory<CallOptions>(queryTransport);
49+
const getMsgClient = createClientFactory<TxCallOptions>(txTransport);
5150
return {
5251
ibc: {
5352
applications: {

ts/src/generated/createNodeSDK.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { createServiceLoader } from "../sdk/client/createServiceLoader.ts";
2-
import { SDKOptions } from "../sdk/types.ts";
32

43
import type * as akash_audit_v1_query from "./protos/akash/audit/v1/query.ts";
54
import type * as akash_audit_v1_msg from "./protos/akash/audit/v1/msg.ts";
@@ -41,9 +40,9 @@ export const serviceLoader= createServiceLoader([
4140
() => import("./protos/akash/take/v1/query_akash.ts").then(m => m.Query),
4241
() => import("./protos/akash/take/v1/service_akash.ts").then(m => m.Msg)
4342
] as const);
44-
export function createSDK(queryTransport: Transport, txTransport: Transport, options?: SDKOptions) {
45-
const getClient = createClientFactory<CallOptions>(queryTransport, options?.clientOptions);
46-
const getMsgClient = createClientFactory<TxCallOptions>(txTransport, options?.clientOptions);
43+
export function createSDK(queryTransport: Transport, txTransport: Transport) {
44+
const getClient = createClientFactory<CallOptions>(queryTransport);
45+
const getMsgClient = createClientFactory<TxCallOptions>(txTransport);
4746
return {
4847
akash: {
4948
audit: {

0 commit comments

Comments
 (0)