ts-proto 1.105.0 → 1.106.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/CHANGELOG.md CHANGED
@@ -1,3 +1,31 @@
1
+ ## [1.106.1](https://github.com/stephenh/ts-proto/compare/v1.106.0...v1.106.1) (2022-02-21)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * support json_name containing hyphen on all field types ([#521](https://github.com/stephenh/ts-proto/issues/521)) ([8d9e78e](https://github.com/stephenh/ts-proto/commit/8d9e78eb39c460f6727458f6a2dd149deb983668))
7
+
8
+ # [1.106.0](https://github.com/stephenh/ts-proto/compare/v1.105.2...v1.106.0) (2022-02-21)
9
+
10
+
11
+ ### Features
12
+
13
+ * Support json names containing non-alphanumeric characters ([#520](https://github.com/stephenh/ts-proto/issues/520)) ([ce44668](https://github.com/stephenh/ts-proto/commit/ce44668b8fe01b14f50ac3c5c950f73db769fa76))
14
+
15
+ ## [1.105.2](https://github.com/stephenh/ts-proto/compare/v1.105.1...v1.105.2) (2022-02-17)
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * Fix snakeToCamel single value parsing. ([#513](https://github.com/stephenh/ts-proto/issues/513)) ([e1ad866](https://github.com/stephenh/ts-proto/commit/e1ad866c95751c37ed13f02f4da2dc9076ab4758))
21
+
22
+ ## [1.105.1](https://github.com/stephenh/ts-proto/compare/v1.105.0...v1.105.1) (2022-02-14)
23
+
24
+
25
+ ### Bug Fixes
26
+
27
+ * generate canonical JSON encoding for FieldMasks ([#510](https://github.com/stephenh/ts-proto/issues/510)) ([0ec4e97](https://github.com/stephenh/ts-proto/commit/0ec4e97a2649dc15af1c925f8a2ff6adf1e17d9b))
28
+
1
29
  # [1.105.0](https://github.com/stephenh/ts-proto/compare/v1.104.1...v1.105.0) (2022-02-12)
2
30
 
3
31
 
package/README.markdown CHANGED
@@ -23,6 +23,7 @@
23
23
  - [Only Types](#only-types)
24
24
  - [NestJS Support](#nestjs-support)
25
25
  - [Watch Mode](#watch-mode)
26
+ - [Basic gRPC implementation](#basic-grpc-implementation)
26
27
  - [Sponsors](#sponsors)
27
28
  - [Development](#development)
28
29
  - [Assumptions](#assumptions)
@@ -331,7 +332,7 @@ Generated code will be placed in the Gradle build directory.
331
332
 
332
333
  (Note that this only uses the grpc-web runtime, you don't need to use any of their generated code, i.e. the ts-proto output replaces their `ts-protoc-gen` output.)
333
334
 
334
- You'll need to add the `@improbable-eng/grpc-web` and a transport to your project's `package.json`; see the `integration/grpc-web` directory for a working example.
335
+ You'll need to add the `@improbable-eng/grpc-web` and a transport to your project's `package.json`; see the `integration/grpc-web` directory for a working example. Also see [#504](https://github.com/stephenh/ts-proto/issues/504) for integrating with [grpc-web-devtools](https://github.com/SafetyCulture/grpc-web-devtools).
335
336
 
336
337
  - With `--ts_proto_opt=returnObservable=true`, the return type of service methods will be `Observable<T>` instead of `Promise<T>`.
337
338
 
@@ -406,6 +407,51 @@ If you want to run `ts-proto` on every change of a proto file, you'll need to us
406
407
  "proto:watch": "chokidar \"**/*.proto\" -c \"npm run proto:generate\""
407
408
  ```
408
409
 
410
+ ### Basic gRPC implementation
411
+
412
+ `ts-proto` is RPC framework agnostic - how you transmit your data to and from
413
+ your data source is up to you. The generated client implementations all expect
414
+ a `rpc` parameter, which type is defined like this:
415
+
416
+ ```ts
417
+ interface Rpc {
418
+ request(service: string, method: string, data: Uint8Array): Promise<Uint8Array>;
419
+ }
420
+ ```
421
+
422
+ If you're working with gRPC, a simple implementation could look like this:
423
+
424
+ ```ts
425
+ type RpcImpl = (service: string, method: string, data: Uint8Array) => Promise<Uint8Array>;
426
+
427
+ const sendRequest: RpcImpl = (service, method, data) => {
428
+ // Conventionally in gRPC, the request path looks like
429
+ // "package.names.ServiceName/MethodName",
430
+ // we therefore construct such a string
431
+ const path = `${service}/${method}`;
432
+
433
+ return new Promise((resolve, reject) => {
434
+ // makeUnaryRequest transmits the result (and error) with a callback
435
+ // transform this into a promise!
436
+ const resultCallback: UnaryCallback<any> = (err, res) => {
437
+ if (err) {
438
+ return reject(err);
439
+ }
440
+ resolve(res);
441
+ };
442
+
443
+ function passThrough(argument: any) {
444
+ return argument;
445
+ }
446
+
447
+ // Using passThrough as the serialize and deserialize functions
448
+ conn.makeUnaryRequest(path, passThrough, passThrough, data, resultCallback);
449
+ });
450
+ };
451
+
452
+ const rpc: Rpc = { request: sendRequest }
453
+ ```
454
+
409
455
  # Sponsors
410
456
 
411
457
  Kudos to our sponsors:
@@ -591,6 +637,7 @@ Their interpretation is defined by the Protobuf specification, and libraries are
591
637
  * [google.protobuf.Value](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#Value) &lrarr; `any | undefined` (i.e. `number | string | boolean | null | array | object`)
592
638
  * [google.protobuf.ListValue](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#ListValue) &lrarr; `any[]`
593
639
  * [google.protobuf.Struct](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#Struct) &lrarr; `{ [key: string]: any } | undefined`
640
+ * [google.protobuf.FieldMask](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#fieldmask) &lrarr; `string[]` (only in the JSON, `FieldMask` is still a message)
594
641
 
595
642
  ## Wrapper Types
596
643
 
package/build/main.js CHANGED
@@ -895,7 +895,9 @@ function generateFromJson(ctx, fullName, messageDesc) {
895
895
  // add a check for each incoming field
896
896
  messageDesc.field.forEach((field) => {
897
897
  const fieldName = case_1.maybeSnakeToCamel(field.name, options);
898
- const jsonName = utils_1.determineFieldJsonName(field, options);
898
+ const jsonName = utils_1.getFieldJsonName(field, options);
899
+ const jsonProperty = utils_1.getPropertyAccessor('object', jsonName);
900
+ const jsonPropertyOptional = utils_1.getPropertyAccessor('object', jsonName, true);
899
901
  // get code that extracts value from incoming object
900
902
  const readSnippet = (from) => {
901
903
  if (types_1.isEnum(field)) {
@@ -934,6 +936,9 @@ function generateFromJson(ctx, fullName, messageDesc) {
934
936
  else if (types_1.isAnyValueType(field) || types_1.isStructType(field)) {
935
937
  return ts_poet_1.code `${from}`;
936
938
  }
939
+ else if (types_1.isFieldMaskType(field)) {
940
+ return ts_poet_1.code `{paths: ${from}.split(",")}`;
941
+ }
937
942
  else if (types_1.isListValueType(field)) {
938
943
  return ts_poet_1.code `[...${from}]`;
939
944
  }
@@ -1010,8 +1015,8 @@ function generateFromJson(ctx, fullName, messageDesc) {
1010
1015
  const fieldType = types_1.toTypeName(ctx, messageDesc, field);
1011
1016
  const i = maybeCastToNumber(ctx, messageDesc, field, 'key');
1012
1017
  chunks.push(ts_poet_1.code `
1013
- ${fieldName}: ${ctx.utils.isObject}(object.${jsonName})
1014
- ? Object.entries(object.${jsonName}).reduce<${fieldType}>((acc, [key, value]) => {
1018
+ ${fieldName}: ${ctx.utils.isObject}(${jsonProperty})
1019
+ ? Object.entries(${jsonProperty}).reduce<${fieldType}>((acc, [key, value]) => {
1015
1020
  acc[${i}] = ${readSnippet('value')};
1016
1021
  return acc;
1017
1022
  }, {})
@@ -1021,12 +1026,12 @@ function generateFromJson(ctx, fullName, messageDesc) {
1021
1026
  else {
1022
1027
  const readValueSnippet = readSnippet('e');
1023
1028
  if (readValueSnippet.toString() === ts_poet_1.code `e`.toString()) {
1024
- chunks.push(ts_poet_1.code `${fieldName}: Array.isArray(object?.${jsonName}) ? [...object.${jsonName}] : [],`);
1029
+ chunks.push(ts_poet_1.code `${fieldName}: Array.isArray(${jsonPropertyOptional}) ? [...${jsonProperty}] : [],`);
1025
1030
  }
1026
1031
  else {
1027
1032
  // Explicit `any` type required to make TS with noImplicitAny happy. `object` is also `any` here.
1028
1033
  chunks.push(ts_poet_1.code `
1029
- ${fieldName}: Array.isArray(object?.${jsonName}) ? object.${jsonName}.map((e: any) => ${readValueSnippet}): [],
1034
+ ${fieldName}: Array.isArray(${jsonPropertyOptional}) ? ${jsonProperty}.map((e: any) => ${readValueSnippet}): [],
1030
1035
  `);
1031
1036
  }
1032
1037
  }
@@ -1039,36 +1044,36 @@ function generateFromJson(ctx, fullName, messageDesc) {
1039
1044
  const fieldName = case_1.maybeSnakeToCamel(messageDesc.oneofDecl[field.oneofIndex].name, options);
1040
1045
  chunks.push(ts_poet_1.code `${fieldName}: `);
1041
1046
  }
1042
- const ternaryIf = ts_poet_1.code `${ctx.utils.isSet}(object.${jsonName})`;
1043
- const ternaryThen = ts_poet_1.code `{ $case: '${fieldName}', ${fieldName}: ${readSnippet(`object.${jsonName}`)}`;
1047
+ const ternaryIf = ts_poet_1.code `${ctx.utils.isSet}(${jsonProperty})`;
1048
+ const ternaryThen = ts_poet_1.code `{ $case: '${fieldName}', ${fieldName}: ${readSnippet(`${jsonProperty}`)}`;
1044
1049
  chunks.push(ts_poet_1.code `${ternaryIf} ? ${ternaryThen}} : `);
1045
1050
  if (field === lastCase) {
1046
1051
  chunks.push(ts_poet_1.code `undefined,`);
1047
1052
  }
1048
1053
  }
1049
1054
  else if (types_1.isAnyValueType(field)) {
1050
- chunks.push(ts_poet_1.code `${fieldName}: ${ctx.utils.isSet}(object?.${jsonName})
1051
- ? ${readSnippet(`object.${jsonName}`)}
1055
+ chunks.push(ts_poet_1.code `${fieldName}: ${ctx.utils.isSet}(${jsonPropertyOptional})
1056
+ ? ${readSnippet(`${jsonProperty}`)}
1052
1057
  : undefined,
1053
1058
  `);
1054
1059
  }
1055
1060
  else if (types_1.isStructType(field)) {
1056
- chunks.push(ts_poet_1.code `${fieldName}: ${ctx.utils.isObject}(object.${jsonName})
1057
- ? ${readSnippet(`object.${jsonName}`)}
1061
+ chunks.push(ts_poet_1.code `${fieldName}: ${ctx.utils.isObject}(${jsonProperty})
1062
+ ? ${readSnippet(`${jsonProperty}`)}
1058
1063
  : undefined,`);
1059
1064
  }
1060
1065
  else if (types_1.isListValueType(field)) {
1061
1066
  chunks.push(ts_poet_1.code `
1062
- ${fieldName}: Array.isArray(object.${jsonName})
1063
- ? ${readSnippet(`object.${jsonName}`)}
1067
+ ${fieldName}: Array.isArray(${jsonProperty})
1068
+ ? ${readSnippet(`${jsonProperty}`)}
1064
1069
  : undefined,
1065
1070
  `);
1066
1071
  }
1067
1072
  else {
1068
1073
  const fallback = types_1.isWithinOneOf(field) ? 'undefined' : types_1.defaultValue(ctx, field);
1069
1074
  chunks.push(ts_poet_1.code `
1070
- ${fieldName}: ${ctx.utils.isSet}(object.${jsonName})
1071
- ? ${readSnippet(`object.${jsonName}`)}
1075
+ ${fieldName}: ${ctx.utils.isSet}(${jsonProperty})
1076
+ ? ${readSnippet(`${jsonProperty}`)}
1072
1077
  : ${fallback},
1073
1078
  `);
1074
1079
  }
@@ -1089,7 +1094,8 @@ function generateToJson(ctx, fullName, messageDesc) {
1089
1094
  // then add a case for each field
1090
1095
  messageDesc.field.forEach((field) => {
1091
1096
  const fieldName = case_1.maybeSnakeToCamel(field.name, options);
1092
- const jsonName = utils_1.determineFieldJsonName(field, options);
1097
+ const jsonName = utils_1.getFieldJsonName(field, options);
1098
+ const jsonProperty = utils_1.getPropertyAccessor('obj', jsonName);
1093
1099
  const readSnippet = (from) => {
1094
1100
  if (types_1.isEnum(field)) {
1095
1101
  const toJson = types_1.getEnumMethod(ctx, field.typeName, 'ToJSON');
@@ -1151,6 +1157,9 @@ function generateToJson(ctx, fullName, messageDesc) {
1151
1157
  else if (types_1.isAnyValueType(field)) {
1152
1158
  return ts_poet_1.code `${from}`;
1153
1159
  }
1160
+ else if (types_1.isFieldMaskType(field)) {
1161
+ return ts_poet_1.code `${from}.paths.join()`;
1162
+ }
1154
1163
  else if (types_1.isMessage(field) && !types_1.isValueType(ctx, field) && !types_1.isMapType(ctx, messageDesc, field)) {
1155
1164
  const type = types_1.basicTypeName(ctx, field, { keepValueType: true });
1156
1165
  return ts_poet_1.code `${from} ? ${type}.toJSON(${from}) : ${types_1.defaultValue(ctx, field)}`;
@@ -1177,10 +1186,10 @@ function generateToJson(ctx, fullName, messageDesc) {
1177
1186
  if (types_1.isMapType(ctx, messageDesc, field)) {
1178
1187
  // Maps might need their values transformed, i.e. bytes --> base64
1179
1188
  chunks.push(ts_poet_1.code `
1180
- obj.${jsonName} = {};
1189
+ ${jsonProperty} = {};
1181
1190
  if (message.${fieldName}) {
1182
1191
  Object.entries(message.${fieldName}).forEach(([k, v]) => {
1183
- obj.${jsonName}[k] = ${readSnippet('v')};
1192
+ ${jsonProperty}[k] = ${readSnippet('v')};
1184
1193
  });
1185
1194
  }
1186
1195
  `);
@@ -1189,9 +1198,9 @@ function generateToJson(ctx, fullName, messageDesc) {
1189
1198
  // Arrays might need their elements transformed
1190
1199
  chunks.push(ts_poet_1.code `
1191
1200
  if (message.${fieldName}) {
1192
- obj.${jsonName} = message.${fieldName}.map(e => ${readSnippet('e')});
1201
+ ${jsonProperty} = message.${fieldName}.map(e => ${readSnippet('e')});
1193
1202
  } else {
1194
- obj.${jsonName} = [];
1203
+ ${jsonProperty} = [];
1195
1204
  }
1196
1205
  `);
1197
1206
  }
@@ -1199,11 +1208,11 @@ function generateToJson(ctx, fullName, messageDesc) {
1199
1208
  // oneofs in a union are only output as `oneof name = ...`
1200
1209
  const oneofName = case_1.maybeSnakeToCamel(messageDesc.oneofDecl[field.oneofIndex].name, options);
1201
1210
  const v = readSnippet(`message.${oneofName}?.${fieldName}`);
1202
- chunks.push(ts_poet_1.code `message.${oneofName}?.$case === '${fieldName}' && (obj.${jsonName} = ${v});`);
1211
+ chunks.push(ts_poet_1.code `message.${oneofName}?.$case === '${fieldName}' && (${jsonProperty} = ${v});`);
1203
1212
  }
1204
1213
  else {
1205
1214
  const v = readSnippet(`message.${fieldName}`);
1206
- chunks.push(ts_poet_1.code `message.${fieldName} !== undefined && (obj.${jsonName} = ${v});`);
1215
+ chunks.push(ts_poet_1.code `message.${fieldName} !== undefined && (${jsonProperty} = ${v});`);
1207
1216
  }
1208
1217
  });
1209
1218
  chunks.push(ts_poet_1.code `return obj;`);
@@ -1323,7 +1332,7 @@ function generateFromPartial(ctx, fullName, messageDesc) {
1323
1332
  && object.${oneofName}?.${fieldName} !== null
1324
1333
  ) {
1325
1334
  message.${oneofName} = { $case: '${fieldName}', ${fieldName}: ${v} };
1326
- }
1335
+ }
1327
1336
  `);
1328
1337
  }
1329
1338
  else if (readSnippet(`x`).toCodeString() == 'x') {
@@ -1362,7 +1371,7 @@ function generateWrap(ctx, fullProtoTypeName) {
1362
1371
  if (ctx.options.oneof === options_1.OneofOption.UNIONS) {
1363
1372
  chunks.push(ts_poet_1.code `wrap(value: any): Value {
1364
1373
  const result = createBaseValue();
1365
-
1374
+
1366
1375
  if (value === null) {
1367
1376
  result.kind = {$case: 'nullValue', nullValue: NullValue.NULL_VALUE};
1368
1377
  } else if (typeof value === 'boolean') {
package/build/options.js CHANGED
@@ -111,6 +111,9 @@ function optionsFromParameter(parameter) {
111
111
  else if (options.snakeToCamel === true) {
112
112
  options.snakeToCamel = ['keys', 'json'];
113
113
  }
114
+ else if (typeof options.snakeToCamel === 'string') {
115
+ options.snakeToCamel = [options.snakeToCamel];
116
+ }
114
117
  return options;
115
118
  }
116
119
  exports.optionsFromParameter = optionsFromParameter;
@@ -118,8 +121,14 @@ exports.optionsFromParameter = optionsFromParameter;
118
121
  function parseParameter(parameter) {
119
122
  const options = {};
120
123
  const pairs = parameter.split(',').map((s) => s.split('='));
121
- pairs.forEach(([key, value]) => {
122
- options[key] = value === 'true' ? true : value === 'false' ? false : value;
124
+ pairs.forEach(([key, _value]) => {
125
+ const value = _value === 'true' ? true : _value === 'false' ? false : _value;
126
+ if (options[key]) {
127
+ options[key] = [options[key], value];
128
+ }
129
+ else {
130
+ options[key] = value;
131
+ }
123
132
  });
124
133
  return options;
125
134
  }
package/build/types.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.detectBatchMethod = exports.responsePromiseOrObservable = exports.responseObservable = exports.responsePromise = exports.responseType = exports.requestType = exports.rawRequestType = exports.detectMapType = exports.toTypeName = exports.getEnumMethod = exports.messageToTypeName = exports.wrapperTypeName = exports.valueTypeName = exports.isEmptyType = exports.isLongValueType = exports.isStructTypeName = exports.isStructType = exports.isListValueTypeName = exports.isListValueType = exports.isBytesValueType = exports.isAnyValueTypeName = exports.isAnyValueType = exports.isValueType = exports.isTimestamp = exports.isObjectId = exports.isMapType = exports.isWholeNumber = exports.isLong = exports.isRepeated = exports.isWithinOneOfThatShouldBeUnion = exports.isWithinOneOf = exports.isEnum = exports.isMessage = exports.isBytes = exports.isPrimitive = exports.isOptionalProperty = exports.isScalar = exports.createTypeMap = exports.notDefaultCheck = exports.defaultValue = exports.packedType = exports.toReaderCall = exports.basicTypeName = exports.basicLongWireType = exports.basicWireType = void 0;
3
+ exports.detectBatchMethod = exports.responsePromiseOrObservable = exports.responseObservable = exports.responsePromise = exports.responseType = exports.requestType = exports.rawRequestType = exports.detectMapType = exports.toTypeName = exports.getEnumMethod = exports.messageToTypeName = exports.wrapperTypeName = exports.valueTypeName = exports.isEmptyType = exports.isLongValueType = exports.isStructTypeName = exports.isStructType = exports.isListValueTypeName = exports.isListValueType = exports.isFieldMaskTypeName = exports.isFieldMaskType = exports.isBytesValueType = exports.isAnyValueTypeName = exports.isAnyValueType = exports.isValueType = exports.isTimestamp = exports.isObjectId = exports.isMapType = exports.isWholeNumber = exports.isLong = exports.isRepeated = exports.isWithinOneOfThatShouldBeUnion = exports.isWithinOneOf = exports.isEnum = exports.isMessage = exports.isBytes = exports.isPrimitive = exports.isOptionalProperty = exports.isScalar = exports.createTypeMap = exports.notDefaultCheck = exports.defaultValue = exports.packedType = exports.toReaderCall = exports.basicTypeName = exports.basicLongWireType = exports.basicWireType = void 0;
4
4
  const ts_proto_descriptors_1 = require("ts-proto-descriptors");
5
5
  const ts_poet_1 = require("ts-poet");
6
6
  const options_1 = require("./options");
@@ -403,6 +403,14 @@ function isBytesValueType(field) {
403
403
  return field.typeName === '.google.protobuf.BytesValue';
404
404
  }
405
405
  exports.isBytesValueType = isBytesValueType;
406
+ function isFieldMaskType(field) {
407
+ return isFieldMaskTypeName(field.typeName);
408
+ }
409
+ exports.isFieldMaskType = isFieldMaskType;
410
+ function isFieldMaskTypeName(typeName) {
411
+ return typeName === 'google.protobuf.FieldMask' || typeName === '.google.protobuf.FieldMask';
412
+ }
413
+ exports.isFieldMaskTypeName = isFieldMaskTypeName;
406
414
  function isListValueType(field) {
407
415
  return isListValueTypeName(field.typeName);
408
416
  }
package/build/utils.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.impProto = exports.determineFieldJsonName = exports.FormattedMethodDescriptor = exports.assertInstanceOf = exports.maybePrefixPackage = exports.prefixDisableLinter = exports.maybeAddComment = exports.upperFirst = exports.lowerFirst = exports.singular = exports.fail = exports.readToBuffer = exports.protoFilesToGenerate = void 0;
3
+ exports.impProto = exports.getPropertyAccessor = exports.getFieldJsonName = exports.FormattedMethodDescriptor = exports.assertInstanceOf = exports.maybePrefixPackage = exports.prefixDisableLinter = exports.maybeAddComment = exports.upperFirst = exports.lowerFirst = exports.singular = exports.fail = exports.readToBuffer = exports.protoFilesToGenerate = void 0;
4
4
  const ts_poet_1 = require("ts-poet");
5
5
  const options_1 = require("./options");
6
6
  const case_1 = require("./case");
@@ -144,7 +144,7 @@ class FormattedMethodDescriptor {
144
144
  }
145
145
  }
146
146
  exports.FormattedMethodDescriptor = FormattedMethodDescriptor;
147
- function determineFieldJsonName(field, options) {
147
+ function getFieldJsonName(field, options) {
148
148
  // jsonName will be camelCased by the protocol compiler, plus can be overridden by the user,
149
149
  // so just use that instead of our own maybeSnakeToCamel
150
150
  if (options.snakeToCamel.includes('json')) {
@@ -154,7 +154,21 @@ function determineFieldJsonName(field, options) {
154
154
  return field.name;
155
155
  }
156
156
  }
157
- exports.determineFieldJsonName = determineFieldJsonName;
157
+ exports.getFieldJsonName = getFieldJsonName;
158
+ /**
159
+ * Returns a snippet for reading an object's property, such as `foo.bar`, or `foo['bar']` if the property name contains unusual characters.
160
+ * For simplicity, we don't match the ECMA 5/6 rules for valid identifiers exactly, and return array syntax liberally.
161
+ * @param objectName
162
+ * @param propertyName
163
+ * @param optional
164
+ */
165
+ function getPropertyAccessor(objectName, propertyName, optional = false) {
166
+ let validIdentifier = /^[a-zA-Z_$][\w$]*$/;
167
+ return validIdentifier.test(propertyName)
168
+ ? `${objectName}${optional ? '?' : ''}.${propertyName}`
169
+ : `${objectName}${optional ? '?.' : ''}[${JSON.stringify(propertyName)}]`;
170
+ }
171
+ exports.getPropertyAccessor = getPropertyAccessor;
158
172
  function impProto(options, module, type) {
159
173
  return ts_poet_1.imp(`${type}@./${module}${options.fileSuffix}`);
160
174
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-proto",
3
- "version": "1.105.0",
3
+ "version": "1.106.1",
4
4
  "description": "",
5
5
  "main": "build/plugin.js",
6
6
  "repository": "github:stephenh/ts-proto",