@aptre/protobuf-es-lite 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/binary.d.ts CHANGED
@@ -50,7 +50,7 @@ declare function readMapEntry(field: FieldInfo & {
50
50
  }, reader: IBinaryReader, options: BinaryReadOptions): [string | number, ScalarValue | AnyMessage | undefined];
51
51
  declare function readScalar(reader: IBinaryReader, type: ScalarType): ScalarValue;
52
52
  declare function readScalarLTString(reader: IBinaryReader, type: ScalarType): Exclude<ScalarValue, bigint>;
53
- declare function readMessage<T>(message: T, fields: FieldList, reader: IBinaryReader, lengthOrEndTagFieldNo: number, options: BinaryReadOptions, delimitedMessageEncoding?: boolean): void;
53
+ declare function readMessage<T>(message: T, fields: FieldList, reader: IBinaryReader, lengthOrEndTagFieldNo: number, options: BinaryReadOptions, delimitedMessageEncoding: boolean): void;
54
54
  /**
55
55
  * Serialize a message to binary data.
56
56
  */
package/dist/binary.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { isFieldSet, resolveMessageType, } from "./field.js";
2
2
  import { handleUnknownField, unknownFieldsSymbol } from "./unknown.js";
3
- import { wrapField } from "./field-wrapper.js";
3
+ import { unwrapField, wrapField } from "./field-wrapper.js";
4
4
  import { LongType, ScalarType, scalarZeroValue, } from "./scalar.js";
5
5
  import { assert } from "./assert.js";
6
6
  import { BinaryReader, BinaryWriter, WireType, } from "./binary-encoding.js";
@@ -26,7 +26,7 @@ reader, field, wireType, options) {
26
26
  if (field.oneof) {
27
27
  var oneofMsg = target[field.oneof.localName];
28
28
  if (!oneofMsg) {
29
- oneofMsg = target[field.oneof.localName] = {};
29
+ oneofMsg = target[field.oneof.localName] = Object.create(null);
30
30
  }
31
31
  target = oneofMsg;
32
32
  if (target.case != localName) {
@@ -74,19 +74,16 @@ reader, field, wireType, options) {
74
74
  if (!Array.isArray(tgtArr)) {
75
75
  tgtArr = target[localName] = [];
76
76
  }
77
- tgtArr.push(readMessageField(reader, {}, messageType.fields, options, field));
77
+ tgtArr.push(unwrapField(messageType.fieldWrapper, readMessageField(reader, Object.create(null), messageType.fields, options, field)));
78
78
  }
79
79
  else {
80
- target[localName] = readMessageField(reader, {}, messageType.fields, options, field);
81
- if (messageType.fieldWrapper && !field.oneof && !field.repeated) {
82
- target[localName] = messageType.fieldWrapper.unwrapField(target[localName]);
83
- }
80
+ target[localName] = unwrapField(messageType.fieldWrapper, readMessageField(reader, Object.create(null), messageType.fields, options, field));
84
81
  }
85
82
  break;
86
83
  case "map":
87
84
  let [mapKey, mapVal] = readMapEntry(field, reader, options);
88
85
  if (typeof target[localName] !== "object") {
89
- target[localName] = {};
86
+ target[localName] = Object.create(null);
90
87
  }
91
88
  // safe to assume presence of map object, oneof cannot contain repeated values
92
89
  target[localName][mapKey] = mapVal;
@@ -113,7 +110,7 @@ function readMapEntry(field, reader, options) {
113
110
  break;
114
111
  case "message":
115
112
  const messageType = resolveMessageType(field.V.T);
116
- val = readMessageField(reader, {}, messageType.fields, options, undefined);
113
+ val = readMessageField(reader, Object.create(null), messageType.fields, options, undefined);
117
114
  break;
118
115
  }
119
116
  break;
@@ -135,7 +132,7 @@ function readMapEntry(field, reader, options) {
135
132
  val = field.V.T.values[0].no;
136
133
  break;
137
134
  case "message":
138
- val = {};
135
+ val = Object.create(null);
139
136
  break;
140
137
  }
141
138
  }
@@ -184,13 +181,10 @@ function readScalarLTString(reader, type) {
184
181
  // Read a message, avoiding MessageType.fromBinary() to re-use the
185
182
  // BinaryReadOptions and the IBinaryReader.
186
183
  function readMessageField(reader, message, fields, options, field) {
187
- const delimited = field?.delimited;
188
- readMessage(message, fields, reader, delimited ? field.no : reader.uint32(), // eslint-disable-line @typescript-eslint/strict-boolean-expressions
189
- options, delimited);
184
+ readMessage(message, fields, reader, field?.delimited ? field.no : reader.uint32(), options, field?.delimited ?? false);
190
185
  return message;
191
186
  }
192
187
  function readMessage(message, fields, reader, lengthOrEndTagFieldNo, options, delimitedMessageEncoding) {
193
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
194
188
  const end = delimitedMessageEncoding ? reader.len : reader.pos + lengthOrEndTagFieldNo;
195
189
  let fieldNo, wireType;
196
190
  while (reader.pos < end) {
@@ -227,7 +221,9 @@ function writeMessage(message, fields, writer, options) {
227
221
  const value = field.oneof ?
228
222
  message[field.oneof.localName].value
229
223
  : message[field.localName];
230
- writeField(field, value, writer, options);
224
+ if (value !== undefined) {
225
+ writeField(field, value, writer, options);
226
+ }
231
227
  }
232
228
  if (options.writeUnknownFields) {
233
229
  writeUnknownFields(message, writer);
@@ -5,7 +5,15 @@ import { ScalarType } from "./scalar.js";
5
5
  * ergonomic for use as a message field.
6
6
  */
7
7
  export interface FieldWrapper<T = any, U = any> {
8
+ /**
9
+ * Wrap a primitive message field value in its corresponding wrapper
10
+ * message. This function is idempotent.
11
+ */
8
12
  wrapField(value: U | null | undefined): T;
13
+ /**
14
+ * If the given field uses one of the well-known wrapper types, return
15
+ * the primitive type it wraps.
16
+ */
9
17
  unwrapField(value: T): U | null | undefined;
10
18
  }
11
19
  /**
@@ -13,6 +21,11 @@ export interface FieldWrapper<T = any, U = any> {
13
21
  * message. This function is idempotent.
14
22
  */
15
23
  export declare function wrapField<T>(fieldWrapper: FieldWrapper<T> | undefined, value: any): T;
24
+ /**
25
+ * Wrap a primitive message field value in its corresponding wrapper
26
+ * message. This function is idempotent.
27
+ */
28
+ export declare function unwrapField<T, U = T>(fieldWrapper: FieldWrapper<T> | undefined, value: any): U;
16
29
  /**
17
30
  * If the given field uses one of the well-known wrapper types, return
18
31
  * the primitive type it wraps.
@@ -9,6 +9,13 @@ export function wrapField(fieldWrapper, value) {
9
9
  }
10
10
  return fieldWrapper.wrapField(value);
11
11
  }
12
+ /**
13
+ * Wrap a primitive message field value in its corresponding wrapper
14
+ * message. This function is idempotent.
15
+ */
16
+ export function unwrapField(fieldWrapper, value) {
17
+ return !!fieldWrapper ? fieldWrapper.unwrapField(value) : value;
18
+ }
12
19
  /**
13
20
  * If the given field uses one of the well-known wrapper types, return
14
21
  * the primitive type it wraps.
package/dist/field.d.ts CHANGED
@@ -113,7 +113,7 @@ export declare function newFieldList(fields: FieldListSource, packedByDefault: b
113
113
  /**
114
114
  * Returns true if the field is set.
115
115
  */
116
- export declare function isFieldSet(field: FieldInfo, target: Record<string, any>): boolean;
116
+ export declare function isFieldSet(field: FieldInfo, target: Record<string, any> | null | undefined): boolean;
117
117
  /**
118
118
  * Returns the JSON name for a protobuf field, exactly like protoc does.
119
119
  */
package/dist/field.js CHANGED
@@ -95,28 +95,32 @@ export function newFieldList(fields, packedByDefault) {
95
95
  */
96
96
  export function isFieldSet(field, target) {
97
97
  const localName = field.localName;
98
+ if (!target) {
99
+ return false;
100
+ }
98
101
  if (field.repeated) {
99
- return target[localName].length > 0;
102
+ return !!target[localName]?.length;
100
103
  }
101
104
  if (field.oneof) {
102
- return target[field.oneof.localName].case === localName; // eslint-disable-line @typescript-eslint/no-unsafe-member-access
105
+ return target[field.oneof.localName]?.case === localName; // eslint-disable-line @typescript-eslint/no-unsafe-member-access
103
106
  }
104
107
  switch (field.kind) {
105
108
  case "enum":
106
109
  case "scalar":
107
110
  if (field.opt || field.req) {
108
111
  // explicit presence
109
- return target[localName] !== undefined;
112
+ return target[localName] != null;
110
113
  }
111
114
  // implicit presence
112
115
  if (field.kind == "enum") {
113
116
  return target[localName] !== field.T.values[0].no;
114
117
  }
118
+ // not zero value
115
119
  return !isScalarZeroValue(field.T, target[localName]);
116
120
  case "message":
117
- return target[localName] !== undefined;
121
+ return target[localName] != null;
118
122
  case "map":
119
- return Object.keys(target[localName]).length > 0; // eslint-disable-line @typescript-eslint/no-unsafe-argument
123
+ return (target[localName] != null && !!Object.keys(target[localName]).length); // eslint-disable-line @typescript-eslint/no-unsafe-argument
120
124
  }
121
125
  }
122
126
  /**
@@ -118,7 +118,8 @@ export type Timestamp = Message<{
118
118
  declare const Timestamp_Wkt: {
119
119
  fromJson(json: JsonValue): Timestamp;
120
120
  toJson(msg: Timestamp): JsonValue;
121
- toDate(msg: Timestamp): Date;
121
+ toDate(msg: Timestamp | null | undefined): Date | null;
122
+ fromDate(value: Date | null | undefined): Timestamp;
122
123
  };
123
124
  export declare const Timestamp: MessageType<Timestamp> & typeof Timestamp_Wkt;
124
125
  export {};
@@ -89,8 +89,20 @@ const Timestamp_Wkt = {
89
89
  return new Date(ms).toISOString().replace(".000Z", z);
90
90
  },
91
91
  toDate(msg) {
92
+ if (!msg?.seconds && !msg?.nanos) {
93
+ return null;
94
+ }
92
95
  return new Date(Number(msg.seconds ?? 0) * 1000 + Math.ceil((msg.nanos ?? 0) / 1000000));
93
96
  },
97
+ fromDate(value) {
98
+ if (value == null) {
99
+ return {};
100
+ }
101
+ const ms = value.getTime();
102
+ const seconds = Math.floor(ms / 1000);
103
+ const nanos = (ms % 1000) * 1000000;
104
+ return { seconds: protoInt64.parse(seconds), nanos: nanos };
105
+ },
94
106
  };
95
107
  // Timestamp contains the message type declaration for Timestamp.
96
108
  export const Timestamp = createMessageType({
package/dist/json.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import { isFieldSet, resolveMessageType, } from "./field.js";
2
2
  import { assert, assertFloat32, assertInt32, assertUInt32 } from "./assert.js";
3
- import { LongType, ScalarType, scalarZeroValue, } from "./scalar.js";
4
- import { wrapField } from "./field-wrapper.js";
3
+ import { LongType, ScalarType, isScalarZeroValue, normalizeScalarValue, scalarZeroValue, } from "./scalar.js";
4
+ import { unwrapField, wrapField } from "./field-wrapper.js";
5
+ import { enumZeroValue, normalizeEnumValue } from "./enum.js";
5
6
  import { protoInt64 } from "./proto-int64.js";
6
7
  import { protoBase64 } from "./proto-base64.js";
8
+ import { throwSanitizeKey } from "./names.js";
7
9
  // Default options for parsing JSON.
8
10
  const jsonReadDefaults = {
9
11
  ignoreUnknownFields: false,
@@ -65,7 +67,7 @@ function readMessage(fields, typeName, json, options, message) {
65
67
  return message;
66
68
  }
67
69
  function writeMessage(message, fields, options) {
68
- const json = {};
70
+ const json = Object.create(null);
69
71
  let field;
70
72
  try {
71
73
  for (field of fields.byNumber()) {
@@ -123,7 +125,7 @@ function readField(target, jsonValue, field, options) {
123
125
  switch (field.kind) {
124
126
  case "message":
125
127
  const messageType = resolveMessageType(field.T);
126
- targetArray.push(messageType.fromJson(jsonItem, options));
128
+ targetArray.push(unwrapField(messageType.fieldWrapper, messageType.fromJson(jsonItem, options)));
127
129
  break;
128
130
  case "enum":
129
131
  const enumValue = readEnum(field.T, jsonItem, options.ignoreUnknownFields, true);
@@ -155,7 +157,7 @@ function readField(target, jsonValue, field, options) {
155
157
  }
156
158
  var targetMap = target[localName];
157
159
  if (typeof targetMap !== "object") {
158
- targetMap = target[localName] = {};
160
+ targetMap = target[localName] = Object.create(null);
159
161
  }
160
162
  for (const [jsonMapKey, jsonMapValue] of Object.entries(jsonValue)) {
161
163
  if (jsonMapValue === null) {
@@ -172,6 +174,7 @@ function readField(target, jsonValue, field, options) {
172
174
  }
173
175
  throw new Error(m);
174
176
  }
177
+ throwSanitizeKey(key);
175
178
  switch (field.V.kind) {
176
179
  case "message":
177
180
  const messageType = resolveMessageType(field.V.T);
@@ -210,11 +213,7 @@ function readField(target, jsonValue, field, options) {
210
213
  messageType.typeName != "google.protobuf.Value") {
211
214
  return;
212
215
  }
213
- let currentValue = target[localName];
214
- target[localName] = currentValue = messageType.fromJson(jsonValue, options);
215
- if (messageType.fieldWrapper && !field.oneof) {
216
- target[localName] = messageType.fieldWrapper.unwrapField(currentValue);
217
- }
216
+ target[localName] = unwrapField(messageType.fieldWrapper, messageType.fromJson(jsonValue, options));
218
217
  break;
219
218
  case "enum":
220
219
  const enumValue = readEnum(field.T, jsonValue, options.ignoreUnknownFields, false);
@@ -418,7 +417,7 @@ export function clearField(field, target) {
418
417
  else {
419
418
  switch (field.kind) {
420
419
  case "map":
421
- target[localName] = {};
420
+ target[localName] = Object.create(null);
422
421
  break;
423
422
  case "enum":
424
423
  target[localName] = implicitPresence ? field.T.values[0].no : undefined;
@@ -436,7 +435,6 @@ export function clearField(field, target) {
436
435
  // Decide whether an unset field should be emitted with JSON write option `emitDefaultValues`
437
436
  function canEmitFieldDefaultValue(field) {
438
437
  if (field.repeated || field.kind == "map") {
439
- // maps are {}, repeated fields are []
440
438
  return true;
441
439
  }
442
440
  if (field.oneof) {
@@ -456,9 +454,9 @@ function canEmitFieldDefaultValue(field) {
456
454
  }
457
455
  function writeField(field, value, options) {
458
456
  if (field.kind == "map") {
459
- assert(typeof value == "object" && value != null);
460
- const jsonObj = {};
461
- const entries = Object.entries(value);
457
+ const jsonObj = Object.create(null);
458
+ assert(!value || typeof value === "object");
459
+ const entries = value ? Object.entries(value) : [];
462
460
  switch (field.V.kind) {
463
461
  case "scalar":
464
462
  for (const [entryKey, entryValue] of entries) {
@@ -485,24 +483,28 @@ function writeField(field, value, options) {
485
483
  : undefined;
486
484
  }
487
485
  if (field.repeated) {
488
- assert(Array.isArray(value));
486
+ assert(!value || Array.isArray(value));
489
487
  const jsonArr = [];
490
- switch (field.kind) {
491
- case "scalar":
492
- for (let i = 0; i < value.length; i++) {
493
- jsonArr.push(writeScalar(field.T, value[i]));
494
- }
495
- break;
496
- case "enum":
497
- for (let i = 0; i < value.length; i++) {
498
- jsonArr.push(writeEnum(field.T, value[i], options.enumAsInteger));
499
- }
500
- break;
501
- case "message":
502
- for (let i = 0; i < value.length; i++) {
503
- jsonArr.push(value[i].toJson(options));
504
- }
505
- break;
488
+ const valueArr = value;
489
+ if (valueArr && valueArr.length) {
490
+ switch (field.kind) {
491
+ case "scalar":
492
+ for (let i = 0; i < valueArr.length; i++) {
493
+ jsonArr.push(writeScalar(field.T, valueArr[i]));
494
+ }
495
+ break;
496
+ case "enum":
497
+ for (let i = 0; i < valueArr.length; i++) {
498
+ jsonArr.push(writeEnum(field.T, valueArr[i], options.enumAsInteger));
499
+ }
500
+ break;
501
+ case "message":
502
+ const messageType = resolveMessageType(field.T);
503
+ for (let i = 0; i < valueArr.length; i++) {
504
+ jsonArr.push(messageType.toJson(wrapField(messageType.fieldWrapper, valueArr[i])));
505
+ }
506
+ break;
507
+ }
506
508
  }
507
509
  return options.emitDefaultValues || jsonArr.length > 0 ?
508
510
  jsonArr
@@ -510,10 +512,22 @@ function writeField(field, value, options) {
510
512
  }
511
513
  switch (field.kind) {
512
514
  case "scalar":
515
+ const scalarValue = normalizeScalarValue(field.T, value, false);
516
+ if (!options.emitDefaultValues
517
+ && isScalarZeroValue(field.T, scalarValue)) {
518
+ return undefined;
519
+ }
513
520
  return writeScalar(field.T, value);
514
521
  case "enum":
522
+ const enumValue = normalizeEnumValue(field.T, value);
523
+ if (!options.emitDefaultValues && enumZeroValue(field.T) === enumValue) {
524
+ return undefined;
525
+ }
515
526
  return writeEnum(field.T, value, options.enumAsInteger);
516
527
  case "message":
528
+ if (!options.emitDefaultValues && value == null) {
529
+ return undefined;
530
+ }
517
531
  const messageType = resolveMessageType(field.T);
518
532
  return messageType.toJson(wrapField(messageType.fieldWrapper, value));
519
533
  }
package/dist/message.d.ts CHANGED
@@ -111,7 +111,7 @@ export type MessageTypeParams<T extends Message<T>> = Pick<MessageType<T>, "fiel
111
111
  */
112
112
  export declare function createMessageType<T extends Message<T>, E extends Record<string, Function> = {}>(params: MessageTypeParams<T>, exts?: E): MessageType<T> & E;
113
113
  export declare function compareMessages<T extends Message<T>>(fields: FieldList, a: T | undefined | null, b: T | undefined | null): boolean;
114
- export declare function cloneMessage<T extends Message<T>>(message: T, fields: FieldList): T;
114
+ export declare function cloneMessage<T extends Message<T>>(message: T | null | undefined, fields: FieldList): T | null;
115
115
  /**
116
116
  * createCompleteMessage recursively builds a message filled with zero values based on the given FieldList.
117
117
  */
package/dist/message.js CHANGED
@@ -13,9 +13,11 @@
13
13
  // limitations under the License.
14
14
  import { newFieldList, resolveMessageType, } from "./field.js";
15
15
  import { applyPartialMessage } from "./partial.js";
16
- import { ScalarType, scalarEquals } from "./scalar.js";
16
+ import { LongType, ScalarType, scalarEquals, scalarZeroValue, } from "./scalar.js";
17
17
  import { binaryReadMessage, binaryWriteMessage, binaryMakeReadOptions, binaryMakeWriteOptions, } from "./binary.js";
18
18
  import { jsonReadMessage, jsonWriteMessage, jsonMakeReadOptions, jsonMakeWriteOptions, } from "./json.js";
19
+ import { throwSanitizeKey } from "./names.js";
20
+ import { enumZeroValue } from "./enum.js";
19
21
  /**
20
22
  * createMessageType creates a new message type.
21
23
  *
@@ -52,7 +54,7 @@ export function createMessageType(params, exts) {
52
54
  const message = {};
53
55
  if (bytes && bytes.length) {
54
56
  const opt = binaryMakeReadOptions(options);
55
- binaryReadMessage(message, fields, opt.readerFactory(bytes), bytes.byteLength, opt, delimitedMessageEncoding);
57
+ binaryReadMessage(message, fields, opt.readerFactory(bytes), bytes.byteLength, opt, delimitedMessageEncoding ?? false);
56
58
  }
57
59
  return message;
58
60
  },
@@ -109,9 +111,12 @@ export function compareMessages(fields, a, b) {
109
111
  const va = a[m.localName];
110
112
  const vb = b[m.localName];
111
113
  if (m.repeated) {
112
- if (va.length !== vb.length) {
114
+ if ((va?.length ?? 0) !== (vb?.length ?? 0)) {
113
115
  return false;
114
116
  }
117
+ if (!va?.length) {
118
+ return true;
119
+ }
115
120
  // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- repeated fields are never "map"
116
121
  switch (m.kind) {
117
122
  case "message":
@@ -132,9 +137,12 @@ export function compareMessages(fields, a, b) {
132
137
  case "scalar":
133
138
  return scalarEquals(m.T, va, vb);
134
139
  case "oneof":
135
- if (va.case !== vb.case) {
140
+ if (va?.case !== vb?.case) {
136
141
  return false;
137
142
  }
143
+ if (va == null) {
144
+ return true;
145
+ }
138
146
  const s = m.findField(va.case);
139
147
  if (s === undefined) {
140
148
  return true;
@@ -165,61 +173,12 @@ export function compareMessages(fields, a, b) {
165
173
  }
166
174
  });
167
175
  }
168
- // clone a single field value - i.e. the element type of repeated fields, the value type of maps
169
- function cloneSingularField(value, fieldInfo) {
170
- if (value === undefined) {
171
- return value;
172
- }
173
- if (fieldInfo.kind === "message") {
174
- return cloneMessage(value, resolveMessageType(fieldInfo.T).fields);
175
- }
176
- if (fieldInfo.kind === "oneof") {
177
- if (value.case === undefined) {
178
- return undefined;
179
- }
180
- const selectedField = fieldInfo.findField(value.case);
181
- if (!selectedField) {
182
- throw new Error(`Invalid oneof case "${value.case}" for ${fieldInfo.name}`);
183
- }
184
- return {
185
- case: value.case,
186
- value: cloneSingularField(value.value, selectedField),
187
- };
188
- }
189
- if (value instanceof Uint8Array) {
190
- const c = new Uint8Array(value.byteLength);
191
- c.set(value);
192
- return c;
193
- }
194
- return value;
195
- }
196
- // TODO use isFieldSet() here to support future field presence
197
176
  export function cloneMessage(message, fields) {
198
- const clone = {};
199
- for (const member of fields.byMember()) {
200
- const source = message[member.localName];
201
- let copy;
202
- if (member.repeated) {
203
- copy = source.map((v) => cloneSingularField(v, member));
204
- }
205
- else if (member.kind == "map") {
206
- copy = {};
207
- for (const [key, v] of Object.entries(source)) {
208
- copy[key] = cloneSingularField(v, member);
209
- }
210
- }
211
- else if (member.kind == "oneof") {
212
- const f = member.findField(source.case);
213
- copy =
214
- f ?
215
- { case: source.case, value: cloneSingularField(source.value, member) }
216
- : { case: undefined };
217
- }
218
- else {
219
- copy = cloneSingularField(source, member);
220
- }
221
- clone[member.localName] = copy;
177
+ if (message == null) {
178
+ return null;
222
179
  }
180
+ const clone = Object.create(null);
181
+ applyPartialMessage(message, clone, fields, true);
223
182
  return clone;
224
183
  }
225
184
  /**
@@ -227,67 +186,42 @@ export function cloneMessage(message, fields) {
227
186
  */
228
187
  export function createCompleteMessage(fields) {
229
188
  const message = {};
230
- for (const field of fields.list()) {
231
- const fieldKind = field.kind;
189
+ for (const field of fields.byMember()) {
190
+ const { localName, kind: fieldKind } = field;
191
+ throwSanitizeKey(localName);
232
192
  switch (fieldKind) {
193
+ case "oneof":
194
+ message[localName] = Object.create(null);
195
+ message[localName].case = undefined;
196
+ break;
233
197
  case "scalar":
234
198
  if (field.repeated) {
235
- message[field.localName] = [];
199
+ message[localName] = [];
236
200
  }
237
201
  else {
238
- switch (field.T) {
239
- case ScalarType.DOUBLE:
240
- case ScalarType.FLOAT:
241
- message[field.localName] = 0;
242
- break;
243
- case ScalarType.INT64:
244
- case ScalarType.UINT64:
245
- case ScalarType.INT32:
246
- case ScalarType.FIXED64:
247
- case ScalarType.FIXED32:
248
- case ScalarType.UINT32:
249
- case ScalarType.SFIXED32:
250
- case ScalarType.SFIXED64:
251
- case ScalarType.SINT32:
252
- case ScalarType.SINT64:
253
- message[field.localName] = 0;
254
- break;
255
- case ScalarType.BOOL:
256
- message[field.localName] = false;
257
- break;
258
- case ScalarType.STRING:
259
- message[field.localName] = "";
260
- break;
261
- case ScalarType.BYTES:
262
- message[field.localName] =
263
- new Uint8Array();
264
- break;
265
- }
202
+ message[localName] = scalarZeroValue(field.T, LongType.BIGINT);
266
203
  }
267
204
  break;
268
205
  case "enum":
269
- if (field.repeated) {
270
- message[field.localName] = [];
271
- }
272
- else {
273
- message[field.localName] = 0;
274
- }
206
+ message[localName] = field.repeated ? [] : enumZeroValue(field.T);
275
207
  break;
276
208
  case "message":
209
+ // oneofs are handled above
277
210
  if (field.oneof) {
278
- message[field.localName] = undefined;
279
- continue;
211
+ break;
280
212
  }
281
- const messageType = resolveMessageType(field.T);
282
213
  if (field.repeated) {
283
- message[field.localName] = [];
284
- }
285
- else {
286
- message[field.localName] = createCompleteMessage(messageType.fields);
214
+ message[localName] = [];
215
+ break;
287
216
  }
217
+ const messageType = resolveMessageType(field.T);
218
+ message[localName] =
219
+ !!messageType.fieldWrapper ?
220
+ messageType.fieldWrapper.unwrapField(null)
221
+ : createCompleteMessage(messageType.fields);
288
222
  break;
289
223
  case "map":
290
- message[field.localName] = {};
224
+ message[localName] = Object.create(null);
291
225
  break;
292
226
  default:
293
227
  field;
package/dist/names.js CHANGED
@@ -209,6 +209,8 @@ const reservedObjectProperties = new Set([
209
209
  "toString",
210
210
  "toJSON",
211
211
  "valueOf",
212
+ "__proto__",
213
+ "prototype",
212
214
  ]);
213
215
  /**
214
216
  * Names that cannot be used for object properties because they are reserved
@@ -246,7 +248,7 @@ export const safeIdentifier = (name) => {
246
248
  return name;
247
249
  };
248
250
  export function checkSanitizeKey(key) {
249
- return typeof key === "string" && key !== "__proto__";
251
+ return typeof key === "string" && !!key.length && !reservedObjectProperties.has(key);
250
252
  }
251
253
  export function throwSanitizeKey(key) {
252
254
  if (typeof key !== "string") {
package/dist/partial.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import { FieldList, MapValueInfo } from "./field.js";
2
2
  import { Message } from "./message.js";
3
- export declare function applyPartialMessage<T extends Message<T>>(source: Message<T> | undefined, target: Message<T>, fields: FieldList): void;
4
- export declare function applyPartialMap(sourceMap: Record<string, any> | undefined, targetMap: Record<string, any>, value: MapValueInfo): void;
3
+ export declare function applyPartialMessage<T extends Message<T>>(source: Message<T> | undefined, target: Message<T>, fields: FieldList, clone?: boolean): void;
4
+ export declare function applyPartialMap(sourceMap: Record<string, any> | undefined, targetMap: Record<string, any>, value: MapValueInfo, clone: boolean): void;
package/dist/partial.js CHANGED
@@ -4,7 +4,9 @@ import { createCompleteMessage } from "./message.js";
4
4
  import { throwSanitizeKey } from "./names.js";
5
5
  import { normalizeScalarValue } from "./scalar.js";
6
6
  // applyPartialMessage applies a partial source message to a target message.
7
- export function applyPartialMessage(source, target, fields) {
7
+ //
8
+ // if clone is set: values are deep-copied including Uint8Arrays.
9
+ export function applyPartialMessage(source, target, fields, clone = false) {
8
10
  if (source == null || target == null) {
9
11
  return;
10
12
  }
@@ -62,7 +64,7 @@ export function applyPartialMessage(source, target, fields) {
62
64
  }
63
65
  }
64
66
  else if (sourceField.kind === "scalar") {
65
- dv.value = normalizeScalarValue(sourceField.T, sv);
67
+ dv.value = normalizeScalarValue(sourceField.T, sv, clone);
66
68
  }
67
69
  else {
68
70
  dv.value = sv;
@@ -77,10 +79,10 @@ export function applyPartialMessage(source, target, fields) {
77
79
  if (dst == null || !Array.isArray(dst)) {
78
80
  dst = t[localName] = [];
79
81
  }
80
- dst.push(...sourceValue.map((v) => normalizeScalarValue(member.T, v)));
82
+ dst.push(...sourceValue.map((v) => normalizeScalarValue(member.T, v, clone)));
81
83
  break;
82
84
  }
83
- t[localName] = normalizeScalarValue(member.T, sourceValue);
85
+ t[localName] = normalizeScalarValue(member.T, sourceValue, clone);
84
86
  break;
85
87
  case "enum":
86
88
  t[localName] = normalizeEnumValue(member.T, sourceValue);
@@ -93,7 +95,7 @@ export function applyPartialMessage(source, target, fields) {
93
95
  if (typeof tMap !== "object") {
94
96
  tMap = t[localName] = Object.create(null);
95
97
  }
96
- applyPartialMap(sourceValue, tMap, member.V);
98
+ applyPartialMap(sourceValue, tMap, member.V, clone);
97
99
  break;
98
100
  case "message":
99
101
  const mt = resolveMessageType(member.T);
@@ -137,7 +139,7 @@ export function applyPartialMessage(source, target, fields) {
137
139
  }
138
140
  }
139
141
  // applyPartialMap applies a partial source map to a target map.
140
- export function applyPartialMap(sourceMap, targetMap, value) {
142
+ export function applyPartialMap(sourceMap, targetMap, value, clone) {
141
143
  if (sourceMap == null) {
142
144
  return;
143
145
  }
@@ -149,7 +151,7 @@ export function applyPartialMap(sourceMap, targetMap, value) {
149
151
  for (const [k, v] of Object.entries(sourceMap)) {
150
152
  throwSanitizeKey(k);
151
153
  if (v !== undefined) {
152
- targetMap[k] = normalizeScalarValue(value.T, v);
154
+ targetMap[k] = normalizeScalarValue(value.T, v, clone);
153
155
  }
154
156
  else {
155
157
  delete targetMap[k];
@@ -384,9 +384,17 @@ function generateWktMethods(schema, f, message, ref) {
384
384
  f.print(" }");
385
385
  f.print(` return new Date(ms).toISOString().replace(".000Z", z);`);
386
386
  f.print(" },");
387
- f.print(" toDate(msg: ", message, "): Date {");
387
+ f.print(" toDate(msg: ", message, " | null | undefined): Date | null {");
388
+ f.print(" if (!msg?.", localName(ref.seconds), " && !msg?.", localName(ref.nanos), ") { return null; }");
388
389
  f.print(" return new Date(Number(msg.", localName(ref.seconds), " ?? 0) * 1000 + Math.ceil((msg.", localName(ref.nanos), " ?? 0) / 1000000));");
389
390
  f.print(" },");
391
+ f.print(" fromDate(value: Date | null | undefined): ", message, " {");
392
+ f.print(" if (value == null) { return {}; }");
393
+ f.print(" const ms = value.getTime();");
394
+ f.print(" const seconds = Math.floor(ms / 1000);");
395
+ f.print(" const nanos = (ms % 1000) * 1000000;");
396
+ f.print(" return { ", localName(ref.seconds), ": ", protoInt64, ".parse(seconds), ", localName(ref.nanos), ": nanos };");
397
+ f.print(" },");
390
398
  break;
391
399
  case "google.protobuf.Duration":
392
400
  f.print(" fromJson(json: ", JsonValue, " | null | undefined, _options?: Partial<", JsonReadOptions, ">): ", message, " {");
@@ -552,6 +560,30 @@ function generateWktMethods(schema, f, message, ref) {
552
560
  }
553
561
  function generateWktFieldWrapper(f, message, ref) {
554
562
  switch (ref?.typeName) {
563
+ /* TODO Wrap Timestamp => Date
564
+ case "google.protobuf.Timestamp": {
565
+ f.print(" fieldWrapper: {");
566
+ f.print(
567
+ " wrapField(value: ",
568
+ message,
569
+ " | Date | null | undefined): ",
570
+ message,
571
+ " {",
572
+ );
573
+ f.print(
574
+ " if (value == null || value instanceof Date) { return ",
575
+ message,
576
+ "_Wkt.fromDate(value); }",
577
+ );
578
+ f.print(" return ", message, ".createComplete(value);");
579
+ f.print(" },");
580
+ f.print(" unwrapField(msg: ", message, "): Date | null {");
581
+ f.print(" return ", message, "_Wkt.toDate(msg);");
582
+ f.print(" }");
583
+ f.print(" } as const,");
584
+ break;
585
+ }
586
+ */
555
587
  case "google.protobuf.DoubleValue":
556
588
  case "google.protobuf.FloatValue":
557
589
  case "google.protobuf.Int64Value":
package/dist/scalar.d.ts CHANGED
@@ -72,6 +72,7 @@ export declare function isScalarZeroValue(type: ScalarType, value: unknown): boo
72
72
  * Zero or null is cast to the zero value.
73
73
  * Bytes is cast to a Uint8Array.
74
74
  * The BigInt long type is used.
75
+ * If clone is set, Uint8Array will always be copied to a new value.
75
76
  */
76
- export declare function normalizeScalarValue<T>(type: ScalarType, value: T | null | undefined, longType?: LongType): T;
77
- export declare function toU8Arr(input: ArrayLike<number>): Uint8Array;
77
+ export declare function normalizeScalarValue<T>(type: ScalarType, value: T | null | undefined, clone: boolean, longType?: LongType): T;
78
+ export declare function toU8Arr(input: ArrayLike<number>, clone: boolean): Uint8Array;
package/dist/scalar.js CHANGED
@@ -166,21 +166,22 @@ export function isScalarZeroValue(type, value) {
166
166
  * Zero or null is cast to the zero value.
167
167
  * Bytes is cast to a Uint8Array.
168
168
  * The BigInt long type is used.
169
+ * If clone is set, Uint8Array will always be copied to a new value.
169
170
  */
170
- export function normalizeScalarValue(type, value, longType = LongType.BIGINT) {
171
+ export function normalizeScalarValue(type, value, clone, longType = LongType.BIGINT) {
171
172
  if (value == null) {
172
173
  return scalarZeroValue(type, longType);
173
174
  }
174
175
  if (type === ScalarType.BYTES) {
175
- return toU8Arr(value);
176
+ return toU8Arr(value, clone);
176
177
  }
177
178
  if (isScalarZeroValue(type, value)) {
178
179
  return scalarZeroValue(type, longType);
179
180
  }
180
- // TODO: enforce correct type for other values as well.
181
181
  return value;
182
182
  }
183
183
  // converts any ArrayLike<number> to Uint8Array if necessary.
184
- export function toU8Arr(input) {
185
- return input instanceof Uint8Array ? input : new Uint8Array(input);
184
+ // if clone is set, force clones the array to a copy.
185
+ export function toU8Arr(input, clone) {
186
+ return !clone && input instanceof Uint8Array ? input : new Uint8Array(input);
186
187
  }
@@ -4,10 +4,8 @@
4
4
  "compilerOptions": {
5
5
  "paths": {
6
6
  "@aptre/protobuf-es-lite": [
7
- "../",
8
- "../dist"
7
+ "../src",
9
8
  ],
10
- "@aptre/protobuf-es-lite/google": ["../src/google", "../dist/google"],
11
9
  }
12
10
  }
13
11
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@aptre/protobuf-es-lite",
3
3
  "description": "Lightweight Protobuf codegen for TypeScript and JavaScript.",
4
- "version": "0.4.0",
4
+ "version": "0.4.2",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "url": "git+ssh://git@github.com/aperturerobotics/protobuf-es-lite.git"
@@ -49,18 +49,19 @@
49
49
  "scripts": {
50
50
  "clean": "rimraf ./dist",
51
51
  "build": "npm run clean && tsc --project tsconfig.json --outDir ./dist",
52
- "release:version": "npm version patch -m \"release: v%s\" --no-git-tag-version",
53
- "release:version:minor": "npm version minor -m \"release: v%s\" --no-git-tag-version",
54
- "release:commit": "git reset && git add package.json && git commit -s -m \"release: v$npm_package_version\" && git tag v$npm_package_version",
55
- "release:publish": "git push && git push --tags && npm run build && npm publish",
56
- "release": "npm run release:version && npm run release:commit",
57
- "release:minor": "npm run release:version:minor && npm run release:commit",
58
52
  "typecheck": "tsc --noEmit --project tsconfig.json --outDir ./dist",
59
53
  "gen": "npm run build && npm run gen:wkt && npm run gen:example",
60
54
  "gen:wkt": "cd ./src && bash gen.bash",
61
55
  "gen:example": "bash gen.bash ./example/example.proto",
62
56
  "format": "prettier --write './src/**/(*.ts|*.tsx|*.html|*.css|*.scss)'",
63
- "precommit": "lint-staged"
57
+ "precommit": "lint-staged",
58
+ "test": "vitest run",
59
+ "release:version": "npm version patch -m \"release: v%s\" --no-git-tag-version",
60
+ "release:version:minor": "npm version minor -m \"release: v%s\" --no-git-tag-version",
61
+ "release:commit": "git reset && git add package.json && git commit -s -m \"release: v$npm_package_version\" && git tag v$npm_package_version",
62
+ "release:publish": "git push && git push --tags && npm run build && npm publish",
63
+ "release": "npm run release:version && npm run release:commit",
64
+ "release:minor": "npm run release:version:minor && npm run release:commit"
64
65
  },
65
66
  "preferUnplugged": true,
66
67
  "pre-commit": [
@@ -76,7 +77,8 @@
76
77
  "pre-commit": "^1.2.2",
77
78
  "prettier": "^3.2.5",
78
79
  "rimraf": "^5.0.5",
79
- "typescript": "^5.4.5"
80
+ "typescript": "^5.4.5",
81
+ "vitest": "^1.5.3"
80
82
  },
81
83
  "lint-staged": {
82
84
  "package.json": "prettier --write",