@pbwire/pbwire 0.1.0 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -20
- package/build.sh +1 -1
- package/lib/pbwire-compat.js +525 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,9 +1,36 @@
|
|
|
1
1
|
# pbwire Node.js binding
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
The runtime is `runtime/addon.c`, an N-API addon in C over `libpbwire.a`;
|
|
4
|
+
the `Schema`/`Message` surface is `lib/pbwire.js`. No npm dependencies.
|
|
5
|
+
|
|
6
|
+
Most code never touches that surface: generate a protobufjs-static-shaped
|
|
7
|
+
facade once (`make -C codegen gen-node`) and use it like the
|
|
8
|
+
library it mirrors — same `create`/`encode`/`decode`/`verify` calls,
|
|
9
|
+
same observable behavior, but the wire format stays in the C core:
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
const { Person } = require('./tests/gen/addressbook.js');
|
|
13
|
+
|
|
14
|
+
const msg = Person.create({name: 'alice', id: 7});
|
|
15
|
+
Person.verify(msg); // null: valid
|
|
16
|
+
const wire = Person.encode(msg).finish(); // Buffer
|
|
17
|
+
const back = Person.decode(wire);
|
|
18
|
+
back.name; // 'alice'
|
|
19
|
+
back.toJSON(); // {name: 'alice', id: 7, ...}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The generated file embeds its own `FileDescriptorSet`, so there is no
|
|
23
|
+
`.desc` to ship: `require` it and go. int64 reads as `BigInt`; `create`/
|
|
24
|
+
`fromObject` also take `number|string|Long-like`. `toObject()` keys are
|
|
25
|
+
camelCase (protobufjs convention); pass `{keepCase: true}` for proto
|
|
26
|
+
names. `fromObject` rejects bad shapes with `TypeError`; `verify`
|
|
27
|
+
returns the same reason strings as protobufjs. The one deliberate
|
|
28
|
+
difference: direct post-hoc writes of an implicit zero (`m.i32 = 0`)
|
|
29
|
+
are not marked present — set values through `create`/`fromObject` to
|
|
30
|
+
write explicit zeros.
|
|
31
|
+
|
|
32
|
+
The dynamic API is still there for gateways and schemas known only at
|
|
33
|
+
runtime:
|
|
7
34
|
|
|
8
35
|
```js
|
|
9
36
|
const { Schema } = require('./lib/pbwire');
|
|
@@ -11,18 +38,12 @@ const { Schema } = require('./lib/pbwire');
|
|
|
11
38
|
const schema = Schema.fromFile('tests/data/demo.desc');
|
|
12
39
|
const person = schema.message('demo.Person');
|
|
13
40
|
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const back = person.decode(wire);
|
|
22
|
-
back.get('name'); // 'alice'
|
|
23
|
-
back.get('inner').x; // 42
|
|
24
|
-
back.encode().equals(wire); // true: byte-exact re-encode
|
|
25
|
-
back.free();
|
|
41
|
+
const wire2 = person.encode({name: 'alice', id: 7, inner: {x: 42}});
|
|
42
|
+
const back2 = person.decode(wire2);
|
|
43
|
+
back2.get('name'); // 'alice'
|
|
44
|
+
back2.get('inner').x; // 42
|
|
45
|
+
back2.encode().equals(wire2); // true: byte-exact re-encode
|
|
46
|
+
back2.free();
|
|
26
47
|
```
|
|
27
48
|
|
|
28
49
|
## How it works
|
|
@@ -195,11 +216,11 @@ Also out of scope here: JSON/text format (`pbw_json_print`/`pbw_text_print`),
|
|
|
195
216
|
field presence introspection (`pbw_message_field_count`), and delimited framing.
|
|
196
217
|
Each is one addon export away when a consumer needs it.
|
|
197
218
|
|
|
198
|
-
##
|
|
219
|
+
## Generated models
|
|
199
220
|
|
|
200
|
-
`protoc-gen-pbwire
|
|
221
|
+
`protoc-gen-pbwire` (in `../../codegen`) generates `matrix3.d.ts` interfaces
|
|
201
222
|
(for TypeScript) and a JSDoc/`@ts-check` `.js` with the same shapes plus the enum
|
|
202
223
|
constants, describing exactly the plain objects `MessageType.encode()` accepts
|
|
203
224
|
and `Message.get()` returns. The committed output is `tests/gen/` and
|
|
204
|
-
`examples/gen/`; the
|
|
205
|
-
See
|
|
225
|
+
`examples/gen/`; the generated test type-checks with `tsc --strict --checkJs`.
|
|
226
|
+
See `../../codegen/README.md`.
|
package/build.sh
CHANGED
|
@@ -46,4 +46,4 @@ if [ "${1:-all}" = build ]; then
|
|
|
46
46
|
fi
|
|
47
47
|
|
|
48
48
|
# --expose-gc so the finalizer test can force the collections it checks.
|
|
49
|
-
exec node --expose-gc --test "$here/test/test_pbwire.js" "$here/tests/
|
|
49
|
+
exec node --expose-gc --test "$here/test/test_pbwire.js" "$here/tests/generated_test.js" "$here/tests/compat_test.js"
|
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* pbwire compat runtime: the verify/fromObject/toObject engine every generated
|
|
4
|
+
* facade delegates to. Schemas are cached per embedded descriptor set, so any
|
|
5
|
+
* number of generated files share this module with no global-state surprises:
|
|
6
|
+
* one entry per distinct FileDescriptorSet, keyed by its own bytes.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { Schema } = require('./pbwire.js');
|
|
10
|
+
|
|
11
|
+
const _schemas = new Map();
|
|
12
|
+
function _type(descB64, full) {
|
|
13
|
+
let byName = _schemas.get(descB64);
|
|
14
|
+
if (byName === undefined) {
|
|
15
|
+
byName = new Map();
|
|
16
|
+
byName.set('', Schema.fromBytes(Buffer.from(descB64, 'base64')));
|
|
17
|
+
_schemas.set(descB64, byName);
|
|
18
|
+
}
|
|
19
|
+
let t = byName.get(full);
|
|
20
|
+
if (t === undefined) {
|
|
21
|
+
t = byName.get('').message(full);
|
|
22
|
+
byName.set(full, t);
|
|
23
|
+
}
|
|
24
|
+
return t;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function _isLongLike(v) {
|
|
28
|
+
return typeof v === 'object' && v !== null &&
|
|
29
|
+
Number.isInteger(v.low) && Number.isInteger(v.high);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function _longToBigInt(v) {
|
|
33
|
+
return (BigInt(v.high >>> 0) << 32n) | BigInt(v.low >>> 0);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// _toBigInt normalizes an int64-ish value for the encoder. BigInt passes
|
|
37
|
+
// through, numbers pass through (the addon casts them, same as plain
|
|
38
|
+
// objects), numeric strings and Long-like {low, high} become BigInt.
|
|
39
|
+
function _toBigInt(v, path, unsigned) {
|
|
40
|
+
if (typeof v === 'bigint') {
|
|
41
|
+
// fall through to the range check below
|
|
42
|
+
} else if (typeof v === 'number') {
|
|
43
|
+
if (!Number.isInteger(v)) {
|
|
44
|
+
throw new TypeError('pbwire: ' + path + ' is not an integer');
|
|
45
|
+
}
|
|
46
|
+
return v;
|
|
47
|
+
} else if (typeof v === 'string') {
|
|
48
|
+
if (!/^-?\d+$/.test(v)) {
|
|
49
|
+
throw new TypeError('pbwire: ' + path + ' is not an integer string');
|
|
50
|
+
}
|
|
51
|
+
v = BigInt(v);
|
|
52
|
+
} else if (_isLongLike(v)) {
|
|
53
|
+
v = _longToBigInt(v);
|
|
54
|
+
} else {
|
|
55
|
+
throw new TypeError('pbwire: ' + path + ' is not an int64 value');
|
|
56
|
+
}
|
|
57
|
+
if (unsigned) {
|
|
58
|
+
if (v < 0n || v > 18446744073709551615n) {
|
|
59
|
+
throw new TypeError('pbwire: ' + path + ' out of uint64 range');
|
|
60
|
+
}
|
|
61
|
+
} else if (v < -9223372036854775808n || v > 9223372036854775807n) {
|
|
62
|
+
throw new TypeError('pbwire: ' + path + ' out of int64 range');
|
|
63
|
+
}
|
|
64
|
+
return v;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Shape check only (any BigInt passes); range is enforced when the value
|
|
68
|
+
// is normalized for the encoder in _toBigInt.
|
|
69
|
+
function _checkInt64(v) {
|
|
70
|
+
if (typeof v === 'bigint') {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
if (typeof v === 'number') {
|
|
74
|
+
return Number.isInteger(v);
|
|
75
|
+
}
|
|
76
|
+
if (typeof v === 'string') {
|
|
77
|
+
return /^-?\d+$/.test(v);
|
|
78
|
+
}
|
|
79
|
+
return _isLongLike(v);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// _verifyField returns null when v is valid, else a reason string.
|
|
83
|
+
function _verifyField(fd, v, path, enums, classes) {
|
|
84
|
+
if (fd.rep) {
|
|
85
|
+
if (!Array.isArray(v)) {
|
|
86
|
+
return path + ': array expected';
|
|
87
|
+
}
|
|
88
|
+
for (let i = 0; i < v.length; i++) {
|
|
89
|
+
const bad = _verifyOne(fd, v[i], path + '[' + i + ']', enums, classes);
|
|
90
|
+
if (bad !== null) {
|
|
91
|
+
return bad;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
return _verifyOne(fd, v, path, enums, classes);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function _verifyOne(fd, v, path, enums, classes) {
|
|
100
|
+
switch (fd.k) {
|
|
101
|
+
// Reason strings mirror protobufjs verify() verbatim so mixed code reads
|
|
102
|
+
// the same errors from either library. Like protobufjs, 32-bit checks are
|
|
103
|
+
// integer-only (range truncation happens in the encoder, identically).
|
|
104
|
+
case 'i32':
|
|
105
|
+
case 's32':
|
|
106
|
+
case 'sfx32':
|
|
107
|
+
case 'u32':
|
|
108
|
+
case 'fx32':
|
|
109
|
+
return Number.isInteger(v) ? null : path + ': integer expected';
|
|
110
|
+
case 'i64':
|
|
111
|
+
case 's64':
|
|
112
|
+
case 'sfx64':
|
|
113
|
+
case 'u64':
|
|
114
|
+
case 'fx64':
|
|
115
|
+
return _checkInt64(v) ? null : path + ': integer|Long expected';
|
|
116
|
+
case 'f32':
|
|
117
|
+
case 'f64':
|
|
118
|
+
return (typeof v === 'number') ? null : path + ': number expected';
|
|
119
|
+
case 'bool':
|
|
120
|
+
return (typeof v === 'boolean') ? null : path + ': boolean expected';
|
|
121
|
+
case 'str':
|
|
122
|
+
return (typeof v === 'string') ? null : path + ': string expected';
|
|
123
|
+
case 'bytes':
|
|
124
|
+
return (typeof v === 'object' && v !== null &&
|
|
125
|
+
(Buffer.isBuffer(v) || v instanceof Uint8Array))
|
|
126
|
+
? null : path + ': buffer expected';
|
|
127
|
+
case 'enum': {
|
|
128
|
+
const names = enums[fd.en];
|
|
129
|
+
if (typeof v === 'number' && Number.isInteger(v)) {
|
|
130
|
+
// Open enums (proto3) carry unknown values through the round trip.
|
|
131
|
+
if (!fd.closed || names === undefined) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
for (const key in names) {
|
|
135
|
+
if (names[key] === v) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return path + ': enum value expected';
|
|
140
|
+
}
|
|
141
|
+
return path + ': enum value expected';
|
|
142
|
+
}
|
|
143
|
+
case 'msg':
|
|
144
|
+
if (typeof v !== 'object' || v === null || Array.isArray(v)) {
|
|
145
|
+
return path + ': object expected';
|
|
146
|
+
}
|
|
147
|
+
return classes[fd.msg].verify(v);
|
|
148
|
+
default:
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function _fromOne(fd, v, path, enums, classes) {
|
|
154
|
+
switch (fd.k) {
|
|
155
|
+
case 'i64':
|
|
156
|
+
case 's64':
|
|
157
|
+
case 'sfx64':
|
|
158
|
+
return _toBigInt(v, path, false);
|
|
159
|
+
case 'u64':
|
|
160
|
+
case 'fx64':
|
|
161
|
+
return _toBigInt(v, path, true);
|
|
162
|
+
case 'bytes':
|
|
163
|
+
if (Buffer.isBuffer(v)) {
|
|
164
|
+
return v;
|
|
165
|
+
}
|
|
166
|
+
if (v instanceof Uint8Array) {
|
|
167
|
+
return Buffer.from(v);
|
|
168
|
+
}
|
|
169
|
+
throw new TypeError('pbwire: ' + path + ' is not bytes');
|
|
170
|
+
case 'enum': {
|
|
171
|
+
if (typeof v === 'string') {
|
|
172
|
+
const names = enums[fd.en];
|
|
173
|
+
if (names !== undefined && names[v] !== undefined) {
|
|
174
|
+
return names[v];
|
|
175
|
+
}
|
|
176
|
+
throw new TypeError('pbwire: ' + path + ' unknown enum name ' + v);
|
|
177
|
+
}
|
|
178
|
+
if (typeof v === 'number' && Number.isInteger(v)) {
|
|
179
|
+
return v;
|
|
180
|
+
}
|
|
181
|
+
throw new TypeError('pbwire: ' + path + ' is not an enum value');
|
|
182
|
+
}
|
|
183
|
+
case 'msg':
|
|
184
|
+
if (v !== null && typeof v === 'object' && !(v instanceof classes[fd.msg])) {
|
|
185
|
+
return classes[fd.msg].fromObject(v);
|
|
186
|
+
}
|
|
187
|
+
return v;
|
|
188
|
+
default:
|
|
189
|
+
return v;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function _fromValue(fd, v, path, enums, classes) {
|
|
194
|
+
if (v === null || v === undefined) {
|
|
195
|
+
return fd.rep ? [] : null;
|
|
196
|
+
}
|
|
197
|
+
if (fd.rep) {
|
|
198
|
+
if (!Array.isArray(v)) {
|
|
199
|
+
throw new TypeError('pbwire: ' + path + ' is not an array');
|
|
200
|
+
}
|
|
201
|
+
return v.map((item, i) => _fromOne(fd, item, path + '[' + i + ']', enums, classes));
|
|
202
|
+
}
|
|
203
|
+
return _fromOne(fd, v, path, enums, classes);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function _toInt64(v, mode) {
|
|
207
|
+
const big = typeof v === 'bigint' ? v
|
|
208
|
+
: typeof v === 'number' ? BigInt(Math.trunc(v))
|
|
209
|
+
: _longToBigInt(v);
|
|
210
|
+
if (mode === 'String') {
|
|
211
|
+
return big.toString();
|
|
212
|
+
}
|
|
213
|
+
if (mode === 'Number') {
|
|
214
|
+
return Number(big);
|
|
215
|
+
}
|
|
216
|
+
return big;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function _toOne(fd, v, opts, enums, revEnums) {
|
|
220
|
+
switch (fd.k) {
|
|
221
|
+
case 'i64':
|
|
222
|
+
case 's64':
|
|
223
|
+
case 'sfx64':
|
|
224
|
+
case 'u64':
|
|
225
|
+
case 'fx64':
|
|
226
|
+
return _toInt64(v, opts.longs || 'BigInt');
|
|
227
|
+
case 'enum':
|
|
228
|
+
if ((opts.enums || 'Number') === 'String') {
|
|
229
|
+
const rev = revEnums[fd.en];
|
|
230
|
+
if (rev !== undefined && rev[v] !== undefined) {
|
|
231
|
+
return rev[v];
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return v;
|
|
235
|
+
case 'bytes':
|
|
236
|
+
return (opts.bytes || 'Buffer') === 'base64' ? Buffer.from(v).toString('base64') : v;
|
|
237
|
+
case 'msg':
|
|
238
|
+
return v.toObject(opts);
|
|
239
|
+
default:
|
|
240
|
+
return v;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Output keys are camelCase like protobufjs toObject; keepCase:true keeps
|
|
245
|
+
// the proto names (the dynamic API's convention).
|
|
246
|
+
function _toPlain(cls, msg, opts, enums, revEnums) {
|
|
247
|
+
const out = {};
|
|
248
|
+
const keyOf = (fd) => (opts.keepCase ? fd.n : fd.js);
|
|
249
|
+
const marked = msg._present instanceof Set ? msg._present : null;
|
|
250
|
+
for (const fd of cls._FIELDS) {
|
|
251
|
+
const v = msg[fd.js];
|
|
252
|
+
if (v === null || v === undefined) {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
if (fd.rep && v.length === 0) {
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
// Unmarked fields are constructor defaults or direct post-hoc writes:
|
|
259
|
+
// include non-default values (direct post-hoc writes work), drop default
|
|
260
|
+
// ones (so create({}) encodes empty). Marked fields always go on the wire.
|
|
261
|
+
if (!opts.defaults && !fd.req && marked !== null &&
|
|
262
|
+
!marked.has(fd.js) && _isDefault(fd, v)) {
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
out[keyOf(fd)] = fd.rep
|
|
266
|
+
? v.map((item) => _toOne(fd, item, opts, enums, revEnums))
|
|
267
|
+
: _toOne(fd, v, opts, enums, revEnums);
|
|
268
|
+
}
|
|
269
|
+
if (opts.defaults) {
|
|
270
|
+
for (const fd of cls._FIELDS) {
|
|
271
|
+
if (out[keyOf(fd)] !== undefined) {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if (fd.rep) {
|
|
275
|
+
out[keyOf(fd)] = [];
|
|
276
|
+
} else if (fd.k === 'msg') {
|
|
277
|
+
out[keyOf(fd)] = null;
|
|
278
|
+
} else {
|
|
279
|
+
out[keyOf(fd)] = _toOne(fd, _defaultFor(fd), opts, enums, revEnums);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return out;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function _isDefault(fd, v) {
|
|
287
|
+
if (fd.rep) {
|
|
288
|
+
return v.length === 0;
|
|
289
|
+
}
|
|
290
|
+
switch (fd.k) {
|
|
291
|
+
case 'i64':
|
|
292
|
+
case 's64':
|
|
293
|
+
case 'sfx64':
|
|
294
|
+
case 'u64':
|
|
295
|
+
case 'fx64':
|
|
296
|
+
return v === 0 || v === 0n;
|
|
297
|
+
case 'bool':
|
|
298
|
+
return v === false;
|
|
299
|
+
case 'str':
|
|
300
|
+
return v === '';
|
|
301
|
+
case 'bytes':
|
|
302
|
+
return v.length === 0;
|
|
303
|
+
case 'msg':
|
|
304
|
+
return v === null || v === undefined;
|
|
305
|
+
default:
|
|
306
|
+
return v === 0;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function _defaultFor(fd, classes) {
|
|
311
|
+
if (fd.rep) {
|
|
312
|
+
return [];
|
|
313
|
+
}
|
|
314
|
+
// Presence fields (explicit presence or sub-message) default to absent.
|
|
315
|
+
if (fd.has) {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
switch (fd.k) {
|
|
319
|
+
case 'i64':
|
|
320
|
+
case 's64':
|
|
321
|
+
case 'sfx64':
|
|
322
|
+
case 'u64':
|
|
323
|
+
case 'fx64':
|
|
324
|
+
return 0n;
|
|
325
|
+
case 'f32':
|
|
326
|
+
case 'f64':
|
|
327
|
+
case 'i32':
|
|
328
|
+
case 's32':
|
|
329
|
+
case 'sfx32':
|
|
330
|
+
case 'u32':
|
|
331
|
+
case 'fx32':
|
|
332
|
+
case 'enum':
|
|
333
|
+
return 0;
|
|
334
|
+
case 'bool':
|
|
335
|
+
return false;
|
|
336
|
+
case 'str':
|
|
337
|
+
return '';
|
|
338
|
+
case 'bytes':
|
|
339
|
+
return Buffer.alloc(0);
|
|
340
|
+
case 'msg':
|
|
341
|
+
return null;
|
|
342
|
+
default:
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function _encodeVarint(n) {
|
|
348
|
+
const parts = [];
|
|
349
|
+
while (n >= 128) {
|
|
350
|
+
parts.push((n % 128) | 128);
|
|
351
|
+
n = Math.floor(n / 128);
|
|
352
|
+
}
|
|
353
|
+
parts.push(n);
|
|
354
|
+
return Buffer.from(parts);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function _decodeVarint(buf, pos) {
|
|
358
|
+
let n = 0;
|
|
359
|
+
let shift = 0;
|
|
360
|
+
while (pos < buf.length) {
|
|
361
|
+
const b = buf[pos++];
|
|
362
|
+
n += (b & 127) * 2 ** shift;
|
|
363
|
+
shift += 7;
|
|
364
|
+
if ((b & 128) === 0) {
|
|
365
|
+
return [n, pos];
|
|
366
|
+
}
|
|
367
|
+
if (shift >= 64) {
|
|
368
|
+
throw new Error('pbwire: varint too long');
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
throw new Error('pbwire: truncated varint');
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// _makeClass builds one message class over a MessageType. cls._FIELDS is the
|
|
375
|
+
// table the engine above reads; wire bytes always come from the C core.
|
|
376
|
+
// _fromWire converts C-validated wire values into an instance. Unlike
|
|
377
|
+
// fromObject it performs no validation and no coercion: the decoder has
|
|
378
|
+
// already checked every value against the schema, so nested plains only
|
|
379
|
+
// need wrapping and every present field only needs marking. The encode
|
|
380
|
+
// boundary (addon tree build) remains the loudness net for bad data.
|
|
381
|
+
function _fromWire(cls, plain) {
|
|
382
|
+
// new keeps V8's hidden-class shape (measured faster than Object.create
|
|
383
|
+
// + defineProperty here); the constructor's defaults stand for absent
|
|
384
|
+
// fields, present ones are converted and marked below.
|
|
385
|
+
const inst = new cls();
|
|
386
|
+
const classes = cls._CLASSES;
|
|
387
|
+
for (const fd of cls._FIELDS) {
|
|
388
|
+
const v = plain[fd.n];
|
|
389
|
+
if (v === undefined) {
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
if (fd.k === 'msg') {
|
|
393
|
+
if (fd.rep) {
|
|
394
|
+
inst[fd.js] = v.map((item) => _fromWire(classes[fd.msg], item));
|
|
395
|
+
} else {
|
|
396
|
+
inst[fd.js] = _fromWire(classes[fd.msg], v);
|
|
397
|
+
}
|
|
398
|
+
} else if (fd.rep) {
|
|
399
|
+
inst[fd.js] = v.slice();
|
|
400
|
+
} else {
|
|
401
|
+
inst[fd.js] = v;
|
|
402
|
+
}
|
|
403
|
+
inst._present.add(fd.js);
|
|
404
|
+
}
|
|
405
|
+
return inst;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function _makeClass(full, descB64, fields) {
|
|
409
|
+
class Compat {
|
|
410
|
+
constructor(props) {
|
|
411
|
+
props = props || {};
|
|
412
|
+
Object.defineProperty(this, '_present', { value: new Set(), enumerable: false, writable: true });
|
|
413
|
+
for (const fd of fields) {
|
|
414
|
+
const v = props[fd.js] !== undefined ? props[fd.js] : props[fd.n];
|
|
415
|
+
if (v === undefined) {
|
|
416
|
+
this[fd.js] = _defaultFor(fd);
|
|
417
|
+
} else {
|
|
418
|
+
this[fd.js] = v;
|
|
419
|
+
// fromObject normalizes below; direct construction marks as set.
|
|
420
|
+
this._present.add(fd.js);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
static create(props) {
|
|
425
|
+
return this.fromObject(props || {});
|
|
426
|
+
}
|
|
427
|
+
static encode(message) {
|
|
428
|
+
const inst = (message instanceof this) ? message : this.fromObject(message);
|
|
429
|
+
// The dynamic encoder takes proto names; toObject() shows camelCase.
|
|
430
|
+
const plain = _toPlain(this, inst, { keepCase: true }, this._ENUMS, this._REVENUMS);
|
|
431
|
+
const wire = _type(descB64, full).encode(plain);
|
|
432
|
+
return { finish: () => wire };
|
|
433
|
+
}
|
|
434
|
+
static encodeDelimited(message) {
|
|
435
|
+
const wire = this.encode(message).finish();
|
|
436
|
+
const out = Buffer.concat([_encodeVarint(wire.length), wire]);
|
|
437
|
+
return { finish: () => out };
|
|
438
|
+
}
|
|
439
|
+
static decode(bytes) {
|
|
440
|
+
const buf = Buffer.isBuffer(bytes) ? bytes : Buffer.from(bytes);
|
|
441
|
+
const m = _type(descB64, full).decode(buf);
|
|
442
|
+
const plain = {};
|
|
443
|
+
for (const fd of fields) {
|
|
444
|
+
const v = m.get(fd.n);
|
|
445
|
+
if (v !== undefined) {
|
|
446
|
+
plain[fd.n] = v;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return _fromWire(Compat, plain);
|
|
450
|
+
}
|
|
451
|
+
static decodeDelimited(bytes) {
|
|
452
|
+
const buf = Buffer.isBuffer(bytes) ? bytes : Buffer.from(bytes);
|
|
453
|
+
const pair = _decodeVarint(buf, 0);
|
|
454
|
+
return this.decode(buf.subarray(pair[1], pair[1] + pair[0]));
|
|
455
|
+
}
|
|
456
|
+
static verify(obj) {
|
|
457
|
+
if (obj === null || typeof obj !== 'object' || Array.isArray(obj)) {
|
|
458
|
+
return 'object expected';
|
|
459
|
+
}
|
|
460
|
+
const seen = new Map();
|
|
461
|
+
for (const fd of fields) {
|
|
462
|
+
let v = obj[fd.js];
|
|
463
|
+
if (v === undefined) {
|
|
464
|
+
v = obj[fd.n];
|
|
465
|
+
}
|
|
466
|
+
if (v === null || v === undefined) {
|
|
467
|
+
if (fd.req) {
|
|
468
|
+
return fd.n + ': required field missing';
|
|
469
|
+
}
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
const bad = _verifyField(fd, v, fd.n, this._ENUMS, this._CLASSES);
|
|
473
|
+
if (bad !== null) {
|
|
474
|
+
return bad;
|
|
475
|
+
}
|
|
476
|
+
if (fd.oneof !== null) {
|
|
477
|
+
if (seen.has(fd.oneof)) {
|
|
478
|
+
return fd.oneof + ': multiple values';
|
|
479
|
+
}
|
|
480
|
+
seen.set(fd.oneof, true);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
static fromObject(obj) {
|
|
486
|
+
if (obj === null || typeof obj !== 'object' || Array.isArray(obj)) {
|
|
487
|
+
throw new TypeError('pbwire: fromObject expects an object');
|
|
488
|
+
}
|
|
489
|
+
const inst = new this();
|
|
490
|
+
for (const fd of fields) {
|
|
491
|
+
let v = obj[fd.js] !== undefined ? obj[fd.js] : obj[fd.n];
|
|
492
|
+
if (v === undefined) {
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
if (v === null) {
|
|
496
|
+
inst[fd.js] = fd.rep ? [] : null;
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
// One validation logic for verify() and fromObject(): normalize,
|
|
500
|
+
// then reject with the same reason verify() would give.
|
|
501
|
+
const nv = _fromValue(fd, v, fd.n, this._ENUMS, this._CLASSES);
|
|
502
|
+
const bad = _verifyField(fd, nv, fd.n, this._ENUMS, this._CLASSES);
|
|
503
|
+
if (bad !== null) {
|
|
504
|
+
throw new TypeError('pbwire: ' + bad);
|
|
505
|
+
}
|
|
506
|
+
inst[fd.js] = nv;
|
|
507
|
+
inst._present.add(fd.js);
|
|
508
|
+
}
|
|
509
|
+
return inst;
|
|
510
|
+
}
|
|
511
|
+
static toObject(message, opts) {
|
|
512
|
+
const inst = (message instanceof this) ? message : this.fromObject(message);
|
|
513
|
+
return _toPlain(this, inst, opts || {}, this._ENUMS, this._REVENUMS);
|
|
514
|
+
}
|
|
515
|
+
toObject(opts) {
|
|
516
|
+
return this.constructor.toObject(this, opts);
|
|
517
|
+
}
|
|
518
|
+
toJSON() {
|
|
519
|
+
return this.constructor.toObject(this, { enums: 'String', longs: 'String', bytes: 'base64' });
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
Compat._FIELDS = fields;
|
|
523
|
+
return Compat;
|
|
524
|
+
}
|
|
525
|
+
module.exports = { _makeClass };
|