ts-proto 2.12.4 → 2.13.1
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 +3 -1
- package/build/src/generate-nestjs.js +4 -4
- package/build/src/generate-services.js +5 -4
- package/build/src/main.d.ts +2 -1
- package/build/src/main.js +24 -17
- package/build/src/options.d.ts +1 -0
- package/build/src/options.js +1 -0
- package/package.json +1 -1
package/README.markdown
CHANGED
|
@@ -264,7 +264,7 @@ When ts-proto recognizes methods of this pattern, it will automatically create a
|
|
|
264
264
|
|
|
265
265
|
This provides the client code with the illusion that it can make individual `Get<OperationName>` calls (which is generally preferable/easier when implementing the client's business logic), but the actual implementation that ts-proto provides will end up making `Batch<OperationName>` calls to the backend service.
|
|
266
266
|
|
|
267
|
-
You also need
|
|
267
|
+
You also need `context=true`, which adds a Go-style `ctx` parameter to every client method. Your `ctx` must implement `getDataLoader`, which ts-proto uses to cache request-scoped [DataLoaders](https://github.com/graphql/dataloader) that handle batch detection and flushing. If you want `ctx` for tracing/metadata only, pass `useContextDataloaders=false` to skip the DataLoader contract.
|
|
268
268
|
|
|
269
269
|
See the `batching.proto` file and related tests for examples/more details.
|
|
270
270
|
|
|
@@ -317,6 +317,8 @@ Generated code will be placed in the Gradle build directory.
|
|
|
317
317
|
|
|
318
318
|
- With `--ts_proto_opt=context=true`, the services will have a Go-style `ctx` parameter, which is useful for tracing/logging/etc. if you're not using node's `async_hooks` api due to performance reasons.
|
|
319
319
|
|
|
320
|
+
By default `Context` must extend a generated `DataLoaders` interface so ts-proto can emit DataLoader-backed wrappers around `Get*`/`Batch*` methods. Pass `useContextDataloaders=false` to drop the constraint and skip those wrappers when you only need `ctx` for tracing/metadata.
|
|
321
|
+
|
|
320
322
|
- With `--ts_proto_opt=forceLong=long`, all 64-bit numbers will be parsed as instances of `Long` (using the [long](https://www.npmjs.com/package/long) library).
|
|
321
323
|
|
|
322
324
|
With `--ts_proto_opt=forceLong=string`, all 64-bit numbers will be output as strings.
|
|
@@ -13,7 +13,7 @@ function generateNestjsServiceController(ctx, fileDesc, sourceInfo, serviceDesc)
|
|
|
13
13
|
const chunks = [];
|
|
14
14
|
const Metadata = (0, ts_poet_1.imp)("t:Metadata@@grpc/grpc-js");
|
|
15
15
|
(0, utils_1.maybeAddComment)(options, sourceInfo, chunks, serviceDesc.options?.deprecated);
|
|
16
|
-
const t = options.context ? `<${main_1.contextTypeVar}>` : "";
|
|
16
|
+
const t = options.context ? `<${(0, main_1.contextTypeVar)(options)}>` : "";
|
|
17
17
|
chunks.push((0, ts_poet_1.code) `
|
|
18
18
|
export interface ${serviceDesc.name}Controller${t} {
|
|
19
19
|
`);
|
|
@@ -53,7 +53,7 @@ function generateNestjsServiceController(ctx, fileDesc, sourceInfo, serviceDesc)
|
|
|
53
53
|
chunks.push((0, ts_poet_1.code) `
|
|
54
54
|
${methodDesc.formattedName}(${(0, ts_poet_1.joinCode)(params, { on: ", " })}): ${returns};
|
|
55
55
|
`);
|
|
56
|
-
if (options.context) {
|
|
56
|
+
if (options.context && options.useContextDataloaders) {
|
|
57
57
|
const batchMethod = (0, types_1.detectBatchMethod)(ctx, fileDesc, serviceDesc, methodDesc);
|
|
58
58
|
if (batchMethod) {
|
|
59
59
|
const maybeCtx = options.context ? "ctx: Context," : "";
|
|
@@ -74,7 +74,7 @@ function generateNestjsServiceClient(ctx, fileDesc, sourceInfo, serviceDesc) {
|
|
|
74
74
|
const chunks = [];
|
|
75
75
|
const Metadata = (0, ts_poet_1.imp)("t:Metadata@@grpc/grpc-js");
|
|
76
76
|
(0, utils_1.maybeAddComment)(options, sourceInfo, chunks);
|
|
77
|
-
const t = options.context ? `<${main_1.contextTypeVar}>` : ``;
|
|
77
|
+
const t = options.context ? `<${(0, main_1.contextTypeVar)(options)}>` : ``;
|
|
78
78
|
chunks.push((0, ts_poet_1.code) `
|
|
79
79
|
export interface ${serviceDesc.name}Client${t} {
|
|
80
80
|
`);
|
|
@@ -102,7 +102,7 @@ function generateNestjsServiceClient(ctx, fileDesc, sourceInfo, serviceDesc) {
|
|
|
102
102
|
${(0, ts_poet_1.joinCode)(params, { on: "," })}
|
|
103
103
|
): ${returns};
|
|
104
104
|
`);
|
|
105
|
-
if (options.context) {
|
|
105
|
+
if (options.context && options.useContextDataloaders) {
|
|
106
106
|
const batchMethod = (0, types_1.detectBatchMethod)(ctx, fileDesc, serviceDesc, methodDesc);
|
|
107
107
|
if (batchMethod) {
|
|
108
108
|
const maybeContext = options.context ? `ctx: Context,` : "";
|
|
@@ -24,7 +24,7 @@ function generateService(ctx, fileDesc, sourceInfo, serviceDesc) {
|
|
|
24
24
|
const { options } = ctx;
|
|
25
25
|
const chunks = [];
|
|
26
26
|
(0, utils_1.maybeAddComment)(options, sourceInfo, chunks, serviceDesc.options?.deprecated);
|
|
27
|
-
const maybeTypeVar = options.context ? `<${main_1.contextTypeVar}>` : "";
|
|
27
|
+
const maybeTypeVar = options.context ? `<${(0, main_1.contextTypeVar)(options)}>` : "";
|
|
28
28
|
chunks.push((0, ts_poet_1.code) `export interface ${(0, ts_poet_1.def)(serviceDesc.name)}${maybeTypeVar} {`);
|
|
29
29
|
serviceDesc.method.forEach((methodDesc, index) => {
|
|
30
30
|
(0, utils_1.assertInstanceOf)(methodDesc, utils_1.FormattedMethodDescriptor);
|
|
@@ -61,7 +61,7 @@ function generateService(ctx, fileDesc, sourceInfo, serviceDesc) {
|
|
|
61
61
|
}
|
|
62
62
|
chunks.push((0, ts_poet_1.code) `${methodDesc.formattedName}(${(0, ts_poet_1.joinCode)(params, { on: "," })}): ${(0, types_1.responsePromiseOrObservable)(ctx, methodDesc)};`);
|
|
63
63
|
// If this is a batch method, auto-generate the singular version of it
|
|
64
|
-
if (options.context) {
|
|
64
|
+
if (options.context && options.useContextDataloaders) {
|
|
65
65
|
const batchMethod = (0, types_1.detectBatchMethod)(ctx, fileDesc, serviceDesc, methodDesc);
|
|
66
66
|
if (batchMethod) {
|
|
67
67
|
chunks.push((0, ts_poet_1.code) `${batchMethod.singleMethodName}(
|
|
@@ -208,7 +208,7 @@ function generateServiceClientImpl(ctx, fileDesc, serviceDesc) {
|
|
|
208
208
|
chunks.push((0, ts_poet_1.code) `export const ${serviceNameConst} = "${serviceName}";`);
|
|
209
209
|
// Define the FooServiceImpl class
|
|
210
210
|
const i = options.context ? `${name}<Context>` : name;
|
|
211
|
-
const t = options.context ? `<${main_1.contextTypeVar}>` : "";
|
|
211
|
+
const t = options.context ? `<${(0, main_1.contextTypeVar)(options)}>` : "";
|
|
212
212
|
chunks.push((0, ts_poet_1.code) `export class ${name}ClientImpl${t} implements ${(0, ts_poet_1.def)(i)} {`);
|
|
213
213
|
// Create the constructor(rpc: Rpc)
|
|
214
214
|
const rpcType = options.context ? "Rpc<Context>" : "Rpc";
|
|
@@ -226,13 +226,14 @@ function generateServiceClientImpl(ctx, fileDesc, serviceDesc) {
|
|
|
226
226
|
// Create a method for each FooService method
|
|
227
227
|
for (const methodDesc of serviceDesc.method) {
|
|
228
228
|
// See if this fuzzy matches to a batchable method
|
|
229
|
-
if (options.context) {
|
|
229
|
+
if (options.context && options.useContextDataloaders) {
|
|
230
230
|
const batchMethod = (0, types_1.detectBatchMethod)(ctx, fileDesc, serviceDesc, methodDesc);
|
|
231
231
|
if (batchMethod) {
|
|
232
232
|
chunks.push(generateBatchingRpcMethod(ctx, batchMethod));
|
|
233
233
|
}
|
|
234
234
|
}
|
|
235
235
|
if (options.context &&
|
|
236
|
+
options.useContextDataloaders &&
|
|
236
237
|
methodDesc.name.match(/^Get[A-Z]/) &&
|
|
237
238
|
!methodDesc.serverStreaming &&
|
|
238
239
|
!methodDesc.clientStreaming) {
|
package/build/src/main.d.ts
CHANGED
|
@@ -39,6 +39,7 @@ declare function makeObjectIdMethods(): {
|
|
|
39
39
|
declare function makeTimestampMethods(options: Options, longs: ReturnType<typeof makeLongUtils>, bytes: ReturnType<typeof makeByteUtils>): {
|
|
40
40
|
toTimestamp: ConditionalOutput;
|
|
41
41
|
fromTimestamp: ConditionalOutput;
|
|
42
|
+
timestampFractionRegex: ConditionalOutput;
|
|
42
43
|
fromJsonTimestamp: ConditionalOutput;
|
|
43
44
|
};
|
|
44
45
|
declare function makeComparisonUtils(): {
|
|
@@ -57,5 +58,5 @@ declare function makeExtensionClass(options: Options): {
|
|
|
57
58
|
declare function makeAssertionUtils(bytes: ReturnType<typeof makeByteUtils>): {
|
|
58
59
|
fail: ConditionalOutput;
|
|
59
60
|
};
|
|
60
|
-
export declare
|
|
61
|
+
export declare function contextTypeVar(options: Options): string;
|
|
61
62
|
export {};
|
package/build/src/main.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.contextTypeVar = void 0;
|
|
4
3
|
exports.generateFile = generateFile;
|
|
5
4
|
exports.makeUtils = makeUtils;
|
|
5
|
+
exports.contextTypeVar = contextTypeVar;
|
|
6
6
|
const ts_poet_1 = require("ts-poet");
|
|
7
7
|
const ConditionalOutput_1 = require("ts-poet/build/ConditionalOutput");
|
|
8
8
|
const ts_proto_descriptors_1 = require("ts-proto-descriptors");
|
|
@@ -288,7 +288,7 @@ function generateFile(ctx, fileDesc) {
|
|
|
288
288
|
chunks.push((0, generate_grpc_web_1.addGrpcWebMisc)(ctx, hasServerStreamingMethods));
|
|
289
289
|
}
|
|
290
290
|
}
|
|
291
|
-
if (options.context) {
|
|
291
|
+
if (options.context && options.useContextDataloaders) {
|
|
292
292
|
chunks.push((0, generate_services_1.generateDataLoaderOptionsType)());
|
|
293
293
|
chunks.push((0, generate_services_1.generateDataLoadersType)());
|
|
294
294
|
}
|
|
@@ -773,6 +773,8 @@ function makeTimestampMethods(options, longs, bytes) {
|
|
|
773
773
|
return new ${bytes.globalThis}.Date(millis);
|
|
774
774
|
}
|
|
775
775
|
`);
|
|
776
|
+
// Declared once per file, so the regex isn't compiled on each fromJsonTimestamp call
|
|
777
|
+
const timestampFractionRegex = (0, ts_poet_1.conditionalOutput)("timestampFractionRegex", (0, ts_poet_1.code) `const timestampFractionRegex = /:\\d{2}\\.\\d+/;`);
|
|
776
778
|
const fromJsonTimestamp = (0, ts_poet_1.conditionalOutput)("fromJsonTimestamp", options.useDate === options_1.DateOption.DATE
|
|
777
779
|
? (0, ts_poet_1.code) `
|
|
778
780
|
function fromJsonTimestamp(o: any): Date {
|
|
@@ -802,13 +804,24 @@ function makeTimestampMethods(options, longs, bytes) {
|
|
|
802
804
|
if (o instanceof ${bytes.globalThis}.Date) {
|
|
803
805
|
return ${toTimestamp}(o);
|
|
804
806
|
} else if (typeof o === "string") {
|
|
805
|
-
|
|
807
|
+
// Date only keeps milliseconds, and parses longer fractions in an engine-specific way,
|
|
808
|
+
// so parse the fractional seconds ourselves and give Date only the whole seconds
|
|
809
|
+
const fraction = ${timestampFractionRegex}.exec(o);
|
|
810
|
+
if (fraction === null) {
|
|
811
|
+
return ${toTimestamp}(new ${bytes.globalThis}.Date(o));
|
|
812
|
+
}
|
|
813
|
+
const fractionNanos = Number(fraction[0].slice(4, 13).padEnd(9, "0"));
|
|
814
|
+
const wholeSeconds = new ${bytes.globalThis}.Date(o.replace(fraction[0], fraction[0].slice(0, 3)));
|
|
815
|
+
const timestamp = ${toTimestamp}(
|
|
816
|
+
new ${bytes.globalThis}.Date(wholeSeconds.getTime() + Math.trunc(fractionNanos / 1_000_000)),
|
|
817
|
+
);
|
|
818
|
+
return { ...timestamp, nanos: (timestamp.nanos ?? 0) + (fractionNanos % 1_000_000) };
|
|
806
819
|
} else {
|
|
807
820
|
return ${options.typePrefix}Timestamp${options.typeSuffix}.fromJSON(o);
|
|
808
821
|
}
|
|
809
822
|
}
|
|
810
823
|
`);
|
|
811
|
-
return { toTimestamp, fromTimestamp, fromJsonTimestamp };
|
|
824
|
+
return { toTimestamp, fromTimestamp, timestampFractionRegex, fromJsonTimestamp };
|
|
812
825
|
}
|
|
813
826
|
function makeComparisonUtils() {
|
|
814
827
|
const isObject = (0, ts_poet_1.conditionalOutput)("isObject", (0, ts_poet_1.code) `
|
|
@@ -938,9 +951,7 @@ function generateBaseInstanceFactory(ctx, fullName, messageDesc, fullTypeName) {
|
|
|
938
951
|
const { oneofIndex } = field;
|
|
939
952
|
if (!processedOneofs.has(oneofIndex)) {
|
|
940
953
|
processedOneofs.add(oneofIndex);
|
|
941
|
-
const name = options
|
|
942
|
-
? (0, utils_1.getFieldName)(field, options)
|
|
943
|
-
: (0, case_1.maybeSnakeToCamel)(messageDesc.oneofDecl[oneofIndex].name, ctx.options);
|
|
954
|
+
const name = (0, case_1.maybeSnakeToCamel)(messageDesc.oneofDecl[oneofIndex].name, ctx.options);
|
|
944
955
|
fields.push((0, ts_poet_1.code) `${(0, utils_1.safeAccessor)(name)}: ${(0, utils_1.nullOrUndefined)(options)}`);
|
|
945
956
|
}
|
|
946
957
|
continue;
|
|
@@ -1205,9 +1216,7 @@ function generateDecode(ctx, fullName, messageDesc) {
|
|
|
1205
1216
|
}
|
|
1206
1217
|
}
|
|
1207
1218
|
else if ((0, types_1.isWithinOneOfThatShouldBeUnion)(options, field)) {
|
|
1208
|
-
const oneofNameWithMessage = options
|
|
1209
|
-
? messageProperty
|
|
1210
|
-
: (0, utils_1.getPropertyAccessor)("message", (0, case_1.maybeSnakeToCamel)(messageDesc.oneofDecl[field.oneofIndex].name, options));
|
|
1219
|
+
const oneofNameWithMessage = (0, utils_1.getPropertyAccessor)("message", (0, case_1.maybeSnakeToCamel)(messageDesc.oneofDecl[field.oneofIndex].name, options));
|
|
1211
1220
|
const valueName = (0, utils_1.oneofValueName)(fieldName, options);
|
|
1212
1221
|
chunks.push((0, ts_poet_1.code) `
|
|
1213
1222
|
${tagCheck}
|
|
@@ -1509,9 +1518,7 @@ function generateEncode(ctx, fullName, messageDesc) {
|
|
|
1509
1518
|
else if ((0, types_1.isWithinOneOfThatShouldBeUnion)(options, field)) {
|
|
1510
1519
|
if (!processedOneofs.has(field.oneofIndex)) {
|
|
1511
1520
|
processedOneofs.add(field.oneofIndex);
|
|
1512
|
-
const oneofNameWithMessage = options
|
|
1513
|
-
? messageProperty
|
|
1514
|
-
: (0, utils_1.getPropertyAccessor)("message", (0, case_1.maybeSnakeToCamel)(messageDesc.oneofDecl[field.oneofIndex].name, options));
|
|
1521
|
+
const oneofNameWithMessage = (0, utils_1.getPropertyAccessor)("message", (0, case_1.maybeSnakeToCamel)(messageDesc.oneofDecl[field.oneofIndex].name, options));
|
|
1515
1522
|
chunks.push((0, ts_poet_1.code) `switch (${oneofNameWithMessage}?.$case) {`);
|
|
1516
1523
|
for (const oneOfField of oneOfFieldsDict[field.oneofIndex]) {
|
|
1517
1524
|
const writeSnippet = getEncodeWriteSnippet(ctx, oneOfField);
|
|
@@ -2386,9 +2393,7 @@ function generateToJson(ctx, fullName, fullProtobufTypeName, messageDesc) {
|
|
|
2386
2393
|
}
|
|
2387
2394
|
else if ((0, types_1.isWithinOneOfThatShouldBeUnion)(options, field)) {
|
|
2388
2395
|
// oneofs in a union are only output as `oneof name = ...`
|
|
2389
|
-
const oneofNameWithMessage = options
|
|
2390
|
-
? messageProperty
|
|
2391
|
-
: (0, utils_1.getPropertyAccessor)("message", (0, case_1.maybeSnakeToCamel)(messageDesc.oneofDecl[field.oneofIndex].name, options));
|
|
2396
|
+
const oneofNameWithMessage = (0, utils_1.getPropertyAccessor)("message", (0, case_1.maybeSnakeToCamel)(messageDesc.oneofDecl[field.oneofIndex].name, options));
|
|
2392
2397
|
const valueName = (0, utils_1.oneofValueName)(fieldName, options);
|
|
2393
2398
|
chunks.push((0, ts_poet_1.code) `
|
|
2394
2399
|
${currentIfTarget === oneofNameWithMessage ? "else " : ""}if (${oneofNameWithMessage}?.$case === '${fieldName}') {
|
|
@@ -2629,7 +2634,9 @@ function generateFromPartial(ctx, fullName, messageDesc) {
|
|
|
2629
2634
|
chunks.push((0, ts_poet_1.code) `}`);
|
|
2630
2635
|
return (0, ts_poet_1.joinCode)(chunks, { on: "\n" });
|
|
2631
2636
|
}
|
|
2632
|
-
|
|
2637
|
+
function contextTypeVar(options) {
|
|
2638
|
+
return options.useContextDataloaders ? "Context extends DataLoaders" : "Context";
|
|
2639
|
+
}
|
|
2633
2640
|
function convertFromObjectKey(ctx, messageDesc, field, variableName) {
|
|
2634
2641
|
const { keyType, keyField } = (0, types_1.detectMapType)(ctx, messageDesc, field);
|
|
2635
2642
|
if (keyType.toCodeString([]) === "string") {
|
package/build/src/options.d.ts
CHANGED
package/build/src/options.js
CHANGED