ts-proto 2.15.0 → 2.15.2

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.
@@ -339,10 +339,10 @@ function generateCachingRpcMethod(ctx, fileDesc, serviceDesc, methodDesc) {
339
339
  function generateRpcType(ctx, hasStreamingMethods) {
340
340
  const { options } = ctx;
341
341
  const metadata = options.metadataType ? (0, ts_poet_1.imp)(options.metadataType) : (0, ts_poet_1.imp)("t:Metadata@@grpc/grpc-js");
342
- const metadataType = metadata.symbol;
343
342
  const maybeContext = options.context ? "<Context>" : "";
344
343
  const maybeContextParam = options.context ? "ctx: Context," : "";
345
- const maybeMetadataParam = options.metadataType || options.addGrpcMetadata ? `metadata?: ${metadataType},` : "";
344
+ // Use the import itself, so it is added even if no method of the service refers to it.
345
+ const maybeMetadataParam = options.metadataType || options.addGrpcMetadata ? (0, ts_poet_1.code) `metadata?: ${metadata},` : "";
346
346
  const maybeAbortSignalParam = options.useAbortSignal ? "abortSignal?: AbortSignal," : "";
347
347
  const methods = [[(0, ts_poet_1.code) `request`, (0, ts_poet_1.code) `Uint8Array`, (0, ts_poet_1.code) `Promise<Uint8Array>`]];
348
348
  const additionalMethods = [];
@@ -24,7 +24,7 @@ declare function makeDeepPartial(options: Options, longs: ReturnType<typeof make
24
24
  Exact: ConditionalOutput;
25
25
  };
26
26
  declare function makeMessageFns(options: Options, deepPartial: ReturnType<typeof makeDeepPartial>, extension: ReturnType<typeof makeExtensionClass>): {
27
- MessageFns: ConditionalOutput;
27
+ MessageFns: ConditionalOutput | undefined;
28
28
  ExtensionFns: ConditionalOutput;
29
29
  ExtensionHolder: ConditionalOutput;
30
30
  StructWrapperFns: ConditionalOutput;
@@ -42,6 +42,9 @@ declare function makeTimestampMethods(options: Options, longs: ReturnType<typeof
42
42
  fromTimestamp: ConditionalOutput;
43
43
  timestampFractionRegex: ConditionalOutput;
44
44
  fromJsonTimestamp: ConditionalOutput;
45
+ timestampJsonRegex: ConditionalOutput;
46
+ timestampFromJson: ConditionalOutput;
47
+ timestampToJson: ConditionalOutput;
45
48
  };
46
49
  declare function makeAnyJsonUtils(): {
47
50
  anyJsonValueTypes: ConditionalOutput;
package/build/src/main.js CHANGED
@@ -88,14 +88,15 @@ function generateFile(ctx, fileDesc) {
88
88
  (0, visit_1.visit)(fileDesc, sourceInfo, (fullName, message, _sInfo, fullProtoTypeName) => {
89
89
  const fullTypeName = (0, utils_1.maybePrefixPackage)(fileDesc, fullProtoTypeName);
90
90
  const outputWrapAndUnwrap = (0, generate_struct_wrappers_1.isWrapperType)(fullTypeName);
91
- // Only decode, fromPartial, and wrap use the createBase method
91
+ // Only decode, fromPartial, and wrap use the createBase method. The deep `Value.wrap`
92
+ // generated for NestJS builds its result without it.
92
93
  if ((options.outputEncodeMethods &&
93
94
  options.outputEncodeMethods !== "encode-no-creation" &&
94
95
  options.outputEncodeMethods !== "encode-only" &&
95
96
  (options.outputDecodeIncludeTypes === "" ||
96
97
  new RegExp(options.outputDecodeIncludeTypes).test(fullTypeName))) ||
97
98
  options.outputPartialMethods ||
98
- outputWrapAndUnwrap) {
99
+ (outputWrapAndUnwrap && !(options.nestJs && (0, types_1.isAnyValueTypeName)(fullTypeName)))) {
99
100
  chunks.push(generateBaseInstanceFactory(ctx, fullName, message, fullTypeName));
100
101
  }
101
102
  const staticMembers = [];
@@ -172,7 +173,10 @@ function generateFile(ctx, fileDesc) {
172
173
  const messageFnsTypeParameters = [fullName, hasTypeMember && `'${fullTypeName}'`]
173
174
  .filter((p) => !!p)
174
175
  .join(", ");
175
- const interfaces = [(0, ts_poet_1.code) `${utils.MessageFns}<${messageFnsTypeParameters}>`];
176
+ const interfaces = [];
177
+ if (utils.MessageFns) {
178
+ interfaces.push((0, ts_poet_1.code) `${utils.MessageFns}<${messageFnsTypeParameters}>`);
179
+ }
176
180
  if (options.outputEncodeMethods &&
177
181
  options.outputExtensions &&
178
182
  options.unknownFields &&
@@ -601,11 +605,15 @@ function makeMessageFns(options, deepPartial, extension) {
601
605
  }
602
606
  }
603
607
  const maybeExport = options.exportCommonSymbols ? "export" : "";
604
- const MessageFns = (0, ts_poet_1.conditionalOutput)("MessageFns", (0, ts_poet_1.code) `
605
- ${maybeExport} interface MessageFns<T${hasTypeMember ? ", V extends string" : ""}> {
606
- ${(0, ts_poet_1.joinCode)(commonStaticMembers, { on: "\n" })}
607
- }
608
- `);
608
+ // Without any common members, e.g. with only the NestJS wrappers, the interface
609
+ // would be empty and its type parameter unused, so it is not generated at all.
610
+ const MessageFns = commonStaticMembers.length > 0
611
+ ? (0, ts_poet_1.conditionalOutput)("MessageFns", (0, ts_poet_1.code) `
612
+ ${maybeExport} interface MessageFns<T${hasTypeMember ? ", V extends string" : ""}> {
613
+ ${(0, ts_poet_1.joinCode)(commonStaticMembers, { on: "\n" })}
614
+ }
615
+ `)
616
+ : undefined;
609
617
  const ExtensionFns = (0, ts_poet_1.conditionalOutput)("ExtensionFns", (0, ts_poet_1.code) `
610
618
  ${maybeExport} interface ExtensionFns<T> {
611
619
  ${(0, ts_poet_1.joinCode)(extensionStaticMembers, { on: "\n" })}
@@ -829,7 +837,43 @@ function makeTimestampMethods(options, longs, bytes) {
829
837
  }
830
838
  }
831
839
  `);
832
- return { toTimestamp, fromTimestamp, timestampFractionRegex, fromJsonTimestamp };
840
+ // Declared once per file, so the regex isn't compiled on each timestampFromJson call
841
+ const timestampJsonRegex = (0, ts_poet_1.conditionalOutput)("timestampJsonRegex", (0, ts_poet_1.code) `const timestampJsonRegex = /^.+T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(?:Z|[+-]\\d{2}:\\d{2})$/;`);
842
+ // Parses the canonical JSON of a Timestamp, an RFC 3339 string
843
+ const timestampFromJson = (0, ts_poet_1.conditionalOutput)("timestampFromJson", (0, ts_poet_1.code) `
844
+ function timestampFromJson(value: string): { seconds: number; nanos: number } {
845
+ const match = ${timestampJsonRegex}.exec(value);
846
+ // Date only keeps milliseconds, so it parses the string without the fractional seconds
847
+ const fraction = match?.[1];
848
+ const millis = match
849
+ ? new ${bytes.globalThis}.Date(fraction === undefined ? value : value.replace(fraction, "")).getTime()
850
+ : NaN;
851
+ if (!match || ${bytes.globalThis}.Number.isNaN(millis)) {
852
+ throw new ${bytes.globalThis}.Error(\`Invalid timestamp: \${value}\`);
853
+ }
854
+ const nanos = fraction === undefined ? 0 : ${bytes.globalThis}.Number(fraction.slice(1, 10).padEnd(9, "0"));
855
+ return { seconds: millis / 1_000, nanos };
856
+ }
857
+ `);
858
+ // Writes the canonical JSON of a Timestamp, an RFC 3339 string
859
+ const timestampToJson = (0, ts_poet_1.conditionalOutput)("timestampToJson", (0, ts_poet_1.code) `
860
+ function timestampToJson(seconds: number, nanos: number): string {
861
+ const date = new ${bytes.globalThis}.Date(seconds * 1_000).toISOString().slice(0, 19);
862
+ if (nanos === 0) return \`\${date}Z\`;
863
+ const padded = nanos.toString().padStart(9, "0");
864
+ const len = nanos % 1_000_000 === 0 ? 3 : nanos % 1_000 === 0 ? 6 : 9;
865
+ return \`\${date}.\${padded.slice(0, len)}Z\`;
866
+ }
867
+ `);
868
+ return {
869
+ toTimestamp,
870
+ fromTimestamp,
871
+ timestampFractionRegex,
872
+ fromJsonTimestamp,
873
+ timestampJsonRegex,
874
+ timestampFromJson,
875
+ timestampToJson,
876
+ };
833
877
  }
834
878
  function makeAnyJsonUtils() {
835
879
  // Well-known types whose JSON is not an object of their fields. Packed in an Any, that
@@ -1905,10 +1949,22 @@ function generateFromJson(ctx, fullName, fullTypeName, messageDesc) {
1905
1949
  `;
1906
1950
  }
1907
1951
  // create the basic function declaration
1908
- chunks.push((0, ts_poet_1.code) `
1909
- fromJSON(${messageDesc.field.length > 0 ? "object" : "_"}: any): ${fullName} {
1910
- return {
1911
- `);
1952
+ if (fullTypeName === "google.protobuf.Timestamp") {
1953
+ // The canonical JSON of a Timestamp is an RFC 3339 string; the object form is still accepted.
1954
+ chunks.push((0, ts_poet_1.code) `
1955
+ fromJSON(object: any): ${fullName} {
1956
+ if (typeof object === "string") {
1957
+ return ${fullName}.fromJSON(${utils.timestampFromJson}(object));
1958
+ }
1959
+ return {
1960
+ `);
1961
+ }
1962
+ else {
1963
+ chunks.push((0, ts_poet_1.code) `
1964
+ fromJSON(${messageDesc.field.length > 0 ? "object" : "_"}: any): ${fullName} {
1965
+ return {
1966
+ `);
1967
+ }
1912
1968
  if ((0, options_1.addTypeToMessages)(options)) {
1913
1969
  chunks.push((0, ts_poet_1.code) `$type: ${fullName}.$type,`);
1914
1970
  }
@@ -2223,7 +2279,7 @@ function generateFromJson(ctx, fullName, fullTypeName, messageDesc) {
2223
2279
  chunks.push((0, ts_poet_1.code) `}`);
2224
2280
  return (0, ts_poet_1.joinCode)(chunks, { on: "\n" });
2225
2281
  }
2226
- function generateCanonicalToJson(fullName, fullProtobufTypeName, { useOptionals, useNullAsOptional, useDuration, forceLong }) {
2282
+ function generateCanonicalToJson(fullName, fullProtobufTypeName, { useOptionals, useNullAsOptional, useDuration, useJsonTimestamp, forceLong }, utils) {
2227
2283
  if ((0, types_1.isFieldMaskTypeName)(fullProtobufTypeName)) {
2228
2284
  const returnType = useOptionals === "all" ? `string | ${(0, utils_1.nullOrUndefined)({ useNullAsOptional })}` : "string";
2229
2285
  const pathModifier = useOptionals === "all" ? "?" : "";
@@ -2269,6 +2325,16 @@ function generateCanonicalToJson(fullName, fullProtobufTypeName, { useOptionals,
2269
2325
  }
2270
2326
  `;
2271
2327
  }
2328
+ if (fullProtobufTypeName === "google.protobuf.Timestamp" && useJsonTimestamp === options_1.JsonTimestampOption.RFC3339) {
2329
+ const seconds = forceLong === options_1.LongOption.LONG
2330
+ ? (0, ts_poet_1.code) `message.seconds?.toNumber() ?? 0`
2331
+ : (0, ts_poet_1.code) `${utils.globalThis}.Number(message.seconds ?? 0)`;
2332
+ return (0, ts_poet_1.code) `
2333
+ toJSON(message: ${fullName}): string {
2334
+ return ${utils.timestampToJson}(${seconds}, message.nanos ?? 0);
2335
+ }
2336
+ `;
2337
+ }
2272
2338
  return undefined;
2273
2339
  }
2274
2340
  /** Whether `google.protobuf.Any` gets its canonical `@type` JSON form, see the `anyType` option. */
@@ -2348,7 +2414,7 @@ function generateToJson(ctx, fullName, fullProtobufTypeName, messageDesc) {
2348
2414
  chunks.push(generateCanonicalAnyToJson(ctx, fullName, messageDesc));
2349
2415
  return (0, ts_poet_1.joinCode)(chunks, { on: "\n" });
2350
2416
  }
2351
- const canonicalToJson = generateCanonicalToJson(fullName, fullProtobufTypeName, options);
2417
+ const canonicalToJson = generateCanonicalToJson(fullName, fullProtobufTypeName, options, utils);
2352
2418
  if (canonicalToJson) {
2353
2419
  chunks.push(canonicalToJson);
2354
2420
  return (0, ts_poet_1.joinCode)(chunks, { on: "\n" });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-proto",
3
- "version": "2.15.0",
3
+ "version": "2.15.2",
4
4
  "description": "",
5
5
  "main": "build/src/plugin.js",
6
6
  "repository": "github:stephenh/ts-proto",