From c28d21064462814d2b6af269f6156d672853d7be Mon Sep 17 00:00:00 2001 From: Joshua Wise Date: Mon, 31 Jan 2022 12:11:12 -0600 Subject: [PATCH 01/33] fix: support for nested messages and enums within group blocks --- src/parse.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/parse.js b/src/parse.js index 0e538be0b..a36a97aed 100644 --- a/src/parse.js +++ b/src/parse.js @@ -443,6 +443,14 @@ function parse(source, root, options) { } break; + case "message": + parseType(type, token); + break; + + case "enum": + parseEnum(type, token); + break; + /* istanbul ignore next */ default: throw illegal(token); // there are no groups with proto3 semantics From a75d7b8d8e118a9544071249c12be29a84319a7f Mon Sep 17 00:00:00 2001 From: Joshua Wise Date: Mon, 31 Jan 2022 12:34:17 -0600 Subject: [PATCH 02/33] test: support for nested messages and enums within group blocks --- tests/data/test.json | 55 +++++++++++++++++++++++++++++++++++++++---- tests/data/test.proto | 19 ++++++++++++--- 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/tests/data/test.json b/tests/data/test.json index c91edc0fc..5186ed4a2 100644 --- a/tests/data/test.json +++ b/tests/data/test.json @@ -412,18 +412,26 @@ "type": "OptionalGroup", "id": 3 }, + "messageInGroup": { + "type": "MessageInGroup", + "id": 4 + }, + "enumInGroup": { + "type": "EnumInGroup", + "id": 5 + }, "id": { "type": "string", - "id": 4 + "id": 6 }, "requiredSimple": { "rule": "required", "type": "Simple2", - "id": 5 + "id": 7 }, "optionalSimple": { "type": "Simple2", - "id": 6 + "id": 8 } }, "nested": { @@ -464,6 +472,45 @@ } }, "group": true + }, + "MessageInGroup": { + "fields": { + "id": { + "rule": "required", + "type": "NetedMessage", + "id": 1 + } + }, + "nested": { + "NetedMessage": { + "fields": { + "id": { + "rule": "optional", + "type": "string", + "id": 1 + } + } + } + }, + "group": true + }, + "EnumInGroup": { + "fields": { + "id": { + "rule": "required", + "type": "NestedEnum", + "id": 1 + } + }, + "nested": { + "NestedEnum": { + "values": { + "first": 0, + "second": 1 + } + } + }, + "group": true } } }, @@ -1514,4 +1561,4 @@ } } } -} \ No newline at end of file +} diff --git a/tests/data/test.proto b/tests/data/test.proto index 0d73a7a49..732f975f7 100644 --- a/tests/data/test.proto +++ b/tests/data/test.proto @@ -182,9 +182,22 @@ message TestGroup { optional group OptionalGroup = 3 { required string id = 1; } - optional string id = 4; - required Simple2 required_simple = 5; - optional Simple2 optional_simple = 6; + optional group MessageInGroup = 4 { + message NestedMessage { + optional string id = 1; + } + required NestedMessage id = 1; + } + optional group EnumInGroup = 5 { + enum NestedEnum { + first = 0; + second = 1; + } + required NestedEnum id = 1; + } + optional string id = 6; + required Simple2 required_simple = 7; + optional Simple2 optional_simple = 8; } message TestGroup1 { From bb099f26739e8748074c3295177234eac6c73ca0 Mon Sep 17 00:00:00 2001 From: Udit Vasu Date: Tue, 5 Jul 2022 17:58:54 +0530 Subject: [PATCH 03/33] chore: add google/rpc protos --- google/rpc/error_details.json | 204 +++++++++++++++++++++++++++++++++ google/rpc/error_details.proto | 77 +++++++++++++ google/rpc/status.json | 45 ++++++++ google/rpc/status.proto | 11 ++ 4 files changed, 337 insertions(+) create mode 100644 google/rpc/error_details.json create mode 100644 google/rpc/error_details.proto create mode 100644 google/rpc/status.json create mode 100644 google/rpc/status.proto diff --git a/google/rpc/error_details.json b/google/rpc/error_details.json new file mode 100644 index 000000000..43aa1a5c5 --- /dev/null +++ b/google/rpc/error_details.json @@ -0,0 +1,204 @@ +{ + "nested": { + "google": { + "nested": { + "rpc": { + "nested": { + "RetryInfo": { + "fields": { + "retryDelay": { + "type": "Duration", + "id": 1 + } + }, + "nested": { + "Duration": { + "fields": { + "seconds": { + "type": "int64", + "id": 1 + }, + "nanos": { + "type": "int32", + "id": 2 + } + } + } + } + }, + "DebugInfo": { + "fields": { + "stackEntries": { + "rule": "repeated", + "type": "string", + "id": 1 + }, + "detail": { + "type": "string", + "id": 2 + } + } + }, + "QuotaFailure": { + "fields": { + "violations": { + "rule": "repeated", + "type": "Violation", + "id": 1 + } + }, + "nested": { + "Violation": { + "fields": { + "subject": { + "type": "string", + "id": 1 + }, + "description": { + "type": "string", + "id": 2 + } + } + } + } + }, + "ErrorInfo": { + "fields": { + "reason": { + "type": "string", + "id": 1 + }, + "domain": { + "type": "string", + "id": 2 + }, + "metadata": { + "keyType": "string", + "type": "string", + "id": 3 + } + } + }, + "PreconditionFailure": { + "fields": { + "violations": { + "rule": "repeated", + "type": "Violation", + "id": 1 + } + }, + "nested": { + "Violation": { + "fields": { + "type": { + "type": "string", + "id": 1 + }, + "subject": { + "type": "string", + "id": 2 + }, + "description": { + "type": "string", + "id": 3 + } + } + } + } + }, + "BadRequest": { + "fields": { + "fieldViolations": { + "rule": "repeated", + "type": "FieldViolation", + "id": 1 + } + }, + "nested": { + "FieldViolation": { + "fields": { + "field": { + "type": "string", + "id": 1 + }, + "description": { + "type": "string", + "id": 2 + } + } + } + } + }, + "RequestInfo": { + "fields": { + "requestId": { + "type": "string", + "id": 1 + }, + "servingData": { + "type": "string", + "id": 2 + } + } + }, + "ResourceInfo": { + "fields": { + "resourceType": { + "type": "string", + "id": 1 + }, + "resourceName": { + "type": "string", + "id": 2 + }, + "owner": { + "type": "string", + "id": 3 + }, + "description": { + "type": "string", + "id": 4 + } + } + }, + "Help": { + "fields": { + "links": { + "rule": "repeated", + "type": "Link", + "id": 1 + } + }, + "nested": { + "Link": { + "fields": { + "description": { + "type": "string", + "id": 1 + }, + "url": { + "type": "string", + "id": 2 + } + } + } + } + }, + "LocalizedMessage": { + "fields": { + "locale": { + "type": "string", + "id": 1 + }, + "message": { + "type": "string", + "id": 2 + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/google/rpc/error_details.proto b/google/rpc/error_details.proto new file mode 100644 index 000000000..40d90c372 --- /dev/null +++ b/google/rpc/error_details.proto @@ -0,0 +1,77 @@ +syntax = "proto3"; + +package google.rpc; + +message RetryInfo { + message Duration { + int64 seconds = 1; + int32 nanos = 2; + } + + Duration retry_delay = 1; +} + +message DebugInfo { + repeated string stack_entries = 1; + string detail = 2; +} + +message QuotaFailure { + message Violation { + string subject = 1; + string description = 2; + } + + repeated Violation violations = 1; +} + +message ErrorInfo { + string reason = 1; + string domain = 2; + map metadata = 3; +} + +message PreconditionFailure { + message Violation { + string type = 1; + string subject = 2; + string description = 3; + } + + repeated Violation violations = 1; +} + +message BadRequest { + message FieldViolation { + string field = 1; + string description = 2; + } + + repeated FieldViolation field_violations = 1; +} + +message RequestInfo { + string request_id = 1; + string serving_data = 2; +} + +message ResourceInfo { + string resource_type = 1; + string resource_name = 2; + string owner = 3; + string description = 4; +} + +message Help { + message Link { + string description = 1; + string url = 2; + } + + repeated Link links = 1; +} + +message LocalizedMessage { + string locale = 1; + string message = 2; +} diff --git a/google/rpc/status.json b/google/rpc/status.json new file mode 100644 index 000000000..18ba47f93 --- /dev/null +++ b/google/rpc/status.json @@ -0,0 +1,45 @@ +{ + "nested": { + "google": { + "nested": { + "protobuf": { + "nested": { + "Any": { + "fields": { + "type_url": { + "type": "string", + "id": 1 + }, + "value": { + "type": "bytes", + "id": 2 + } + } + } + } + }, + "rpc": { + "nested": { + "Status": { + "fields": { + "code": { + "type": "int32", + "id": 1 + }, + "message": { + "type": "string", + "id": 2 + }, + "details": { + "rule": "repeated", + "type": "google.protobuf.Any", + "id": 3 + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/google/rpc/status.proto b/google/rpc/status.proto new file mode 100644 index 000000000..50e324607 --- /dev/null +++ b/google/rpc/status.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package google.rpc; + +import "google/protobuf/any.proto"; + +message Status { + int32 code = 1; + string message = 2; + repeated google.protobuf.Any details = 3; +} From f154e215a8e09fb275d73970894146240df99646 Mon Sep 17 00:00:00 2001 From: Udit Vasu Date: Tue, 5 Jul 2022 17:59:33 +0530 Subject: [PATCH 04/33] chore: update package name --- package-lock.json | 7 ++++--- package.json | 11 +++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index c069f1c75..66ba33419 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,14 @@ { - "name": "protobufjs", + "name": "@postman/protobufjs", "version": "6.10.2", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "protobufjs", + "name": "@postman/protobufjs", "version": "6.10.2", "hasInstallScript": true, - "license": "BSD-3-Clause", + "license": "Apache-2.0", "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -5427,6 +5427,7 @@ "node_modules/jaguarjs-jsdoc": { "version": "0.0.1", "resolved": "git+ssh://git@github.com/dcodeIO/jaguarjs-jsdoc.git#ade85ac841f5ca8be40c60d506102039a036a8fa", + "integrity": "sha512-3FqwgUAqzFxO1FfRFvWUh3TtSrMSgtgeg5Twe64FbQTTXJ1I42K9hRnrwARv6NKUKi/74bPWaZHBlDj3b4Hx5A==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index 4f53ee32a..adced47b0 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,12 @@ { - "name": "protobufjs", + "name": "@postman/protobufjs", "version": "6.10.2", "versionScheme": "~", "description": "Protocol Buffers for JavaScript (& TypeScript).", - "author": "Daniel Wirtz ", - "license": "BSD-3-Clause", - "repository": "protobufjs/protobuf.js", - "bugs": "https://github.com/dcodeIO/protobuf.js/issues", - "homepage": "https://protobufjs.github.io/protobuf.js/", + "license": "Apache-2.0", + "repository": "https://github.com/postmanlabs/protobuf.js", + "bugs": "https://github.com/postmanlabs/protobuf.js/issues", + "homepage": "https://github.com/postmanlabs/protobuf.js", "keywords": [ "protobuf", "protocol-buffers", From ed28206b5329dfb3f5ec9b327a1aaa8a917a6195 Mon Sep 17 00:00:00 2001 From: Udit Vasu Date: Tue, 5 Jul 2022 18:27:09 +0530 Subject: [PATCH 05/33] 6.11.3-postman.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 66ba33419..e02ac5998 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@postman/protobufjs", - "version": "6.10.2", + "version": "6.11.3-postman.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@postman/protobufjs", - "version": "6.10.2", + "version": "6.11.3-postman.1", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index adced47b0..0c1de5e5d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@postman/protobufjs", - "version": "6.10.2", + "version": "6.11.3-postman.1", "versionScheme": "~", "description": "Protocol Buffers for JavaScript (& TypeScript).", "license": "Apache-2.0", From c87a7fd4817d168e94cc49c2e1652370c2349415 Mon Sep 17 00:00:00 2001 From: Udit Vasu Date: Wed, 10 Aug 2022 00:27:28 +0530 Subject: [PATCH 06/33] fix: lookup own properties from type, service, and namespace objects --- index.d.ts | 8 ++++++++ src/namespace.js | 2 +- src/service.js | 2 +- src/type.js | 6 +++--- src/util.js | 10 ++++++++++ 5 files changed, 23 insertions(+), 5 deletions(-) diff --git a/index.d.ts b/index.d.ts index be394f08f..49fbff132 100644 --- a/index.d.ts +++ b/index.d.ts @@ -2147,6 +2147,14 @@ export namespace util { */ function safeProp(prop: string): string; + /** + * Returns the value of a property found directly in a given object. + * @param object Source object + * @param prop Property name + * @returns Value or `undefined` if not set + */ + function getProp(object: object, prop: string): any; + /** * Converts the first character of a string to upper case. * @param str String to convert diff --git a/src/namespace.js b/src/namespace.js index de9f4cdb0..262dc5697 100644 --- a/src/namespace.js +++ b/src/namespace.js @@ -191,7 +191,7 @@ Namespace.prototype.addJSON = function addJSON(nestedJson) { * @returns {ReflectionObject|null} The reflection object or `null` if it doesn't exist */ Namespace.prototype.get = function get(name) { - return this.nested && this.nested[name] + return util.getProp(this.nested, name) || null; }; diff --git a/src/service.js b/src/service.js index bc2c3080c..0d2b9428b 100644 --- a/src/service.js +++ b/src/service.js @@ -98,7 +98,7 @@ function clearCache(service) { * @override */ Service.prototype.get = function get(name) { - return this.methods[name] + return util.getProp(this.methods, name) || Namespace.prototype.get.call(this, name); }; diff --git a/src/type.js b/src/type.js index 2e7bda49b..7835dab57 100644 --- a/src/type.js +++ b/src/type.js @@ -312,9 +312,9 @@ Type.prototype.resolveAll = function resolveAll() { * @override */ Type.prototype.get = function get(name) { - return this.fields[name] - || this.oneofs && this.oneofs[name] - || this.nested && this.nested[name] + return util.getProp(this.fields, name) + || util.getProp(this.oneofs, name) + || util.getProp(this.nested, name) || null; }; diff --git a/src/util.js b/src/util.js index c39d33a6a..2ef38f878 100644 --- a/src/util.js +++ b/src/util.js @@ -78,6 +78,16 @@ util.safeProp = function safeProp(prop) { return "." + prop; }; +/** + * Returns the value of a property found directly in a given object. + * @param {Object} object Source object + * @param {string} prop Property name + * @returns {*} Value or `undefined` if not set + */ +util.getProp = function get(object, prop) { + return object && Object.prototype.hasOwnProperty.call(object, prop) ? object[prop] : undefined; +}; + /** * Converts the first character of a string to upper case. * @param {string} str String to convert From e9fba6ed6708d8de9f8a68e0a55b9bfbc4b9b5b6 Mon Sep 17 00:00:00 2001 From: Udit Vasu Date: Wed, 10 Aug 2022 00:43:51 +0530 Subject: [PATCH 07/33] 7.0.0-postman.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1478ebdc4..4d0680667 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@postman/protobufjs", - "version": "6.11.3-postman.1", + "version": "7.0.0-postman.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@postman/protobufjs", - "version": "6.11.3-postman.1", + "version": "7.0.0-postman.1", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 55722b7e4..4c43ee773 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@postman/protobufjs", - "version": "6.11.3-postman.1", + "version": "7.0.0-postman.1", "versionScheme": "~", "description": "Protocol Buffers for JavaScript (& TypeScript).", "license": "Apache-2.0", From 41ad43f55ff52df0513d1896bcf6dea97e83fb43 Mon Sep 17 00:00:00 2001 From: Udit Vasu Date: Mon, 28 Nov 2022 02:51:13 +0530 Subject: [PATCH 08/33] 7.1.2-postman.2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 62246687e..42c73ffa2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@postman/protobufjs", - "version": "7.1.2-postman.1", + "version": "7.1.2-postman.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@postman/protobufjs", - "version": "7.1.2-postman.1", + "version": "7.1.2-postman.2", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index eed7cf5df..1ff8a963d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@postman/protobufjs", - "version": "7.1.2-postman.1", + "version": "7.1.2-postman.2", "versionScheme": "~", "description": "Protocol Buffers for JavaScript (& TypeScript).", "license": "Apache-2.0", From 0dd0d86ad42f6a935700e8a97763d854f53eba4c Mon Sep 17 00:00:00 2001 From: Joshua Wise Date: Wed, 22 Mar 2023 12:10:21 -0500 Subject: [PATCH 09/33] use original casing for common protobufs --- index.d.ts | 12 ++++++------ src/common.js | 36 ++++++++++++++++++------------------ tests/data/common.json | 26 +++++++++++++------------- 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/index.d.ts b/index.d.ts index 8f37ed622..f2b962221 100644 --- a/index.d.ts +++ b/index.d.ts @@ -42,12 +42,12 @@ export namespace common { /** Properties of a google.protobuf.Value message. */ interface IValue { kind?: string; - nullValue?: 0; - numberValue?: number; - stringValue?: string; - boolValue?: boolean; - structValue?: IStruct; - listValue?: IListValue; + null_value?: 0; + number_value?: number; + string_value?: string; + bool_value?: boolean; + struct_value?: IStruct; + list_value?: IListValue; } /** Properties of a google.protobuf.ListValue message. */ diff --git a/src/common.js b/src/common.js index 489ee1c67..fce6ee831 100644 --- a/src/common.js +++ b/src/common.js @@ -139,49 +139,49 @@ common("struct", { * @interface IValue * @type {Object} * @property {string} [kind] - * @property {0} [nullValue] - * @property {number} [numberValue] - * @property {string} [stringValue] - * @property {boolean} [boolValue] - * @property {IStruct} [structValue] - * @property {IListValue} [listValue] + * @property {0} [null_value] + * @property {number} [number_value] + * @property {string} [string_value] + * @property {boolean} [bool_value] + * @property {IStruct} [struct_value] + * @property {IListValue} [list_value] * @memberof common */ Value: { oneofs: { kind: { oneof: [ - "nullValue", - "numberValue", - "stringValue", - "boolValue", - "structValue", - "listValue" + "null_value", + "number_value", + "string_value", + "bool_value", + "struct_value", + "list_value" ] } }, fields: { - nullValue: { + null_value: { type: "NullValue", id: 1 }, - numberValue: { + number_value: { type: "double", id: 2 }, - stringValue: { + string_value: { type: "string", id: 3 }, - boolValue: { + bool_value: { type: "bool", id: 4 }, - structValue: { + struct_value: { type: "Struct", id: 5 }, - listValue: { + list_value: { type: "ListValue", id: 6 } diff --git a/tests/data/common.json b/tests/data/common.json index f2875a788..d21c69d8e 100644 --- a/tests/data/common.json +++ b/tests/data/common.json @@ -47,37 +47,37 @@ "oneofs": { "kind": { "oneof": [ - "nullValue", - "numberValue", - "stringValue", - "boolValue", - "structValue", - "listValue" + "null_value", + "number_value", + "string_value", + "bool_value", + "struct_value", + "list_value" ] } }, "fields": { - "nullValue": { + "null_value": { "type": "NullValue", "id": 1 }, - "numberValue": { + "number_value": { "type": "double", "id": 2 }, - "stringValue": { + "string_value": { "type": "string", "id": 3 }, - "boolValue": { + "bool_value": { "type": "bool", "id": 4 }, - "structValue": { + "struct_value": { "type": "Struct", "id": 5 }, - "listValue": { + "list_value": { "type": "ListValue", "id": 6 } @@ -114,4 +114,4 @@ } } } -} \ No newline at end of file +} From 41830e143c4649417a0003d704bcaf36ec04af6d Mon Sep 17 00:00:00 2001 From: Joshua Wise Date: Wed, 22 Mar 2023 12:12:40 -0500 Subject: [PATCH 10/33] v7.2.0-postman.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1ff8a963d..f7c3d362a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@postman/protobufjs", - "version": "7.1.2-postman.2", + "version": "7.2.0-postman.2", "versionScheme": "~", "description": "Protocol Buffers for JavaScript (& TypeScript).", "license": "Apache-2.0", From 676843fa792c0bf8d5e1d6124039c1ca167ad4ac Mon Sep 17 00:00:00 2001 From: Appurva Murawat Date: Wed, 26 Jun 2024 20:34:28 +0530 Subject: [PATCH 11/33] fix: transformation of map entry from File Descriptor to Reflection obj --- ext/descriptor/index.js | 88 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 81 insertions(+), 7 deletions(-) diff --git a/ext/descriptor/index.js b/ext/descriptor/index.js index 6aafd2ab8..b9a46ed27 100644 --- a/ext/descriptor/index.js +++ b/ext/descriptor/index.js @@ -70,7 +70,7 @@ var Namespace = $protobuf.Namespace, * @param {IFileDescriptorSet|Reader|Uint8Array} descriptor Descriptor * @returns {Root} Root instance */ -Root.fromDescriptor = function fromDescriptor(descriptor) { +Root.fromDescriptor = function fromDescriptor(descriptor, options) { // Decode the descriptor message if specified as a buffer: if (typeof descriptor.length === "number") @@ -89,7 +89,7 @@ Root.fromDescriptor = function fromDescriptor(descriptor) { root.files.push(filePackage.filename = fileDescriptor.name); if (fileDescriptor.messageType) for (i = 0; i < fileDescriptor.messageType.length; ++i) - filePackage.add(Type.fromDescriptor(fileDescriptor.messageType[i], fileDescriptor.syntax)); + filePackage.add(Type.fromDescriptor(fileDescriptor.messageType[i], fileDescriptor.syntax, options)); if (fileDescriptor.enumType) for (i = 0; i < fileDescriptor.enumType.length; ++i) filePackage.add(Enum.fromDescriptor(fileDescriptor.enumType[i])); @@ -198,7 +198,7 @@ var unnamedMessageIndex = 0; * @param {string} [syntax="proto2"] Syntax * @returns {Type} Type instance */ -Type.fromDescriptor = function fromDescriptor(descriptor, syntax) { +Type.fromDescriptor = function fromDescriptor(descriptor, syntax, options) { // Decode the descriptor message if specified as a buffer: if (typeof descriptor.length === "number") @@ -206,6 +206,7 @@ Type.fromDescriptor = function fromDescriptor(descriptor, syntax) { // Create the message type var type = new Type(descriptor.name.length ? descriptor.name : "Type" + unnamedMessageIndex++, fromDescriptorOptions(descriptor.options, exports.MessageOptions)), + useMapField = options && options.useMapField, i; /* Oneofs */ if (descriptor.oneofDecl) @@ -213,7 +214,15 @@ Type.fromDescriptor = function fromDescriptor(descriptor, syntax) { type.add(OneOf.fromDescriptor(descriptor.oneofDecl[i])); /* Fields */ if (descriptor.field) for (i = 0; i < descriptor.field.length; ++i) { - var field = Field.fromDescriptor(descriptor.field[i], syntax); + var mapType = useMapField && descriptor.nestedType + ? descriptor.nestedType.find(function (t) { + return t.options && t.options.mapEntry && t.name === descriptor.field[i].typeName + }) + : null; + var field = mapType + ? MapField.fromDescriptor(descriptor.field[i], mapType, syntax) + : Field.fromDescriptor(descriptor.field[i], syntax); + type.add(field); if (descriptor.field[i].hasOwnProperty("oneofIndex")) // eslint-disable-line no-prototype-builtins type.oneofsArray[descriptor.field[i].oneofIndex].add(field); @@ -223,9 +232,15 @@ Type.fromDescriptor = function fromDescriptor(descriptor, syntax) { type.add(Field.fromDescriptor(descriptor.extension[i], syntax)); /* Nested types */ if (descriptor.nestedType) for (i = 0; i < descriptor.nestedType.length; ++i) { - type.add(Type.fromDescriptor(descriptor.nestedType[i], syntax)); - if (descriptor.nestedType[i].options && descriptor.nestedType[i].options.mapEntry) - type.setOption("map_entry", true); + if (useMapField) { + // Nested types representing map entry are added as MapField and should not be added as Type + if (!descriptor.nestedType[i].options || !descriptor.nestedType[i].options.mapEntry) + type.add(Type.fromDescriptor(descriptor.nestedType[i], syntax, options)); + } else { + type.add(Type.fromDescriptor(descriptor.nestedType[i], syntax, options)); + if (descriptor.nestedType[i].options && descriptor.nestedType[i].options.mapEntry) + type.setOption("map_entry", true); + } } /* Nested enums */ if (descriptor.enumType) for (i = 0; i < descriptor.enumType.length; ++i) @@ -503,6 +518,65 @@ Field.prototype.toDescriptor = function toDescriptor(syntax) { return descriptor; }; +// --- MapField --- + +/** + * Creates a map field from a descriptor. + * @param {IFieldDescriptorProto|Reader|Uint8Array} descriptor Descriptor + * @param {IDescriptorProto} nestedType Nested type descriptor + * @returns {MapField} MapField instance + */ +MapField.fromDescriptor = function fromDescriptor(descriptor, nestedType) { + // Decode the descriptor message if specified as a buffer: + if (typeof descriptor.length === "number") + descriptor = exports.DescriptorProto.decode(descriptor); + + // Decode the nested type if specified as a buffer: + if (typeof nestedType.length === "number") + nestedType = exports.DescriptorProto.decode(nestedType); + + if (typeof descriptor.number !== "number") + throw Error("missing field id"); + + var typeName = nestedType.field[1].typeName; + var type = typeName && typeName.length + ? typeName + : fromDescriptorType(nestedType.field[1].type); + + var field = new MapField( + descriptor.name.length ? descriptor.name : "field" + descriptor.number, + descriptor.number, + fromDescriptorType(nestedType.field[0].type), + type, + fromDescriptorOptions(descriptor.options, exports.FieldOptions) + ); + + var extendee = descriptor.extendee; + if (extendee !== undefined) { + field.extend = extendee.length ? extendee : undefined; + } + + if (descriptor.defaultValue && descriptor.defaultValue.length) { + var defaultValue = descriptor.defaultValue; + switch (defaultValue) { + case "true": case "TRUE": + defaultValue = true; + break; + case "false": case "FALSE": + defaultValue = false; + break; + default: + var match = numberRe.exec(defaultValue); + if (match) + defaultValue = parseInt(defaultValue); // eslint-disable-line radix + break; + } + field.setOption("default", defaultValue); + } + + return field; +} + // --- Enum --- /** From e09ab56a65de47ed6ea465e24ad7ec41da549837 Mon Sep 17 00:00:00 2001 From: Appurva Murawat Date: Thu, 27 Jun 2024 11:44:05 +0530 Subject: [PATCH 12/33] 7.3.2-postman.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index df0946111..9938c8835 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@postman/protobufjs", - "version": "7.2.0-postman.2", + "version": "7.3.2-postman.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@postman/protobufjs", - "version": "7.2.0-postman.2", + "version": "7.3.2-postman.1", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index a95d3e111..e0fd4ce26 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@postman/protobufjs", - "version": "7.2.0-postman.2", + "version": "7.3.2-postman.1", "versionScheme": "~", "description": "Protocol Buffers for JavaScript (& TypeScript).", "license": "Apache-2.0", From 4d98dd752f0ce81062864c13588e55e6fd969ec4 Mon Sep 17 00:00:00 2001 From: Appurva Murawat Date: Thu, 27 Jun 2024 11:53:49 +0530 Subject: [PATCH 13/33] 7.3.2-postman.2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9938c8835..d6cbc366c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@postman/protobufjs", - "version": "7.3.2-postman.1", + "version": "7.3.2-postman.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@postman/protobufjs", - "version": "7.3.2-postman.1", + "version": "7.3.2-postman.2", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index e0fd4ce26..18c9f9ba4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@postman/protobufjs", - "version": "7.3.2-postman.1", + "version": "7.3.2-postman.2", "versionScheme": "~", "description": "Protocol Buffers for JavaScript (& TypeScript).", "license": "Apache-2.0", From 095763666cf99670dacb3c3b686617159014381d Mon Sep 17 00:00:00 2001 From: Appurva Murawat Date: Thu, 27 Jun 2024 11:54:27 +0530 Subject: [PATCH 14/33] chore: update package.lock --- cli/package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/package-lock.json b/cli/package-lock.json index 4ec99a106..64b147c58 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -36,7 +36,7 @@ }, "..": { "name": "@postman/protobufjs", - "version": "7.2.0-postman.2", + "version": "7.3.2-postman.2", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", From c4c1160f7b1f572e4d5998e751ced38ba26aa62c Mon Sep 17 00:00:00 2001 From: Appurva Murawat Date: Fri, 19 Jul 2024 13:02:01 +0530 Subject: [PATCH 15/33] fix: map field detection for converting file descriptor to reflection object --- ext/descriptor/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ext/descriptor/index.js b/ext/descriptor/index.js index b9a46ed27..7525eb22f 100644 --- a/ext/descriptor/index.js +++ b/ext/descriptor/index.js @@ -215,8 +215,10 @@ Type.fromDescriptor = function fromDescriptor(descriptor, syntax, options) { /* Fields */ if (descriptor.field) for (i = 0; i < descriptor.field.length; ++i) { var mapType = useMapField && descriptor.nestedType - ? descriptor.nestedType.find(function (t) { - return t.options && t.options.mapEntry && t.name === descriptor.field[i].typeName + ? descriptor.nestedType.find(function (t) { + var currField = descriptor.field[i]; + var nestedTypeName = currField.typeName && currField.typeName.split('.').pop(); + return t.options && t.options.mapEntry && t.name === nestedTypeName }) : null; var field = mapType From fab5b593287b36c48d9207939b1a828ef6aacd6d Mon Sep 17 00:00:00 2001 From: Appurva Murawat Date: Fri, 19 Jul 2024 21:58:56 +0530 Subject: [PATCH 16/33] 7.3.2-postman.3 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index d6cbc366c..ddf219ed1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@postman/protobufjs", - "version": "7.3.2-postman.2", + "version": "7.3.2-postman.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@postman/protobufjs", - "version": "7.3.2-postman.2", + "version": "7.3.2-postman.3", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 18c9f9ba4..b2e4cdaff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@postman/protobufjs", - "version": "7.3.2-postman.2", + "version": "7.3.2-postman.3", "versionScheme": "~", "description": "Protocol Buffers for JavaScript (& TypeScript).", "license": "Apache-2.0", From 5128bf3382bee491271f91cd3c83b43ec50051dd Mon Sep 17 00:00:00 2001 From: Ankur Dengla Date: Fri, 27 Jun 2025 17:31:37 +0530 Subject: [PATCH 17/33] Added wrappers for Struct and ListValue --- src/wrappers.js | 128 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/src/wrappers.js b/src/wrappers.js index 131e21c05..67d59e783 100644 --- a/src/wrappers.js +++ b/src/wrappers.js @@ -100,3 +100,131 @@ wrappers[".google.protobuf.Any"] = { return this.toObject(message, options); } }; + +// Custom wrapper for Struct +wrappers[".google.protobuf.Struct"] = { + fromObject: function(object) { + console.log('[DEBUG] Struct.fromObject called with:', object); + // If already a Struct instance, return as is + if (object instanceof this.ctor) return object; + // Convert plain JS object to Struct + var fields = {}; + if (object && typeof object === "object" && !Array.isArray(object)) { + for (var k in object) { + if (object[k] !== undefined) { + fields[k] = this.lookup("Value").fromObject(object[k]); + } + } + } + return this.create({ fields }); + }, + toObject: function(message, options) { + // Convert Struct message to plain JS object + var obj = {}; + if (message && message.fields) { + var Value = this.lookup("Value"); + for (var k in message.fields) { + obj[k] = Value.toObject(message.fields[k], options); + } + } + return obj; + } +}; + +// Custom wrapper for Value +wrappers[".google.protobuf.Value"] = { + fromObject: function(object) { + // If already a Value instance, return as is + if (object instanceof this.ctor) return object; + + // Handle different types and convert to appropriate Value field + if (object === null || object === undefined) { + return this.create({ null_value: 0 }); + } + + if (typeof object === "string") { + return this.create({ string_value: object }); + } + + if (typeof object === "number") { + return this.create({ number_value: object }); + } + + if (typeof object === "boolean") { + return this.create({ bool_value: object }); + } + + if (Array.isArray(object)) { + // Use the ListValue wrapper's fromObject to ensure correct construction + var ListValue = this.lookup("ListValue"); + return this.create({ list_value: ListValue.fromObject(object) }); + } + + if (typeof object === "object") { + // Convert object to Struct + var Struct = this.lookup("Struct"); + return this.create({ struct_value: Struct.fromObject(object) }); + } + + // Fallback to null value for unknown types + return this.create({ null_value: 0 }); + }, + toObject: function(message, options) { + // Convert Value message to plain JS object + if (message.hasOwnProperty("null_value")) { + return null; + } + if (message.hasOwnProperty("string_value")) { + return message.string_value; + } + if (message.hasOwnProperty("number_value")) { + return message.number_value; + } + if (message.hasOwnProperty("bool_value")) { + return message.bool_value; + } + if (message.hasOwnProperty("list_value")) { + var values = []; + for (var i = 0; i < message.list_value.values.length; i++) { + values.push(wrappers[".google.protobuf.Value"].toObject.call(this, message.list_value.values[i], options)); + } + return values; + } + if (message.hasOwnProperty("struct_value")) { + var Struct = this.lookup("Struct"); + return Struct.toObject(message.struct_value, options); + } + return null; + } +}; + +// Custom wrapper for ListValue +wrappers[".google.protobuf.ListValue"] = { + fromObject: function(object) { + // If already a ListValue instance, return as is + if (object instanceof this.ctor) return object; + + // Convert array to ListValue + var values = []; + if (Array.isArray(object)) { + var Value = this.lookup("Value"); + for (var i = 0; i < object.length; i++) { + values.push(Value.fromObject(object[i])); + } + } + var msg = this.create(); + msg.values = values; + return msg; + }, + toObject: function(message, options) { + // Convert ListValue message to plain JS array + var values = []; + if (message && message.values) { + var Value = this.lookup("Value"); + for (var i = 0; i < message.values.length; i++) { + values.push(Value.toObject(message.values[i], options)); + } + } + return values; + } +}; From 32b9316c547e66d2262668d0b8f0fd91e5fd42f9 Mon Sep 17 00:00:00 2001 From: Ankur Dengla Date: Wed, 2 Jul 2025 17:44:29 +0530 Subject: [PATCH 18/33] Removed logs + added fallback to wrappers --- src/wrappers.js | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/wrappers.js b/src/wrappers.js index 67d59e783..f73ee9fa9 100644 --- a/src/wrappers.js +++ b/src/wrappers.js @@ -104,7 +104,6 @@ wrappers[".google.protobuf.Any"] = { // Custom wrapper for Struct wrappers[".google.protobuf.Struct"] = { fromObject: function(object) { - console.log('[DEBUG] Struct.fromObject called with:', object); // If already a Struct instance, return as is if (object instanceof this.ctor) return object; // Convert plain JS object to Struct @@ -115,8 +114,11 @@ wrappers[".google.protobuf.Struct"] = { fields[k] = this.lookup("Value").fromObject(object[k]); } } + + return this.create({ fields }); } - return this.create({ fields }); + + return this.fromObject(object); }, toObject: function(message, options) { // Convert Struct message to plain JS object @@ -126,8 +128,10 @@ wrappers[".google.protobuf.Struct"] = { for (var k in message.fields) { obj[k] = Value.toObject(message.fields[k], options); } + return obj; } - return obj; + + return this.toObject(message, options); } }; @@ -166,8 +170,7 @@ wrappers[".google.protobuf.Value"] = { return this.create({ struct_value: Struct.fromObject(object) }); } - // Fallback to null value for unknown types - return this.create({ null_value: 0 }); + return this.fromObject(object); }, toObject: function(message, options) { // Convert Value message to plain JS object @@ -194,7 +197,8 @@ wrappers[".google.protobuf.Value"] = { var Struct = this.lookup("Struct"); return Struct.toObject(message.struct_value, options); } - return null; + + return this.toObject(message, options); } }; @@ -205,26 +209,30 @@ wrappers[".google.protobuf.ListValue"] = { if (object instanceof this.ctor) return object; // Convert array to ListValue - var values = []; if (Array.isArray(object)) { + var values = []; var Value = this.lookup("Value"); for (var i = 0; i < object.length; i++) { values.push(Value.fromObject(object[i])); } + var msg = this.create(); + msg.values = values; + return msg; } - var msg = this.create(); - msg.values = values; - return msg; + + return this.fromObject(object); }, toObject: function(message, options) { // Convert ListValue message to plain JS array - var values = []; if (message && message.values) { + var values = []; var Value = this.lookup("Value"); for (var i = 0; i < message.values.length; i++) { values.push(Value.toObject(message.values[i], options)); } + return values; } - return values; + + return this.toObject(message, options); } }; From 634457a22c03037619a982e89218214eaf1ccb2b Mon Sep 17 00:00:00 2001 From: Ankur Dengla Date: Mon, 15 Sep 2025 21:38:47 +0530 Subject: [PATCH 19/33] Added support for Duration and Timestamp as string --- src/converter.js | 2 - src/verifier.js | 23 ++++-- src/wrappers.js | 211 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 228 insertions(+), 8 deletions(-) diff --git a/src/converter.js b/src/converter.js index 086e00307..e9adc894c 100644 --- a/src/converter.js +++ b/src/converter.js @@ -41,8 +41,6 @@ function genValuePartial_fromObject(gen, field, fieldIndex, prop) { } gen ("}"); } else gen - ("if(typeof d%s!==\"object\")", prop) - ("throw TypeError(%j)", field.fullName + ": object expected") ("m%s=types[%i].fromObject(d%s)", prop, fieldIndex, prop); } else { var isUnsigned = false; diff --git a/src/verifier.js b/src/verifier.js index d58e27abe..df9479b95 100644 --- a/src/verifier.js +++ b/src/verifier.js @@ -30,12 +30,23 @@ function genVerifyValue(gen, field, fieldIndex, ref) { ("break") ("}"); } else { - gen - ("{") - ("var e=types[%i].verify(%s);", fieldIndex, ref) - ("if(e)") - ("return%j+e", field.name + ".") - ("}"); + // Special handling for Duration type to allow string input + if (field.resolvedType.fullName === ".google.protobuf.Duration" || field.resolvedType.fullName === ".google.protobuf.Timestamp") { gen + ("if(typeof %s===\"string\")", ref) + (";") // Allow string input for Duration + ("else {") + ("var e=types[%i].verify(%s);", fieldIndex, ref) + ("if(e)") + ("return%j+e", field.name + ".") + ("}") + } else { + gen + ("{") + ("var e=types[%i].verify(%s);", fieldIndex, ref) + ("if(e)") + ("return%j+e", field.name + ".") + ("}"); + } } } else { switch (field.type) { diff --git a/src/wrappers.js b/src/wrappers.js index f73ee9fa9..939ce52b1 100644 --- a/src/wrappers.js +++ b/src/wrappers.js @@ -236,3 +236,214 @@ wrappers[".google.protobuf.ListValue"] = { return this.toObject(message, options); } }; + +// Custom wrapper for Duration +wrappers[".google.protobuf.Duration"] = { + fromObject: function(object) { + // If already a Duration instance, return as is + if (object instanceof this.ctor) return object; + + // Handle string input (e.g., "1.5s", "2m", "1h", "1h30m", "2d5h30m15s") + if (typeof object === "string") { + // Parse compound duration string like "1h30m15s" or "2d5h30m15s" + // Note: Multiple segments of the same unit are allowed and will be added together + // e.g., "2s32.232s" becomes "34.232s" + var totalSeconds = 0; + var totalNanos = 0; + var sign = 1; + + // Check for negative duration + if (object.startsWith('-')) { + sign = -1; + object = object.substring(1); + } + + // Match all duration parts (e.g., "1h", "30m", "15s") + var parts = object.match(/(\d+(?:\.\d+)?)([smhd])/g); + if (!parts || parts.length === 0) { + throw new Error("Invalid duration format. Expected format: '1.5s', '2m', '1h', '1h30m', '2d5h30m15s', etc."); + } + + // Track units used for validation/warning + var unitsUsed = { s: 0, m: 0, h: 0, d: 0 }; + + for (var i = 0; i < parts.length; i++) { + var part = parts[i]; + var match = part.match(/(\d+(?:\.\d+)?)([smhd])/); + if (!match) continue; + + var value = parseFloat(match[1]); + var unit = match[2]; + + // Count usage of each unit + unitsUsed[unit]++; + + switch (unit) { + case 's': + totalSeconds += Math.floor(value); + totalNanos += Math.round((value - Math.floor(value)) * 1000000000); + break; + case 'm': + totalSeconds += value * 60; + break; + case 'h': + totalSeconds += value * 3600; + break; + case 'd': + totalSeconds += value * 86400; + break; + } + } + + // Warn about unusual formats (multiple segments of same unit) + var duplicateUnits = Object.keys(unitsUsed).filter(unit => unitsUsed[unit] > 1); + if (duplicateUnits.length > 0) { + console.warn('Warning: Duplicate units found in duration:', duplicateUnits.join(', '), + 'in input:', object, '- segments will be added together'); + } + + // Handle nanos overflow + if (totalNanos >= 1000000000) { + totalSeconds += Math.floor(totalNanos / 1000000000); + totalNanos = totalNanos % 1000000000; + } + + return this.create({ + seconds: sign * totalSeconds, + nanos: sign * totalNanos + }); + } + + // Handle number input (seconds) + if (typeof object === "number") { + var seconds = Math.floor(object); + var nanos = Math.round((object - seconds) * 1000000000); + return this.create({ seconds: seconds, nanos: nanos }); + } + + // Handle object input + if (object && typeof object === "object") { + return this.create(object); + } + + return this.fromObject(object); + }, + toObject: function(message, options) { + // Convert Duration message to string representation + if (options && options.json) { + // Handle Long objects for seconds field + var seconds = message.seconds; + if (seconds && typeof seconds === 'object' && seconds.low !== undefined) { + // Convert Long to number + seconds = seconds.low + (seconds.high * 0x100000000); + } + + var totalSeconds = seconds + (message.nanos / 1000000000); + if (totalSeconds === 0) return "0s"; + + var sign = totalSeconds < 0 ? "-" : ""; + totalSeconds = Math.abs(totalSeconds); + + // Convert to compound duration format + var parts = []; + var remaining = totalSeconds; + + // Days + if (remaining >= 86400) { + var days = Math.floor(remaining / 86400); + parts.push(days + "d"); + remaining = remaining % 86400; + } + + // Hours + if (remaining >= 3600) { + var hours = Math.floor(remaining / 3600); + parts.push(hours + "h"); + remaining = remaining % 3600; + } + + // Minutes + if (remaining >= 60) { + var minutes = Math.floor(remaining / 60); + parts.push(minutes + "m"); + remaining = remaining % 60; + } + + // Seconds (including fractional part) + if (remaining > 0 || parts.length === 0) { + var secs = remaining; + if (secs === Math.floor(secs)) { + parts.push(secs + "s"); + } else { + parts.push(secs.toFixed(3) + "s"); + } + } + + return sign + parts.join(""); + } + + return this.toObject(message, options); + } +}; + +// Custom wrapper for Timestamp +wrappers[".google.protobuf.Timestamp"] = { + fromObject: function(object) { + // If already a Timestamp instance, return as is + if (object instanceof this.ctor) { + return object; + } + + // Handle Date object + if (object instanceof Date) { + var seconds = Math.floor(object.getTime() / 1000); + var nanos = (object.getTime() % 1000) * 1000000; + return this.create({ seconds: seconds, nanos: nanos }); + } + + // Handle number input (milliseconds since epoch) + if (typeof object === "number") { + var seconds = Math.floor(object / 1000); + var nanos = (object % 1000) * 1000000; + return this.create({ seconds: seconds, nanos: nanos }); + } + + // Handle string input (ISO 8601 format) + if (typeof object === "string") { + var date = new Date(object); + if (isNaN(date.getTime())) { + throw new Error("Invalid timestamp format. Expected ISO 8601 format."); + } + var seconds = Math.floor(date.getTime() / 1000); + var nanos = (date.getTime() % 1000) * 1000000; + return this.create({ seconds: seconds, nanos: nanos }); + } + + // Handle object input (but not Timestamp instances) + if (object && typeof object === "object" && !(object instanceof this.ctor)) { + return this.create(object); + } + + // Fallback to default behavior - call the original fromObject method + // Use the original fromObject method that was stored in originalThis.fromObject + return this.ctor.prototype.fromObject.call(this, object); + }, + toObject: function(message, options) { + + // Convert Timestamp message to Date object or ISO string + if (options && options.json) { + // Handle Long objects for seconds field + var seconds = message.seconds; + if (seconds && typeof seconds === 'object' && seconds.low !== undefined) { + // Convert Long to number + seconds = seconds.low + (seconds.high * 0x100000000); + } + + var milliseconds = seconds * 1000 + Math.floor(message.nanos / 1000000); + var date = new Date(milliseconds); + return date.toISOString(); + } + + return this.ctor.prototype.toObject.call(this, message, options); + } +}; From fdd99c0cb75f8a35c1d256e4f22543d5be315658 Mon Sep 17 00:00:00 2001 From: Ankur Dengla Date: Wed, 17 Sep 2025 19:27:00 +0530 Subject: [PATCH 20/33] Only calling custom converter logic for Timestamp and Duration --- src/converter.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/converter.js b/src/converter.js index e9adc894c..390402bc8 100644 --- a/src/converter.js +++ b/src/converter.js @@ -40,8 +40,13 @@ function genValuePartial_fromObject(gen, field, fieldIndex, prop) { ("break"); } gen ("}"); - } else gen - ("m%s=types[%i].fromObject(d%s)", prop, fieldIndex, prop); + } else + if (field.resolvedType.fullName === ".google.protobuf.Duration" || field.resolvedType.fullName === ".google.protobuf.Timestamp") { gen + ("m%s=types[%i].fromObject(d%s)", prop, fieldIndex, prop); + } else gen + ("if(typeof d%s!==\"object\")", prop) + ("throw TypeError(%j)", field.fullName + ": object expected") + ("m%s=types[%i].fromObject(d%s)", prop, fieldIndex, prop); } else { var isUnsigned = false; switch (field.type) { From 2ee27f3f7bb321306372cd8b01299cc6b03f4a15 Mon Sep 17 00:00:00 2001 From: Ankur Dengla Date: Wed, 17 Sep 2025 19:30:15 +0530 Subject: [PATCH 21/33] Fixed fallback case --- src/wrappers.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wrappers.js b/src/wrappers.js index 939ce52b1..e560377bb 100644 --- a/src/wrappers.js +++ b/src/wrappers.js @@ -426,7 +426,7 @@ wrappers[".google.protobuf.Timestamp"] = { // Fallback to default behavior - call the original fromObject method // Use the original fromObject method that was stored in originalThis.fromObject - return this.ctor.prototype.fromObject.call(this, object); + return this.fromObject(object); }, toObject: function(message, options) { @@ -444,6 +444,6 @@ wrappers[".google.protobuf.Timestamp"] = { return date.toISOString(); } - return this.ctor.prototype.toObject.call(this, message, options); + return this.toObject(message, options); } }; From cb15aaf6797043da4b39c7db03ecf53d6a25af19 Mon Sep 17 00:00:00 2001 From: Ankur Dengla Date: Thu, 25 Sep 2025 16:35:48 +0530 Subject: [PATCH 22/33] Added support for ms, us and ns in Duration and also always return value in seconds --- src/wrappers.js | 86 +++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 46 deletions(-) diff --git a/src/wrappers.js b/src/wrappers.js index e560377bb..1a3d5e765 100644 --- a/src/wrappers.js +++ b/src/wrappers.js @@ -243,9 +243,9 @@ wrappers[".google.protobuf.Duration"] = { // If already a Duration instance, return as is if (object instanceof this.ctor) return object; - // Handle string input (e.g., "1.5s", "2m", "1h", "1h30m", "2d5h30m15s") + // Handle string input (e.g., "1.5s", "2m", "1h", "1h30m", "500ms", "250us", "100ns") if (typeof object === "string") { - // Parse compound duration string like "1h30m15s" or "2d5h30m15s" + // Parse compound duration string like "1h30m15s" or "1.5s500ms250us100ns" // Note: Multiple segments of the same unit are allowed and will be added together // e.g., "2s32.232s" becomes "34.232s" var totalSeconds = 0; @@ -258,18 +258,18 @@ wrappers[".google.protobuf.Duration"] = { object = object.substring(1); } - // Match all duration parts (e.g., "1h", "30m", "15s") - var parts = object.match(/(\d+(?:\.\d+)?)([smhd])/g); + // Match all duration parts (e.g., "1h", "30m", "15s", "500ms", "250us", "100ns") + var parts = object.match(/(\d+(?:\.\d+)?)(ms|us|ns|[smh])/g); if (!parts || parts.length === 0) { - throw new Error("Invalid duration format. Expected format: '1.5s', '2m', '1h', '1h30m', '2d5h30m15s', etc."); + throw new Error("Invalid duration format. Expected format: '1.5s', '2m', '1h', '1h30m', '500ms', '250us', '100ns', etc."); } // Track units used for validation/warning - var unitsUsed = { s: 0, m: 0, h: 0, d: 0 }; + var unitsUsed = { s: 0, m: 0, h: 0, ms: 0, us: 0, ns: 0 }; for (var i = 0; i < parts.length; i++) { var part = parts[i]; - var match = part.match(/(\d+(?:\.\d+)?)([smhd])/); + var match = part.match(/(\d+(?:\.\d+)?)(ms|us|ns|[smh])/); if (!match) continue; var value = parseFloat(match[1]); @@ -284,13 +284,35 @@ wrappers[".google.protobuf.Duration"] = { totalNanos += Math.round((value - Math.floor(value)) * 1000000000); break; case 'm': - totalSeconds += value * 60; + var minutesTotalSeconds = value * 60; + totalSeconds += Math.floor(minutesTotalSeconds); + totalNanos += Math.round((minutesTotalSeconds - Math.floor(minutesTotalSeconds)) * 1000000000); break; case 'h': - totalSeconds += value * 3600; + var hoursTotalSeconds = value * 3600; + totalSeconds += Math.floor(hoursTotalSeconds); + totalNanos += Math.round((hoursTotalSeconds - Math.floor(hoursTotalSeconds)) * 1000000000); break; - case 'd': - totalSeconds += value * 86400; + case 'ms': + // Convert milliseconds to seconds and nanos + var msSeconds = Math.floor(value / 1000); + var msNanos = Math.round((value % 1000) * 1000000); + totalSeconds += msSeconds; + totalNanos += msNanos; + break; + case 'us': + // Convert microseconds to seconds and nanos + var usSeconds = Math.floor(value / 1000000); + var usNanos = Math.round((value % 1000000) * 1000); + totalSeconds += usSeconds; + totalNanos += usNanos; + break; + case 'ns': + // Convert nanoseconds to seconds and nanos + var nsSeconds = Math.floor(value / 1000000000); + var nsNanos = Math.round(value % 1000000000); + totalSeconds += nsSeconds; + totalNanos += nsNanos; break; } } @@ -344,42 +366,14 @@ wrappers[".google.protobuf.Duration"] = { var sign = totalSeconds < 0 ? "-" : ""; totalSeconds = Math.abs(totalSeconds); - // Convert to compound duration format - var parts = []; - var remaining = totalSeconds; - - // Days - if (remaining >= 86400) { - var days = Math.floor(remaining / 86400); - parts.push(days + "d"); - remaining = remaining % 86400; + // Always return duration in seconds format + if (totalSeconds === Math.floor(totalSeconds)) { + // Integer seconds + return sign + totalSeconds + "s"; + } else { + // Fractional seconds - use up to 9 decimal places + return sign + totalSeconds.toFixed(9) + "s"; } - - // Hours - if (remaining >= 3600) { - var hours = Math.floor(remaining / 3600); - parts.push(hours + "h"); - remaining = remaining % 3600; - } - - // Minutes - if (remaining >= 60) { - var minutes = Math.floor(remaining / 60); - parts.push(minutes + "m"); - remaining = remaining % 60; - } - - // Seconds (including fractional part) - if (remaining > 0 || parts.length === 0) { - var secs = remaining; - if (secs === Math.floor(secs)) { - parts.push(secs + "s"); - } else { - parts.push(secs.toFixed(3) + "s"); - } - } - - return sign + parts.join(""); } return this.toObject(message, options); From 3dde10c76b22474e048e24fe0e2fbfde5671afeb Mon Sep 17 00:00:00 2001 From: Ankur Dengla Date: Thu, 25 Sep 2025 16:57:33 +0530 Subject: [PATCH 23/33] Using object check instead of options.json --- src/wrappers.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wrappers.js b/src/wrappers.js index 1a3d5e765..1f5abae83 100644 --- a/src/wrappers.js +++ b/src/wrappers.js @@ -352,7 +352,7 @@ wrappers[".google.protobuf.Duration"] = { }, toObject: function(message, options) { // Convert Duration message to string representation - if (options && options.json) { + if (message && typeof message === "object" && !Array.isArray(message)) { // Handle Long objects for seconds field var seconds = message.seconds; if (seconds && typeof seconds === 'object' && seconds.low !== undefined) { @@ -425,7 +425,7 @@ wrappers[".google.protobuf.Timestamp"] = { toObject: function(message, options) { // Convert Timestamp message to Date object or ISO string - if (options && options.json) { + if (message && typeof message === "object" && !Array.isArray(message)) { // Handle Long objects for seconds field var seconds = message.seconds; if (seconds && typeof seconds === 'object' && seconds.low !== undefined) { From 3745acd1ecc7e5c20d9ff13df82bda0a84b05f82 Mon Sep 17 00:00:00 2001 From: Ankur Dengla Date: Fri, 26 Sep 2025 01:32:26 +0530 Subject: [PATCH 24/33] Fixed duplication of regex matching --- src/wrappers.js | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/wrappers.js b/src/wrappers.js index 1f5abae83..2a4317cc6 100644 --- a/src/wrappers.js +++ b/src/wrappers.js @@ -259,21 +259,24 @@ wrappers[".google.protobuf.Duration"] = { } // Match all duration parts (e.g., "1h", "30m", "15s", "500ms", "250us", "100ns") - var parts = object.match(/(\d+(?:\.\d+)?)(ms|us|ns|[smh])/g); - if (!parts || parts.length === 0) { + var durationRegex = /(\d+(?:\.\d+)?)(ms|us|μs|ns|[smh])/g; + var matches = []; + var match; + while ((match = durationRegex.exec(object)) !== null) { + matches.push(match); + } + + if (matches.length === 0) { throw new Error("Invalid duration format. Expected format: '1.5s', '2m', '1h', '1h30m', '500ms', '250us', '100ns', etc."); } // Track units used for validation/warning var unitsUsed = { s: 0, m: 0, h: 0, ms: 0, us: 0, ns: 0 }; - for (var i = 0; i < parts.length; i++) { - var part = parts[i]; - var match = part.match(/(\d+(?:\.\d+)?)(ms|us|ns|[smh])/); - if (!match) continue; - - var value = parseFloat(match[1]); - var unit = match[2]; + // Process all matches + for (var i = 0; i < matches.length; i++) { + var value = parseFloat(matches[i][1]); + var unit = matches[i][2]; // Count usage of each unit unitsUsed[unit]++; @@ -300,6 +303,7 @@ wrappers[".google.protobuf.Duration"] = { totalSeconds += msSeconds; totalNanos += msNanos; break; + case 'μs': case 'us': // Convert microseconds to seconds and nanos var usSeconds = Math.floor(value / 1000000); @@ -371,8 +375,11 @@ wrappers[".google.protobuf.Duration"] = { // Integer seconds return sign + totalSeconds + "s"; } else { - // Fractional seconds - use up to 9 decimal places - return sign + totalSeconds.toFixed(9) + "s"; + // Fractional seconds - use up to 9 decimal places, then clip trailing zeros + var formatted = totalSeconds.toFixed(9); + // Remove trailing zeros and decimal point if all zeros + formatted = formatted.replace(/\.?0+$/, ''); + return sign + formatted + "s"; } } From 70c1d00d5c3669a48d12cfe56236cc88eec0b1af Mon Sep 17 00:00:00 2001 From: Ankur Dengla Date: Fri, 26 Sep 2025 05:43:17 +0530 Subject: [PATCH 25/33] Added isLegacyStruct util to handle old struct syntax --- src/util/is-legacy-struct.js | 46 ++++++++++++++++++++++++++++++++++++ src/wrappers.js | 8 +++++++ 2 files changed, 54 insertions(+) create mode 100644 src/util/is-legacy-struct.js diff --git a/src/util/is-legacy-struct.js b/src/util/is-legacy-struct.js new file mode 100644 index 000000000..8ab3efca6 --- /dev/null +++ b/src/util/is-legacy-struct.js @@ -0,0 +1,46 @@ +"use strict"; +module.exports = isLegacyStruct; + +/** + * Identifies where the payload for a struct is in the form of a legacy struct. + * The legacy format is - + * + * { + * fields: { + * "key1": { + * "string_value": "test", + * }, + * "key2": { + * "number_value": 123, + * } + * } + * } + * + * @param {object} payload + * @returns {boolean} + */ +function isLegacyStruct(payload) { + // Value types in a struct + const valueKeysSet = new Set(['string_value', 'number_value', 'bool_value', 'struct_value', 'list_value', 'null_value']); + + // If object has only one key and that key is "fields" which is an object + if (Object.keys(payload).length === 1 && typeof payload.fields === "object" + && typeof payload.fields === "object") { + + // Get all the values of the fields object + // For the given example - + // fieldValues = [{string_value: "test"}, {number_value: 123}] + const fieldValues = Object.values(payload.fields); + + // Check if all the fieldValues have only one key and that key is a valid value type + if (fieldValues.every(fieldValue => { + const fieldValueKeys = Object.keys(fieldValue); + return fieldValueKeys.length === 1 && valueKeysSet.has(fieldValueKeys[0]); + })) { + + return true; + } + } + + return false; +} \ No newline at end of file diff --git a/src/wrappers.js b/src/wrappers.js index 2a4317cc6..2d7f095cf 100644 --- a/src/wrappers.js +++ b/src/wrappers.js @@ -8,6 +8,7 @@ var wrappers = exports; var Message = require("./message"); +var isLegacyStruct = require("./util/is-legacy-struct"); /** * From object converter part of an {@link IWrapper}. @@ -109,6 +110,13 @@ wrappers[".google.protobuf.Struct"] = { // Convert plain JS object to Struct var fields = {}; if (object && typeof object === "object" && !Array.isArray(object)) { + + // If the struct is in the form of a legacy struct, we don't need to convert it. + // Return the fields object as is + if (isLegacyStruct(object)) { + return this.create({ fields: object.fields }); + } + for (var k in object) { if (object[k] !== undefined) { fields[k] = this.lookup("Value").fromObject(object[k]); From 439b27b1931eb2c52f13f507f647542ae83b671c Mon Sep 17 00:00:00 2001 From: Ankur Dengla Date: Mon, 29 Sep 2025 15:03:57 +0530 Subject: [PATCH 26/33] Fixed case for negative duration --- src/wrappers.js | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/wrappers.js b/src/wrappers.js index 2d7f095cf..bc32aa153 100644 --- a/src/wrappers.js +++ b/src/wrappers.js @@ -342,8 +342,10 @@ wrappers[".google.protobuf.Duration"] = { totalNanos = totalNanos % 1000000000; } + // Use util.Long.fromValue to properly create Long objects for int64 fields + var util = require("./util"); return this.create({ - seconds: sign * totalSeconds, + seconds: util.Long.fromValue(sign * totalSeconds), nanos: sign * totalNanos }); } @@ -368,11 +370,28 @@ wrappers[".google.protobuf.Duration"] = { // Handle Long objects for seconds field var seconds = message.seconds; if (seconds && typeof seconds === 'object' && seconds.low !== undefined) { - // Convert Long to number - seconds = seconds.low + (seconds.high * 0x100000000); + // Convert Long to number using utility + seconds = seconds.toNumber(); + } + + // Handle durations correctly for all sign combinations + var totalSeconds; + var nanosSeconds = message.nanos / 1000000000; + + if (seconds < 0 && message.nanos < 0) { + // Both negative: add them together (both contribute to negative duration) + totalSeconds = seconds + nanosSeconds; + } else if (seconds < 0 && message.nanos >= 0) { + // Negative seconds, positive nanos: subtract nanos from negative seconds + totalSeconds = seconds - nanosSeconds; + } else if (seconds >= 0 && message.nanos < 0) { + // Positive seconds, negative nanos: subtract nanos from positive seconds + totalSeconds = seconds + nanosSeconds; // nanosSeconds is already negative + } else { + // Both positive: add them together + totalSeconds = seconds + nanosSeconds; } - var totalSeconds = seconds + (message.nanos / 1000000000); if (totalSeconds === 0) return "0s"; var sign = totalSeconds < 0 ? "-" : ""; From 7cfa813061236dc463425e39e9f050b9e3c36ecf Mon Sep 17 00:00:00 2001 From: Ankur Dengla Date: Mon, 29 Sep 2025 15:58:46 +0530 Subject: [PATCH 27/33] Update isLegacyStruct to handle fields array --- src/util/is-legacy-struct.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/util/is-legacy-struct.js b/src/util/is-legacy-struct.js index 8ab3efca6..a7d132e16 100644 --- a/src/util/is-legacy-struct.js +++ b/src/util/is-legacy-struct.js @@ -24,8 +24,19 @@ function isLegacyStruct(payload) { const valueKeysSet = new Set(['string_value', 'number_value', 'bool_value', 'struct_value', 'list_value', 'null_value']); // If object has only one key and that key is "fields" which is an object - if (Object.keys(payload).length === 1 && typeof payload.fields === "object" - && typeof payload.fields === "object") { + if (payload && Object.keys(payload).length === 1 && payload.fields && typeof payload.fields === "object") { + // Case when the fields key is an array. This can be of the form - + // { + // fields: [ + // {key: "key1", value: {string_value: "test"}}, + // {key: "key2", value: {number_value: 123}} + // ] + // } + if (Array.isArray(payload.fields)) { + return payload.fields.every(field => Object.keys(field).length === 2 && + field.key && field.value && Object.keys(value).length === 1 + && valueKeysSet.has(Object.keys(value)[0])); + } // Get all the values of the fields object // For the given example - @@ -37,10 +48,9 @@ function isLegacyStruct(payload) { const fieldValueKeys = Object.keys(fieldValue); return fieldValueKeys.length === 1 && valueKeysSet.has(fieldValueKeys[0]); })) { - return true; } } return false; -} \ No newline at end of file +} From 117a0b393c5f9a073bf0db1066622b8a739ccfa0 Mon Sep 17 00:00:00 2001 From: Ankur Dengla Date: Mon, 29 Sep 2025 16:02:55 +0530 Subject: [PATCH 28/33] Fixed error message --- src/wrappers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wrappers.js b/src/wrappers.js index bc32aa153..ba3b073cc 100644 --- a/src/wrappers.js +++ b/src/wrappers.js @@ -275,7 +275,7 @@ wrappers[".google.protobuf.Duration"] = { } if (matches.length === 0) { - throw new Error("Invalid duration format. Expected format: '1.5s', '2m', '1h', '1h30m', '500ms', '250us', '100ns', etc."); + throw new Error("Invalid duration format. Expected format in seconds: '1.5s', '20s', '-1s'"); } // Track units used for validation/warning From 4f2d74ee16fc9f1b06be2fa8dadabeaf87a1dcd3 Mon Sep 17 00:00:00 2001 From: Ankur Dengla Date: Mon, 29 Sep 2025 17:31:18 +0530 Subject: [PATCH 29/33] Adressed comments --- src/util/is-legacy-struct.js | 4 ++-- src/wrappers.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/util/is-legacy-struct.js b/src/util/is-legacy-struct.js index a7d132e16..820ee2b23 100644 --- a/src/util/is-legacy-struct.js +++ b/src/util/is-legacy-struct.js @@ -34,8 +34,8 @@ function isLegacyStruct(payload) { // } if (Array.isArray(payload.fields)) { return payload.fields.every(field => Object.keys(field).length === 2 && - field.key && field.value && Object.keys(value).length === 1 - && valueKeysSet.has(Object.keys(value)[0])); + field.key && field.value && Object.keys(field.value).length === 1 + && valueKeysSet.has(Object.keys(field.value)[0])); } // Get all the values of the fields object diff --git a/src/wrappers.js b/src/wrappers.js index ba3b073cc..99d493282 100644 --- a/src/wrappers.js +++ b/src/wrappers.js @@ -275,7 +275,7 @@ wrappers[".google.protobuf.Duration"] = { } if (matches.length === 0) { - throw new Error("Invalid duration format. Expected format in seconds: '1.5s', '20s', '-1s'"); + throw new Error("Invalid duration format. Expected units: h, m, s, ms, us or ns (e.g. 1h30m, 2m, 5s, -1s)"); } // Track units used for validation/warning From 8f0bfa2f9645b6834c41edade38f2ec1061ad7bf Mon Sep 17 00:00:00 2001 From: Ankur Dengla Date: Mon, 29 Sep 2025 18:03:12 +0530 Subject: [PATCH 30/33] Fixed long conversion in Timestamp toObject wrapper --- src/wrappers.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wrappers.js b/src/wrappers.js index 99d493282..0c9177300 100644 --- a/src/wrappers.js +++ b/src/wrappers.js @@ -463,8 +463,8 @@ wrappers[".google.protobuf.Timestamp"] = { // Handle Long objects for seconds field var seconds = message.seconds; if (seconds && typeof seconds === 'object' && seconds.low !== undefined) { - // Convert Long to number - seconds = seconds.low + (seconds.high * 0x100000000); + // Convert Long to number using utility + seconds = seconds.toNumber(); } var milliseconds = seconds * 1000 + Math.floor(message.nanos / 1000000); From c794702f0e7d972293bd3b975e262376a9a40cf9 Mon Sep 17 00:00:00 2001 From: Ankur Dengla Date: Mon, 29 Sep 2025 18:05:30 +0530 Subject: [PATCH 31/33] Fixed long conversion in Timestamp fromObject wrapper --- src/wrappers.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/wrappers.js b/src/wrappers.js index 0c9177300..f1123d711 100644 --- a/src/wrappers.js +++ b/src/wrappers.js @@ -9,6 +9,7 @@ var wrappers = exports; var Message = require("./message"); var isLegacyStruct = require("./util/is-legacy-struct"); +var util = require("./util"); /** * From object converter part of an {@link IWrapper}. @@ -343,7 +344,6 @@ wrappers[".google.protobuf.Duration"] = { } // Use util.Long.fromValue to properly create Long objects for int64 fields - var util = require("./util"); return this.create({ seconds: util.Long.fromValue(sign * totalSeconds), nanos: sign * totalNanos @@ -426,14 +426,16 @@ wrappers[".google.protobuf.Timestamp"] = { if (object instanceof Date) { var seconds = Math.floor(object.getTime() / 1000); var nanos = (object.getTime() % 1000) * 1000000; - return this.create({ seconds: seconds, nanos: nanos }); + // Use util.Long.fromValue to properly create Long objects for int64 fields + return this.create({ seconds: util.Long.fromValue(seconds), nanos: nanos }); } // Handle number input (milliseconds since epoch) if (typeof object === "number") { var seconds = Math.floor(object / 1000); var nanos = (object % 1000) * 1000000; - return this.create({ seconds: seconds, nanos: nanos }); + // Use util.Long.fromValue to properly create Long objects for int64 fields + return this.create({ seconds: util.Long.fromValue(seconds), nanos: nanos }); } // Handle string input (ISO 8601 format) @@ -444,7 +446,8 @@ wrappers[".google.protobuf.Timestamp"] = { } var seconds = Math.floor(date.getTime() / 1000); var nanos = (date.getTime() % 1000) * 1000000; - return this.create({ seconds: seconds, nanos: nanos }); + // Use util.Long.fromValue to properly create Long objects for int64 fields + return this.create({ seconds: util.Long.fromValue(seconds), nanos: nanos }); } // Handle object input (but not Timestamp instances) From 14c2434b0e7e61dd43117beb6e4b378eaf42e7fe Mon Sep 17 00:00:00 2001 From: Ankur Dengla Date: Tue, 30 Sep 2025 03:38:00 +0530 Subject: [PATCH 32/33] Fixed fields array handling --- src/wrappers.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/wrappers.js b/src/wrappers.js index f1123d711..f409da4c9 100644 --- a/src/wrappers.js +++ b/src/wrappers.js @@ -115,7 +115,12 @@ wrappers[".google.protobuf.Struct"] = { // If the struct is in the form of a legacy struct, we don't need to convert it. // Return the fields object as is if (isLegacyStruct(object)) { - return this.create({ fields: object.fields }); + var newFields = Array.isArray(object.fields) ? object.fields.reduce(function(acc, field) { + acc[field.key] = field.value; + return acc; + }, {}) : object.fields; + + return this.create({ fields: newFields }); } for (var k in object) { From 0b393fc3cc6ccf82267e2bbdeed9c6dc76bde79d Mon Sep 17 00:00:00 2001 From: Ankur Dengla Date: Tue, 30 Sep 2025 04:01:49 +0530 Subject: [PATCH 33/33] Added safecheck --- src/util/is-legacy-struct.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/is-legacy-struct.js b/src/util/is-legacy-struct.js index 820ee2b23..da5dfc7fb 100644 --- a/src/util/is-legacy-struct.js +++ b/src/util/is-legacy-struct.js @@ -45,7 +45,7 @@ function isLegacyStruct(payload) { // Check if all the fieldValues have only one key and that key is a valid value type if (fieldValues.every(fieldValue => { - const fieldValueKeys = Object.keys(fieldValue); + const fieldValueKeys = fieldValue ? Object.keys(fieldValue) : []; return fieldValueKeys.length === 1 && valueKeysSet.has(fieldValueKeys[0]); })) { return true;