Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Generate Index Files #821

Merged
merged 7 commits into from
May 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,10 @@ Generated code will be placed in the Gradle build directory.
Extension encode/decode methods are compliant with the `outputEncodeMethods` option, and if `unknownFields=true`,
the `setExtension` and `getExtension` methods will be created for extendable messages, also compliant with `outputEncodeMethods` (setExtension = encode, getExtension = decode).

- With `--ts_proto_opt=outputIndex=true`, index files will be generated based on the proto package namespaces.

This will disable `exportCommonSymbols` to avoid name collisions on the common symbols.

### NestJS Support

We have a great way of working together with [nestjs](https://docs.nestjs.com/microservices/grpc). `ts-proto` generates `interfaces` and `decorators` for you controller, client. For more information see the [nestjs readme](NESTJS.markdown).
Expand Down
11 changes: 10 additions & 1 deletion integration/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { parse } from "path";
import { promisify } from "util";
import { generateFile, makeUtils } from "../src/main";
import { createTypeMap } from "../src/types";
import { prefixDisableLinter } from "../src/utils";
import { generateIndexFiles } from "../src/utils";
import { getTsPoetOpts, optionsFromParameter } from "../src/options";
import { Context } from "../src/context";
import { generateTypeRegistry } from "../src/generate-type-registry";
Expand Down Expand Up @@ -56,6 +56,15 @@ async function generate(binFile: string, baseDir: string, parameter: string) {

await promisify(writeFile)(filePath, code.toString({ ...getTsPoetOpts(options), path }));
}

if (options.outputIndex) {
for (const [path, code] of generateIndexFiles(request.protoFile, options)) {
const filePath = `${baseDir}/${path}`;
const dirPath = parse(filePath).dir;
await promisify(mkdir)(dirPath, { recursive: true }).catch(() => {});
await promisify(writeFile)(filePath, code.toString({ ...getTsPoetOpts(options), path }));
}
}
}

main().then(() => {
Expand Down
14 changes: 14 additions & 0 deletions integration/output-index/a-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as Index from '.';

describe('output-index', () => {
it('generates index files correctly', () => {
expect(Index).toMatchObject({
base: {
A: {
encode: expect.any(Function),
decode: expect.any(Function),
},
},
});
});
});
Binary file added integration/output-index/a.bin
Binary file not shown.
6 changes: 6 additions & 0 deletions integration/output-index/a.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
syntax = "proto3";
package base;

message A {
string a = 1;
}
77 changes: 77 additions & 0 deletions integration/output-index/a.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/* eslint-disable */
import * as _m0 from "protobufjs/minimal";

export interface A {
a: string;
}

function createBaseA(): A {
return { a: "" };
}

export const A = {
encode(message: A, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
if (message.a !== "") {
writer.uint32(10).string(message.a);
}
return writer;
},

decode(input: _m0.Reader | Uint8Array, length?: number): A {
const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
let end = length === undefined ? reader.len : reader.pos + length;
const message = createBaseA();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
case 1:
if (tag !== 10) {
break;
}

message.a = reader.string();
continue;
}
if ((tag & 7) === 4 || tag === 0) {
break;
}
reader.skipType(tag & 7);
}
return message;
},

fromJSON(object: any): A {
return { a: isSet(object.a) ? String(object.a) : "" };
},

toJSON(message: A): unknown {
const obj: any = {};
message.a !== undefined && (obj.a = message.a);
return obj;
},

create<I extends Exact<DeepPartial<A>, I>>(base?: I): A {
return A.fromPartial(base ?? {});
},

fromPartial<I extends Exact<DeepPartial<A>, I>>(object: I): A {
const message = createBaseA();
message.a = object.a ?? "";
return message;
},
};

type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;

type DeepPartial<T> = T extends Builtin ? T
: T extends Array<infer U> ? Array<DeepPartial<U>> : T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial<U>>
: T extends {} ? { [K in keyof T]?: DeepPartial<T[K]> }
: Partial<T>;

type KeysOfUnion<T> = T extends T ? keyof T : never;
type Exact<P, I extends P> = P extends Builtin ? P
: P & { [K in keyof P]: Exact<P[K], I[K]> } & { [K in Exclude<keyof I, KeysOfUnion<P>>]: never };

function isSet(value: any): boolean {
return value !== null && value !== undefined;
}
3 changes: 3 additions & 0 deletions integration/output-index/index.base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/* eslint-disable */

export * from "./a";
3 changes: 3 additions & 0 deletions integration/output-index/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/* eslint-disable */

export * as base from "./index.base";
1 change: 1 addition & 0 deletions integration/output-index/parameters.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
outputIndex=true
6 changes: 6 additions & 0 deletions src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export type Options = {
useReadonlyTypes: boolean;
useSnakeTypeName: boolean;
outputExtensions: boolean;
outputIndex: boolean;
M: { [from: string]: string };
};

Expand Down Expand Up @@ -130,6 +131,7 @@ export function defaultOptions(): Options {
useReadonlyTypes: false,
useSnakeTypeName: true,
outputExtensions: false,
outputIndex: false,
M: {},
};
}
Expand Down Expand Up @@ -218,6 +220,10 @@ export function optionsFromParameter(parameter: string | undefined): Options {
options.initializeFieldsAsUndefined = false;
}

if (options.outputIndex) {
options.exportCommonSymbols = false;
}

return options;
}

Expand Down
16 changes: 9 additions & 7 deletions src/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import {
CodeGeneratorRequest,
CodeGeneratorResponse,
CodeGeneratorResponse_Feature,
FileDescriptorProto,
} from "ts-proto-descriptors";
import { CodeGeneratorRequest, CodeGeneratorResponse, CodeGeneratorResponse_Feature } from "ts-proto-descriptors";
import { promisify } from "util";
import { prefixDisableLinter, protoFilesToGenerate, readToBuffer } from "./utils";
import { generateIndexFiles, protoFilesToGenerate, readToBuffer } from "./utils";
import { generateFile, makeUtils } from "./main";
import { createTypeMap } from "./types";
import { Context } from "./context";
Expand Down Expand Up @@ -46,6 +41,13 @@ async function main() {
files.push({ name: path, content });
}

if (options.outputIndex) {
for (const [path, code] of generateIndexFiles(filesToGenerate, options)) {
const content = code.toString({ ...getTsPoetOpts(options), path });
files.push({ name: path, content });
}
}

const response = CodeGeneratorResponse.fromPartial({
file: files,
supportedFeatures: CodeGeneratorResponse_Feature.FEATURE_PROTO3_OPTIONAL,
Expand Down
45 changes: 44 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { code, Code, imp, Import } from "ts-poet";
import * as path from "path";
import { code, Code, imp, Import, joinCode } from "ts-poet";
import {
CodeGeneratorRequest,
FieldDescriptorProto,
Expand All @@ -15,6 +16,48 @@ export function protoFilesToGenerate(request: CodeGeneratorRequest): FileDescrip
return request.protoFile.filter((f) => request.fileToGenerate.includes(f.name));
}

type PackageTree = {
index: string;
chunks: Code[];
leaves: { [k: string]: PackageTree };
};
export function generateIndexFiles(files: FileDescriptorProto[], options: Options): [string, Code][] {
const packageTree: PackageTree = {
index: "index.ts",
leaves: {},
chunks: [],
};
for (const { name, package: pkg } of files) {
const moduleName = name.replace(".proto", options.fileSuffix);
const pkgParts = pkg.length > 0 ? pkg.split(".") : [];

const branch = pkgParts.reduce<PackageTree>((branch, part, i): PackageTree => {
if (!(part in branch.leaves)) {
const prePkgParts = pkgParts.slice(0, i + 1);
const index = `index.${prePkgParts.join(".")}.ts`;
branch.chunks.push(code`export * as ${part} from "./${path.basename(index, ".ts")}";`);
branch.leaves[part] = {
index,
leaves: {},
chunks: [],
};
}
return branch.leaves[part];
}, packageTree);
branch.chunks.push(code`export * from "./${moduleName}";`);
}

const indexFiles: [string, Code][] = [];
let branches: PackageTree[] = [packageTree];
let currentBranch;
while ((currentBranch = branches.pop())) {
indexFiles.push([currentBranch.index, joinCode(currentBranch.chunks)]);
branches.push(...Object.values(currentBranch.leaves));
}

return indexFiles;
}

export function readToBuffer(stream: ReadStream): Promise<Buffer> {
return new Promise((resolve) => {
const ret: Array<Buffer | string> = [];
Expand Down
1 change: 1 addition & 0 deletions tests/options-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ describe("options", () => {
"outputClientImpl": false,
"outputEncodeMethods": false,
"outputExtensions": false,
"outputIndex": false,
"outputJsonMethods": true,
"outputPartialMethods": false,
"outputSchema": false,
Expand Down