Skip to content

Commit

Permalink
feat: adds new ts sdk generator and basic primitives
Browse files Browse the repository at this point in the history
  • Loading branch information
stalniy committed Feb 19, 2025
1 parent 8f661c1 commit 93bc561
Show file tree
Hide file tree
Showing 182 changed files with 1,188 additions and 55,440 deletions.
31 changes: 22 additions & 9 deletions buf.gen.ts.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
version: v1
# Learn more: https://buf.build/docs/configuration/v2/buf-gen-yaml
version: v2
clean: true
plugins:
- name: ts
- local: ./ts/node_modules/.bin/protoc-gen-es
out: ts/src/gen/protos
strategy: all
path: ./ts/node_modules/.bin/protoc-gen-ts_proto
out: ./ts/src/generated
opt: "esModuleInterop=true,forceLong=long,outputTypeRegistry=true,useExactTypes=false,exportCommonSymbols=false"
- name: grpc-gateway-ts
path: ./ts/node_modules/.bin/protoc-gen-ts_proto
out: ./.cache/tmp/ts/generated-grpc-js
opt: "esModuleInterop=true,forceLong=long,useExactTypes=false,exportCommonSymbols=false,outputServices=grpc-js"
include_imports: true
include_wkt: true
opt:
- target=ts
- json_types=true
- local: ./ts/bin/protoc-sdk-object.ts
out: ts/src/gen
strategy: all
include_imports: true
include_wkt: true
opt:
- target=ts

inputs:
- module: buf.build/cosmos/cosmos-sdk
- directory: ./proto/node
- directory: ./proto/provider
35 changes: 3 additions & 32 deletions script/protocgen.sh
Original file line number Diff line number Diff line change
Expand Up @@ -109,42 +109,13 @@ function ts_patches() {

function gen_ts() {
function cleanup_ts {
rm -rf "${AKASH_DEVCACHE_TMP_TS_GRPC_JS}"
rm -rf "${AKASH_DEVCACHE_TMP_TS_PATCHES}"
rm -rf "./ts/src/gen"
}

trap cleanup_ts EXIT ERR

mkdir -p "${AKASH_DEVCACHE_TMP_TS_GRPC_JS}"
mkdir -p "${AKASH_DEVCACHE_TMP_TS_PATCHES}"

ts_patches preserve

gen buf.gen.ts.yaml

local ts_grpc_js_services

# merge generated grpc-js services to the main generated directory
ts_grpc_js_services=$(find "$AKASH_DEVCACHE_TMP_TS_GRPC_JS" -name 'service.ts')

for file in $ts_grpc_js_services; do
dest_path=$(dirname "${file/$AKASH_DEVCACHE_TMP_TS_GRPC_JS/$AKASH_TS_ROOT\/src\/generated}")
dest_file="${dest_path}/service.grpc-js.ts"

mv "$file" "$dest_file"

path_from_gen_dir=${dest_file#"${AKASH_TS_ROOT}/src/generated/"}
index_file_name_base=${path_from_gen_dir%/service.grpc-js.ts}
index_file_name="index.${index_file_name_base//\//.}.grpc-js.ts"
index_file_path="${AKASH_TS_ROOT}/src/generated/$index_file_name"
export_statement="export * from \"./${path_from_gen_dir%.ts}\";"

echo "$export_statement" >"$index_file_path"
done

ts_patches restore

npm run format --prefix "$AKASH_TS_ROOT"
(cd ts && npm ci)
buf generate --template buf.gen.ts.yaml
}

function gen_doc() {
Expand Down
35 changes: 0 additions & 35 deletions ts/.eslintrc.json

This file was deleted.

1 change: 1 addition & 0 deletions ts/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/build
/node_modules
tsconfig.paths.json
/src/gen/

# Logs
logs
Expand Down
19 changes: 0 additions & 19 deletions ts/.npmignore

This file was deleted.

1 change: 0 additions & 1 deletion ts/.npmrc

This file was deleted.

3 changes: 0 additions & 3 deletions ts/.prettierrc

This file was deleted.

32 changes: 0 additions & 32 deletions ts/.releaserc

This file was deleted.

59 changes: 0 additions & 59 deletions ts/README.md

This file was deleted.

147 changes: 147 additions & 0 deletions ts/bin/protoc-sdk-object.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#!/usr/bin/env npx ts-node -T

import { DescMethod, DescService } from "@bufbuild/protobuf";
import {
createEcmaScriptPlugin,
runNodeJs,
type Schema,
} from "@bufbuild/protoplugin";

runNodeJs(
createEcmaScriptPlugin({
name: "protoc-gen-sdk-object",
version: "v1",
generateTs,
})
);

function generateTs(schema: Schema): void {
const servicesLoaderDefs: string[] = [];
const sdkDefs: Record<string, string> = {};
const imports = new Set<string>();

for (const file of schema.files) {
if (!file.services.length) continue;

file.services.forEach((service) => {
servicesLoaderDefs.push(`() => import('./protos/${file.name}_pb').then(m => m.${service.name})`);
const serviceIndex = servicesLoaderDefs.length - 1;
const serviceMethods = service.methods.map((method, methodIndex) => {
const inputImportPath = `${method.input.file.name}_pb`;
const outputImportPath = `${method.output.file.name}_pb`;
imports.add(inputImportPath).add(outputImportPath);
const methodArgs = [
method.input.name === 'Empty' ? 'input = {}' : `input: ${inputImportPath.replace(/\W+/g, '_')}.${method.input.name}Json`,
`options?: ${isTxService(service) ? 'TxCallOptions' : 'RpcCallOptions'}`
];
const methodName = getSdkMethodName(service, method);

return `${methodName}: withMetadata(async function ${methodName}(${methodArgs.join(', ')}) {\n` +
` const service = await serviceLoader.load(${serviceIndex});\n` +
` return sdk.getClient(service).${decapitalize(method.name)}(input, options);\n` +
`}, { path: [${serviceIndex}, ${methodIndex}] })`
;
});

if (serviceMethods.length > 0) {
const path = service.file.proto.package;
const tabSize = path.split('.').length;
const methods = indent(serviceMethods.join(',\n'), ' '.repeat(tabSize + 1));
const methodsTab = ' '.repeat(tabSize);
const existingValue = getByPath(sdkDefs, path);
if (existingValue) {
const value = existingValue.slice(0, -1).trim() + `,\n${methods}\n${methodsTab}}`;
setByPath(sdkDefs, path, value);
} else {
setByPath(sdkDefs, path, `{\n${methods}\n${methodsTab}}`);
}
}
});
}

const f = schema.generateFile(getOutputFileName(schema));

Array.from(imports).forEach(importPath => {
f.print(`import type * as ${importPath.replace(/\W+/g, '_')} from './protos/${importPath}'`);
});
f.print(`import type { BaseSDK } from '../sdk/BaseSDK';`);
f.print(`import type { RpcCallOptions, TxCallOptions } from '../transport';`);
f.print(`import { createServiceLoader } from '../utils/createServiceLoader';`);
f.print(`import { withMetadata } from '../utils/sdkMetadata';`);
f.print('\n');
f.print(f.export('const', `serviceLoader = createServiceLoader([\n${indent(servicesLoaderDefs.join(',\n'))}\n] as const);`));
f.print(f.export('function', `createSDK<T extends BaseSDK>(sdk: T) {\n return ${indent(stringifyObject(sdkDefs)).trim()}\n}`));
}

function getOutputFileName(schema: Schema): string {
for (const file of schema.files) {
if (file.name.includes('akash/provider/lease')) {
return 'createProviderSDK.ts';
}
if (file.name.includes('akash/cert/v1/msg')) {
return 'createNodeSDK.ts';
}
if (file.name.includes('cosmos/base/tendermint/v1beta1/query') || file.name.includes('cosmos/base/query/v1/query')) {
return 'createCosmosSDK.ts';
}
}

throw new Error("Cannot determine sdk file name");
}

function getByPath(obj: Record<string, any>, path: string) {
const parts = path.split('.');
let current = obj;
for (let i = 0; i < parts.length; i++) {
if (current === undefined) return;
current = current[parts[i]];

}
return current;
}

function setByPath(obj: Record<string, any>, path: string, value: unknown) {
const parts = path.split('.');
let current = obj;
for (let i = 0; i < parts.length - 1; i++) {
const part = parts[i];
if (!(part in current)) {
current[part] = {};
}
current = current[part];
}
current[parts[parts.length - 1]] = value;
};

const indent = (value: string, tab = ' '.repeat(2)) => tab + value.replace(/\n/g, '\n' + tab);
const isTxService = (service: DescService) => service.name === 'Msg';

function getSdkMethodName(service: DescService, method: DescMethod) {
if (isTxService(service) || method.name.startsWith('get') || method.name.startsWith('Get')) {
return decapitalize(method.name);
}

return `get${capitalize(method.name)}`;
}

function capitalize(str: string): string {
return str[0].toUpperCase() + str.slice(1);
}

function decapitalize(str: string): string {
return str[0].toLowerCase() + str.slice(1);
}

function stringifyObject(obj: Record<string, any>, tabSize = 0, wrap = (value: string) => value): string {
if (typeof obj !== 'object') return obj;

const spaces = ' '.repeat(tabSize);
const entries = Object.entries(obj).map(([key, value]) => {
if (typeof value === 'string') {
return `${spaces} ${key}: ${wrap(value)}`;
}
return `${spaces} ${key}: ${stringifyObject(value, tabSize + 2, wrap)}`;
});

return `{\n${entries.join(',\n')}\n${spaces}}`;
}
Loading

0 comments on commit 93bc561

Please sign in to comment.