ts-proto 2.0.4 → 2.2.0

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.markdown CHANGED
@@ -454,7 +454,11 @@ Generated code will be placed in the Gradle build directory.
454
454
 
455
455
  - With `--ts_proto_opt=annotateFilesWithVersion=false`, the generated files will not contain the versions of `protoc` and `ts-proto` used to generate the file. This option is normally set to `true`, such that files list the versions used.
456
456
 
457
- - With `--ts_proto_opt=outputSchema=true`, meta typings will be generated that can later be used in other code generators. If outputSchema is instead specified to be `no-file-descriptor` then we do not include the file descriptor in the generated schema. This is useful if you are trying to minimize the size of the generated schema.
457
+ - With `--ts_proto_opt=outputSchema=true`, meta typings will be generated that can later be used in other code generators.
458
+
459
+ - With `--ts_proto_opt=outputSchema=no-file-descriptor`, meta typings will be generated, but we do not include the file descriptor in the generated schema. This is useful if you are trying to minimize the size of the generated schema.
460
+
461
+ - With `--ts_proto_opt=outputSchema=const`, meta typings will be generated `as const`, allowing type-safe access to all its properties. (only works with TypeScript 4.9 and up, because it also uses the [`satisfies`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#the-satisfies-operator) operator). Can be combined with the `no-file-descriptor` option (`outputSchema=const,outputSchema=no-file-descriptor`) to not include the file descriptor in the generated schema.
458
462
 
459
463
  - With `--ts_proto_opt=outputTypeAnnotations=true`, each message will be given a `$type` field containing its fully-qualified name. You can use `--ts_proto_opt=outputTypeAnnotations=static-only` to omit it from the `interface` declaration, or `--ts_proto_opt=outputTypeAnnotations=optional` to make it an optional property on the `interface` definition. The latter option may be useful if you want to use the `$type` field for runtime type checking on responses from a server.
460
464
 
@@ -4,7 +4,7 @@ import { FileDescriptorProto } from "ts-proto-descriptors";
4
4
  import { Context } from "./context";
5
5
  import { Options } from "./options";
6
6
  export declare function generateFile(ctx: Context, fileDesc: FileDescriptorProto): [string, Code];
7
- export type Utils = ReturnType<typeof makeDeepPartial> & ReturnType<typeof makeObjectIdMethods> & ReturnType<typeof makeTimestampMethods> & ReturnType<typeof makeByteUtils> & ReturnType<typeof makeLongUtils> & ReturnType<typeof makeComparisonUtils> & ReturnType<typeof makeNiceGrpcServerStreamingMethodResult> & ReturnType<typeof makeGrpcWebErrorClass> & ReturnType<typeof makeExtensionClass> & ReturnType<typeof makeAssertionUtils>;
7
+ export type Utils = ReturnType<typeof makeDeepPartial> & ReturnType<typeof makeObjectIdMethods> & ReturnType<typeof makeTimestampMethods> & ReturnType<typeof makeByteUtils> & ReturnType<typeof makeLongUtils> & ReturnType<typeof makeComparisonUtils> & ReturnType<typeof makeNiceGrpcServerStreamingMethodResult> & ReturnType<typeof makeGrpcWebErrorClass> & ReturnType<typeof makeExtensionClass> & ReturnType<typeof makeAssertionUtils> & ReturnType<typeof makeMessageFns>;
8
8
  /** These are runtime utility methods used by the generated code. */
9
9
  export declare function makeUtils(options: Options): Utils;
10
10
  declare function makeLongUtils(options: Options, bytes: ReturnType<typeof makeByteUtils>): {
@@ -22,6 +22,15 @@ declare function makeDeepPartial(options: Options, longs: ReturnType<typeof make
22
22
  DeepPartial: ConditionalOutput;
23
23
  Exact: ConditionalOutput;
24
24
  };
25
+ declare function makeMessageFns(options: Options, deepPartial: ReturnType<typeof makeDeepPartial>, extension: ReturnType<typeof makeExtensionClass>): {
26
+ MessageFns: ConditionalOutput;
27
+ ExtensionFns: ConditionalOutput;
28
+ ExtensionHolder: ConditionalOutput;
29
+ StructWrapperFns: ConditionalOutput;
30
+ AnyValueWrapperFns: ConditionalOutput;
31
+ ListValueWrapperFns: ConditionalOutput;
32
+ FieldMaskWrapperFns: ConditionalOutput;
33
+ };
25
34
  declare function makeObjectIdMethods(): {
26
35
  fromJsonObjectId: ConditionalOutput;
27
36
  fromProtoObjectId: ConditionalOutput;
package/build/src/main.js CHANGED
@@ -96,7 +96,8 @@ function generateFile(ctx, fileDesc) {
96
96
  chunks.push(generateBaseInstanceFactory(ctx, fullName, message, fullTypeName));
97
97
  }
98
98
  const staticMembers = [];
99
- if (options.outputTypeAnnotations || options.outputTypeRegistry) {
99
+ const hasTypeMember = options.outputTypeAnnotations || options.outputTypeRegistry;
100
+ if (hasTypeMember) {
100
101
  staticMembers.push((0, ts_poet_1.code) `$type: '${fullTypeName}' as const`);
101
102
  }
102
103
  if (options.outputExtensions) {
@@ -153,8 +154,37 @@ function generateFile(ctx, fileDesc) {
153
154
  staticMembers.push(...(0, generate_struct_wrappers_1.generateUnwrapShallow)(ctx, fullTypeName, structFieldNames));
154
155
  }
155
156
  if (staticMembers.length > 0) {
157
+ const messageFnsTypeParameters = [fullName, hasTypeMember && `'${fullTypeName}'`]
158
+ .filter((p) => !!p)
159
+ .join(", ");
160
+ const interfaces = [(0, ts_poet_1.code) `${utils.MessageFns}<${messageFnsTypeParameters}>`];
161
+ if (options.outputEncodeMethods &&
162
+ options.outputExtensions &&
163
+ options.unknownFields &&
164
+ message.extensionRange.length) {
165
+ interfaces.push((0, ts_poet_1.code) `${utils.ExtensionFns}<${fullName}>`);
166
+ }
167
+ if ((0, types_1.isStructTypeName)(fullTypeName)) {
168
+ interfaces.push((0, ts_poet_1.code) `${utils.StructWrapperFns}`);
169
+ }
170
+ else if ((0, types_1.isAnyValueTypeName)(fullTypeName)) {
171
+ interfaces.push((0, ts_poet_1.code) `${utils.AnyValueWrapperFns}`);
172
+ }
173
+ else if ((0, types_1.isListValueTypeName)(fullTypeName)) {
174
+ interfaces.push((0, ts_poet_1.code) `${utils.ListValueWrapperFns}`);
175
+ }
176
+ else if ((0, types_1.isFieldMaskTypeName)(fullTypeName)) {
177
+ interfaces.push((0, ts_poet_1.code) `${utils.FieldMaskWrapperFns}`);
178
+ }
179
+ if (options.outputExtensions) {
180
+ for (const extension of message.extension) {
181
+ const name = (0, case_1.maybeSnakeToCamel)(extension.name, ctx.options);
182
+ const type = (0, types_1.toTypeName)(ctx, message, extension);
183
+ interfaces.push((0, ts_poet_1.code) `${utils.ExtensionHolder}<"${name}", ${type}>`);
184
+ }
185
+ }
156
186
  chunks.push((0, ts_poet_1.code) `
157
- export const ${(0, ts_poet_1.def)(fullName)} = {
187
+ export const ${(0, ts_poet_1.def)(fullName)}: ${(0, ts_poet_1.joinCode)(interfaces, { on: " & " })} = {
158
188
  ${(0, ts_poet_1.joinCode)(staticMembers, { on: ",\n\n" })}
159
189
  };
160
190
  `);
@@ -281,17 +311,20 @@ function generateFile(ctx, fileDesc) {
281
311
  function makeUtils(options) {
282
312
  const bytes = makeByteUtils(options);
283
313
  const longs = makeLongUtils(options, bytes);
314
+ const deepPartial = makeDeepPartial(options, longs);
315
+ const extension = makeExtensionClass(options);
284
316
  return {
285
317
  ...bytes,
286
- ...makeDeepPartial(options, longs),
318
+ ...deepPartial,
287
319
  ...makeObjectIdMethods(),
288
320
  ...makeTimestampMethods(options, longs, bytes),
289
321
  ...longs,
290
322
  ...makeComparisonUtils(),
291
323
  ...makeNiceGrpcServerStreamingMethodResult(options),
292
324
  ...makeGrpcWebErrorClass(bytes),
293
- ...makeExtensionClass(options),
325
+ ...extension,
294
326
  ...makeAssertionUtils(bytes),
327
+ ...makeMessageFns(options, deepPartial, extension),
295
328
  };
296
329
  }
297
330
  function makeProtobufTimestampWrapper() {
@@ -461,6 +494,121 @@ function makeDeepPartial(options, longs) {
461
494
  `);
462
495
  return { Builtin, DeepPartial, Exact };
463
496
  }
497
+ function makeMessageFns(options, deepPartial, extension) {
498
+ const BinaryWriter = (0, ts_poet_1.imp)("BinaryWriter@@bufbuild/protobuf/wire");
499
+ const BinaryReader = (0, ts_poet_1.imp)("BinaryReader@@bufbuild/protobuf/wire");
500
+ const { Exact, DeepPartial } = deepPartial;
501
+ const { Extension } = extension;
502
+ const commonStaticMembers = [];
503
+ const extensionStaticMembers = [];
504
+ const hasTypeMember = options.outputTypeAnnotations || options.outputTypeRegistry;
505
+ if (hasTypeMember) {
506
+ commonStaticMembers.push((0, ts_poet_1.code) `readonly $type: V;`);
507
+ }
508
+ if (options.outputEncodeMethods) {
509
+ if (options.outputEncodeMethods === true ||
510
+ options.outputEncodeMethods === "encode-only" ||
511
+ options.outputEncodeMethods === "encode-no-creation") {
512
+ commonStaticMembers.push((0, ts_poet_1.code) `
513
+ encode(message: T, writer?: ${BinaryWriter}): ${BinaryWriter};
514
+ `);
515
+ if (options.outputExtensions && options.unknownFields) {
516
+ extensionStaticMembers.push((0, ts_poet_1.code) `
517
+ setExtension<E>(message: T, extension: ${Extension}<E>, value: E): void;
518
+ `);
519
+ }
520
+ }
521
+ if (options.outputEncodeMethods === true || options.outputEncodeMethods === "decode-only") {
522
+ commonStaticMembers.push((0, ts_poet_1.code) `
523
+ decode(input: ${BinaryReader} | Uint8Array, length?: number): T;
524
+ `);
525
+ if (options.outputExtensions && options.unknownFields) {
526
+ extensionStaticMembers.push((0, ts_poet_1.code) `
527
+ getExtension<E>(message: T, extension: ${Extension}<E>): E | undefined;
528
+ `);
529
+ }
530
+ }
531
+ }
532
+ if (options.useAsyncIterable) {
533
+ commonStaticMembers.push((0, ts_poet_1.code) `
534
+ encodeTransform(
535
+ source: AsyncIterable<T | T[]> | Iterable<T | T[]>
536
+ ): AsyncIterable<Uint8Array>;
537
+ `);
538
+ commonStaticMembers.push((0, ts_poet_1.code) `
539
+ decodeTransform(
540
+ source: AsyncIterable<Uint8Array | Uint8Array[]> | Iterable<Uint8Array | Uint8Array[]>
541
+ ): AsyncIterable<T>;
542
+ `);
543
+ }
544
+ if (options.outputJsonMethods) {
545
+ if (options.outputJsonMethods === true || options.outputJsonMethods === "from-only") {
546
+ commonStaticMembers.push((0, ts_poet_1.code) `fromJSON(object: any): T;`);
547
+ }
548
+ if (options.outputJsonMethods === true || options.outputJsonMethods === "to-only") {
549
+ commonStaticMembers.push((0, ts_poet_1.code) `toJSON(message: T): unknown;`);
550
+ }
551
+ }
552
+ if (options.outputPartialMethods) {
553
+ if (options.useExactTypes) {
554
+ commonStaticMembers.push((0, ts_poet_1.code) `create<I extends ${Exact}<${DeepPartial}<T>, I>>(base?: I): T;`);
555
+ commonStaticMembers.push((0, ts_poet_1.code) `fromPartial<I extends ${Exact}<${DeepPartial}<T>, I>>(object: I): T;`);
556
+ }
557
+ else {
558
+ commonStaticMembers.push((0, ts_poet_1.code) `create(base?: DeepPartial<T>): T;`);
559
+ commonStaticMembers.push((0, ts_poet_1.code) `fromPartial(object: DeepPartial<T>): T;`);
560
+ }
561
+ }
562
+ const maybeExport = options.exportCommonSymbols ? "export" : "";
563
+ const MessageFns = (0, ts_poet_1.conditionalOutput)("MessageFns", (0, ts_poet_1.code) `
564
+ ${maybeExport} interface MessageFns<T${hasTypeMember ? ", V extends string" : ""}> {
565
+ ${(0, ts_poet_1.joinCode)(commonStaticMembers, { on: "\n" })}
566
+ }
567
+ `);
568
+ const ExtensionFns = (0, ts_poet_1.conditionalOutput)("ExtensionFns", (0, ts_poet_1.code) `
569
+ ${maybeExport} interface ExtensionFns<T> {
570
+ ${(0, ts_poet_1.joinCode)(extensionStaticMembers, { on: "\n" })}
571
+ }
572
+ `);
573
+ const ExtensionHolder = (0, ts_poet_1.conditionalOutput)("ExtensionHolder", (0, ts_poet_1.code) `
574
+ ${maybeExport} type ExtensionHolder<T extends string, V> = {
575
+ [key in T]: Extension<V>;
576
+ }
577
+ `);
578
+ const StructWrapperFns = (0, ts_poet_1.conditionalOutput)("StructWrapperFns", (0, ts_poet_1.code) `
579
+ ${maybeExport} interface StructWrapperFns {
580
+ wrap(object: {[key: string]: any} | undefined): Struct;
581
+ unwrap(message: Struct): {[key: string]: any};
582
+ }
583
+ `);
584
+ const AnyValueWrapperFns = (0, ts_poet_1.conditionalOutput)("AnyValueWrapperFns", (0, ts_poet_1.code) `
585
+ ${maybeExport} interface AnyValueWrapperFns {
586
+ wrap(value: any): Value;
587
+ unwrap(message: any): string | number | boolean | Object | null | Array<any> | undefined;
588
+ }
589
+ `);
590
+ const ListValueWrapperFns = (0, ts_poet_1.conditionalOutput)("ListValueWrapperFns", (0, ts_poet_1.code) `
591
+ ${maybeExport} interface ListValueWrapperFns {
592
+ wrap(array: ${options.useReadonlyTypes ? "Readonly" : ""}Array<any> | undefined): ListValue;
593
+ unwrap(message: ${options.useReadonlyTypes ? "any" : "ListValue"}): Array<any>;
594
+ }
595
+ `);
596
+ const FieldMaskWrapperFns = (0, ts_poet_1.conditionalOutput)("FieldMaskWrapperFns", (0, ts_poet_1.code) `
597
+ ${maybeExport} interface FieldMaskWrapperFns {
598
+ wrap(paths: ${options.useReadonlyTypes ? "readonly" : ""} string[]): FieldMask;
599
+ unwrap(message: ${options.useReadonlyTypes ? "any" : "FieldMask"}): string[] ${options.useOptionals === "all" ? "| undefined" : ""};
600
+ }
601
+ `);
602
+ return {
603
+ MessageFns,
604
+ ExtensionFns,
605
+ ExtensionHolder,
606
+ StructWrapperFns,
607
+ AnyValueWrapperFns,
608
+ ListValueWrapperFns,
609
+ FieldMaskWrapperFns,
610
+ };
611
+ }
464
612
  function makeObjectIdMethods() {
465
613
  const mongodb = (0, ts_poet_1.imp)("mongodb*mongodb");
466
614
  const fromProtoObjectId = (0, ts_poet_1.conditionalOutput)("fromProtoObjectId", (0, ts_poet_1.code) `
@@ -32,6 +32,11 @@ export declare enum ServiceOption {
32
32
  DEFAULT = "default",
33
33
  NONE = "none"
34
34
  }
35
+ export declare enum OutputSchemaOption {
36
+ TRUE = "true",
37
+ NO_FILE_DESCRIPTOR = "no-file-descriptor",
38
+ CONST = "const"
39
+ }
35
40
  export type Options = {
36
41
  context: boolean;
37
42
  snakeToCamel: Array<"json" | "keys">;
@@ -69,7 +74,7 @@ export type Options = {
69
74
  unrecognizedEnumName: string;
70
75
  unrecognizedEnumValue: number;
71
76
  exportCommonSymbols: boolean;
72
- outputSchema: boolean | "no-file-descriptor";
77
+ outputSchema: false | OutputSchemaOption[];
73
78
  onlyTypes: boolean;
74
79
  emitImportedFiles: boolean;
75
80
  useAbortSignal: boolean;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ServiceOption = exports.OneofOption = exports.EnvOption = exports.JsonTimestampOption = exports.DateOption = exports.LongOption = void 0;
3
+ exports.OutputSchemaOption = exports.ServiceOption = exports.OneofOption = exports.EnvOption = exports.JsonTimestampOption = exports.DateOption = exports.LongOption = void 0;
4
4
  exports.defaultOptions = defaultOptions;
5
5
  exports.optionsFromParameter = optionsFromParameter;
6
6
  exports.getTsPoetOpts = getTsPoetOpts;
@@ -44,6 +44,12 @@ var ServiceOption;
44
44
  ServiceOption["DEFAULT"] = "default";
45
45
  ServiceOption["NONE"] = "none";
46
46
  })(ServiceOption || (exports.ServiceOption = ServiceOption = {}));
47
+ var OutputSchemaOption;
48
+ (function (OutputSchemaOption) {
49
+ OutputSchemaOption["TRUE"] = "true";
50
+ OutputSchemaOption["NO_FILE_DESCRIPTOR"] = "no-file-descriptor";
51
+ OutputSchemaOption["CONST"] = "const";
52
+ })(OutputSchemaOption || (exports.OutputSchemaOption = OutputSchemaOption = {}));
47
53
  function defaultOptions() {
48
54
  return {
49
55
  context: false,
@@ -165,6 +171,13 @@ function optionsFromParameter(parameter) {
165
171
  if (options.nestJs && options.outputServices.length > 0) {
166
172
  options.outputEncodeMethods = true;
167
173
  }
174
+ // Handle outputSchema=true
175
+ if (options.outputSchema === true) {
176
+ options.outputSchema = [];
177
+ }
178
+ if (typeof options.outputSchema === "string") {
179
+ options.outputSchema = [options.outputSchema];
180
+ }
168
181
  if (options.useDate === true) {
169
182
  // Treat useDate=true as DATE
170
183
  options.useDate = DateOption.DATE;
@@ -7,6 +7,7 @@ const visit_1 = require("./visit");
7
7
  const utils_1 = require("./utils");
8
8
  const types_1 = require("./types");
9
9
  const wire_1 = require("@bufbuild/protobuf/wire");
10
+ const options_1 = require("./options");
10
11
  const fileDescriptorProto = (0, ts_poet_1.imp)("FileDescriptorProto@ts-proto-descriptors");
11
12
  const extensionCache = {};
12
13
  function generateSchema(ctx, fileDesc, sourceInfo) {
@@ -18,6 +19,9 @@ function generateSchema(ctx, fileDesc, sourceInfo) {
18
19
  }
19
20
  extensionCache[extension.extendee][extension.number] = extension;
20
21
  });
22
+ const outputSchemaOptions = ctx.options.outputSchema ? ctx.options.outputSchema : [];
23
+ const outputFileDescriptor = !outputSchemaOptions.includes(options_1.OutputSchemaOption.NO_FILE_DESCRIPTOR);
24
+ const outputAsConst = outputSchemaOptions.includes(options_1.OutputSchemaOption.CONST);
21
25
  chunks.push((0, ts_poet_1.code) `
22
26
  type ProtoMetaMessageOptions = {
23
27
  options?: { [key: string]: any };
@@ -27,7 +31,7 @@ function generateSchema(ctx, fileDesc, sourceInfo) {
27
31
  };
28
32
 
29
33
  export interface ProtoMetadata {
30
- ${ctx.options.outputSchema !== "no-file-descriptor" ? (0, ts_poet_1.code) `fileDescriptor: ${fileDescriptorProto};\n` : ""}references: { [key: string]: any };
34
+ ${outputFileDescriptor ? (0, ts_poet_1.code) `fileDescriptor: ${fileDescriptorProto};\n` : ""}references: { [key: string]: any };
31
35
  dependencies?: ProtoMetadata[];
32
36
  options?: {
33
37
  options?: { [key: string]: any };
@@ -106,7 +110,7 @@ function generateSchema(ctx, fileDesc, sourceInfo) {
106
110
  if (methodsOptions.length > 0 || serviceOptions) {
107
111
  servicesOptions.push((0, ts_poet_1.code) `
108
112
  '${service.name}': {
109
- options: ${serviceOptions},
113
+ ${serviceOptions ? (0, ts_poet_1.code) `options: ${serviceOptions},` : ""}
110
114
  methods: {${(0, ts_poet_1.joinCode)(methodsOptions, { on: "," })}}
111
115
  }
112
116
  `);
@@ -132,17 +136,17 @@ function generateSchema(ctx, fileDesc, sourceInfo) {
132
136
  if (valuesOptions.length > 0 || enumOptions) {
133
137
  enumsOptions.push((0, ts_poet_1.code) `
134
138
  '${Enum.name}': {
135
- options: ${enumOptions},
139
+ ${enumOptions ? (0, ts_poet_1.code) `options: ${enumOptions},` : ""}
136
140
  values: {${(0, ts_poet_1.joinCode)(valuesOptions, { on: "," })}}
137
141
  }
138
142
  `);
139
143
  }
140
144
  });
141
145
  chunks.push((0, ts_poet_1.code) `
142
- export const ${(0, ts_poet_1.def)("protoMetadata")}: ProtoMetadata = {
143
- ${ctx.options.outputSchema !== "no-file-descriptor"
144
- ? (0, ts_poet_1.code) `fileDescriptor: ${fileDescriptorProto}.fromPartial(${descriptor}),\n`
145
- : ""}references: { ${(0, ts_poet_1.joinCode)(references, { on: "," })} },
146
+ export const ${(0, ts_poet_1.def)("protoMetadata")}${outputAsConst ? "" : ": ProtoMetadata"} = {
147
+ ${outputFileDescriptor ? (0, ts_poet_1.code) `fileDescriptor: ${descriptor},\n` : ""}references: { ${(0, ts_poet_1.joinCode)(references, {
148
+ on: ",",
149
+ })} },
146
150
  dependencies: [${(0, ts_poet_1.joinCode)(dependencies, { on: "," })}],
147
151
  ${fileOptions || messagesOptions.length > 0 || servicesOptions.length > 0 || enumsOptions.length > 0
148
152
  ? (0, ts_poet_1.code) `options: {
@@ -152,7 +156,7 @@ function generateSchema(ctx, fileDesc, sourceInfo) {
152
156
  ${enumsOptions.length > 0 ? (0, ts_poet_1.code) `enums: {${(0, ts_poet_1.joinCode)(enumsOptions, { on: "," })}}` : ""}
153
157
  }`
154
158
  : ""}
155
- }
159
+ }${outputAsConst ? " as const satisfies ProtoMetadata" : ""}
156
160
  `);
157
161
  return chunks;
158
162
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-proto",
3
- "version": "2.0.4",
3
+ "version": "2.2.0",
4
4
  "description": "",
5
5
  "main": "build/src/plugin.js",
6
6
  "repository": "github:stephenh/ts-proto",