@trevrpc/trevrpc-js 0.1.0

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.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +202 -0
  3. package/bin/protoc-gen-trevrpc-js.js +17 -0
  4. package/examples/greeter/README.md +35 -0
  5. package/examples/greeter/client.js +172 -0
  6. package/examples/greeter/greeter.node.trevrpc.d.ts +53 -0
  7. package/examples/greeter/greeter.node.trevrpc.js +9 -0
  8. package/examples/greeter/greeter.proto +18 -0
  9. package/examples/greeter/greeter.trevrpc.d.ts +55 -0
  10. package/examples/greeter/greeter.trevrpc.js +103 -0
  11. package/examples/greeter/index.html +140 -0
  12. package/examples/greeter/static-server.js +168 -0
  13. package/package.json +89 -0
  14. package/src/advanced.d.ts +29 -0
  15. package/src/advanced.js +1 -0
  16. package/src/browser-channel.js +45 -0
  17. package/src/channel.js +375 -0
  18. package/src/client.js +1397 -0
  19. package/src/connect.js +6 -0
  20. package/src/connect.node.js +6 -0
  21. package/src/framing.js +618 -0
  22. package/src/generator.js +868 -0
  23. package/src/index.d.ts +577 -0
  24. package/src/index.js +3 -0
  25. package/src/index.node.d.ts +10 -0
  26. package/src/index.node.js +3 -0
  27. package/src/metadata.js +112 -0
  28. package/src/native-loader.js +94 -0
  29. package/src/node-advanced.d.ts +27 -0
  30. package/src/node-advanced.js +1 -0
  31. package/src/node-generated.d.ts +24 -0
  32. package/src/node-generated.js +126 -0
  33. package/src/node-index.d.ts +1 -0
  34. package/src/node-index.js +7 -0
  35. package/src/node.d.ts +298 -0
  36. package/src/node.js +1456 -0
  37. package/src/runtime.js +66 -0
  38. package/src/server-response.js +49 -0
  39. package/src/status.js +145 -0
  40. package/src/timer.js +29 -0
  41. package/src/webtransport.js +624 -0
  42. package/src/wire.js +403 -0
package/src/wire.js ADDED
@@ -0,0 +1,403 @@
1
+ import protobuf from "protobufjs";
2
+
3
+ export { protobuf };
4
+
5
+ export const WireVersion = 1;
6
+
7
+ export const RpcKind = Object.freeze({
8
+ Unary: 0,
9
+ ClientStreaming: 1,
10
+ ServerStreaming: 2,
11
+ BidirectionalStreaming: 3,
12
+ });
13
+
14
+ export const RpcStreamFrameKind = Object.freeze({
15
+ Message: 0,
16
+ Status: 1,
17
+ });
18
+
19
+ export const wireRoot = protobuf.Root.fromJSON({
20
+ nested: {
21
+ trevrpc: {
22
+ nested: {
23
+ RpcKind: {
24
+ values: {
25
+ Unary: 0,
26
+ ClientStreaming: 1,
27
+ ServerStreaming: 2,
28
+ BidirectionalStreaming: 3,
29
+ },
30
+ },
31
+ RpcStreamFrameKind: {
32
+ values: {
33
+ Message: 0,
34
+ Status: 1,
35
+ },
36
+ },
37
+ RpcRequest: {
38
+ fields: {
39
+ service: { type: "string", id: 1 },
40
+ method: { type: "string", id: 2 },
41
+ body: { type: "bytes", id: 3 },
42
+ metadata: { keyType: "string", type: "bytes", id: 4 },
43
+ kind: { type: "RpcKind", id: 5 },
44
+ version: { type: "uint32", id: 6 },
45
+ timeoutNanos: { type: "uint64", id: 7 },
46
+ },
47
+ },
48
+ RpcResponse: {
49
+ fields: {
50
+ status: { type: "uint32", id: 1 },
51
+ message: { type: "string", id: 2 },
52
+ body: { type: "bytes", id: 3 },
53
+ metadata: { keyType: "string", type: "bytes", id: 4 },
54
+ },
55
+ },
56
+ RpcStreamFrame: {
57
+ fields: {
58
+ kind: { type: "RpcStreamFrameKind", id: 1 },
59
+ status: { type: "uint32", id: 2 },
60
+ message: { type: "string", id: 3 },
61
+ body: { type: "bytes", id: 4 },
62
+ metadata: { keyType: "string", type: "bytes", id: 5 },
63
+ },
64
+ },
65
+ },
66
+ },
67
+ },
68
+ });
69
+
70
+ export const RpcRequest = wireRoot.lookupType("trevrpc.RpcRequest");
71
+ export const RpcResponse = wireRoot.lookupType("trevrpc.RpcResponse");
72
+ export const RpcStreamFrame = wireRoot.lookupType("trevrpc.RpcStreamFrame");
73
+
74
+ const MaxFieldNumber = 0x1fffffff;
75
+ const MaxUint32 = 0xffffffffn;
76
+ const MaxUint64 = 0xffffffffffffffffn;
77
+ const fatalUtf8Decoder = new TextDecoder("utf-8", { fatal: true });
78
+
79
+ /** Internal wire-validation failure used by production codec paths. */
80
+ export class WireValidationError extends Error {
81
+ constructor(reason, message) {
82
+ super(message);
83
+ this.name = "WireValidationError";
84
+ this.reason = reason;
85
+ }
86
+ }
87
+
88
+ /** Checks protobuf tags and known-field wire types before protobuf.js decoding. */
89
+ export function preflightWireMessage(messageType, body) {
90
+ scanMessage(messageType, byteView(body), 0);
91
+ }
92
+
93
+ /** Validates TrevRPC protocol enums and versions after protobuf decoding. */
94
+ export function validateWireMessage(messageType, message) {
95
+ if (messageType === RpcRequest) {
96
+ if (message.version !== WireVersion) {
97
+ throw new WireValidationError(
98
+ "unsupported_wire_version",
99
+ `unsupported wire version ${message.version}`,
100
+ );
101
+ }
102
+ if (!Object.values(RpcKind).includes(message.kind)) {
103
+ throw new WireValidationError("unsupported_rpc_kind", `unsupported RPC kind ${message.kind}`);
104
+ }
105
+ return;
106
+ }
107
+
108
+ if (
109
+ messageType === RpcStreamFrame &&
110
+ message.kind !== RpcStreamFrameKind.Message &&
111
+ message.kind !== RpcStreamFrameKind.Status
112
+ ) {
113
+ throw new WireValidationError(
114
+ "unsupported_frame_kind",
115
+ `unsupported stream frame kind ${message.kind}`,
116
+ );
117
+ }
118
+ }
119
+
120
+ /** Creates a protobuf root from a JSON namespace definition. */
121
+ export function createRoot(json) {
122
+ return protobuf.Root.fromJSON(json);
123
+ }
124
+
125
+ /** Creates a stream message frame carrying a protobuf body. */
126
+ export function messageFrame(body) {
127
+ return {
128
+ kind: RpcStreamFrameKind.Message,
129
+ status: 0,
130
+ message: "",
131
+ body,
132
+ metadata: {},
133
+ };
134
+ }
135
+
136
+ function scanMessage(messageType, bytes, depth) {
137
+ if (depth > 64) {
138
+ throw malformedWire("protobuf message nesting exceeded limit");
139
+ }
140
+
141
+ const cursor = { offset: 0 };
142
+ while (cursor.offset < bytes.byteLength) {
143
+ const tag = readVarint(bytes, cursor, "truncated protobuf field tag");
144
+ const fieldNumber = Number(tag >> 3n);
145
+ const wireType = Number(tag & 7n);
146
+ if (fieldNumber === 0 || fieldNumber > MaxFieldNumber) {
147
+ throw malformedWire("invalid protobuf field tag");
148
+ }
149
+
150
+ const field = messageType.fieldsById?.[fieldNumber];
151
+ if (field == null) {
152
+ skipUnknownField(bytes, cursor, fieldNumber, wireType, depth);
153
+ continue;
154
+ }
155
+
156
+ if (!acceptedWireTypes(field).includes(wireType)) {
157
+ throw malformedWire(`field ${field.name} used incompatible protobuf wire type ${wireType}`);
158
+ }
159
+ scanKnownField(field, bytes, cursor, wireType, depth);
160
+ }
161
+ }
162
+
163
+ function scanKnownField(field, bytes, cursor, wireType, depth) {
164
+ if (wireType === 0) {
165
+ const value = readVarint(bytes, cursor, `truncated protobuf field ${field.name}`);
166
+ if (isUint32Field(field) && value > MaxUint32) {
167
+ throw malformedWire(`protobuf field ${field.name} exceeded uint32`);
168
+ }
169
+ return;
170
+ }
171
+ if (wireType === 1) {
172
+ skipFixed(bytes, cursor, 8, field.name);
173
+ return;
174
+ }
175
+ if (wireType === 5) {
176
+ skipFixed(bytes, cursor, 4, field.name);
177
+ return;
178
+ }
179
+ if (wireType !== 2) {
180
+ throw malformedWire(`unsupported protobuf wire type ${wireType}`);
181
+ }
182
+
183
+ const value = readLengthDelimited(bytes, cursor, `truncated protobuf field ${field.name}`);
184
+ if (field.map) {
185
+ scanMapEntry(field, value, depth + 1);
186
+ } else if (field.resolvedType?.fieldsById != null) {
187
+ scanMessage(field.resolvedType, value, depth + 1);
188
+ } else if (field.type === "string") {
189
+ validateUtf8(value, field.name);
190
+ } else if (field.repeated && isPackableField(field)) {
191
+ scanPackedField(field, value);
192
+ }
193
+ }
194
+
195
+ function scanMapEntry(field, bytes, depth) {
196
+ if (depth > 64) {
197
+ throw malformedWire("protobuf message nesting exceeded limit");
198
+ }
199
+
200
+ const cursor = { offset: 0 };
201
+ while (cursor.offset < bytes.byteLength) {
202
+ const tag = readVarint(bytes, cursor, "truncated protobuf map entry tag");
203
+ const fieldNumber = Number(tag >> 3n);
204
+ const wireType = Number(tag & 7n);
205
+ if (fieldNumber === 0 || fieldNumber > MaxFieldNumber) {
206
+ throw malformedWire("invalid protobuf map entry tag");
207
+ }
208
+
209
+ if (fieldNumber === 1) {
210
+ scanSyntheticField(field.keyType, `${field.name}.key`, bytes, cursor, wireType, depth);
211
+ } else if (fieldNumber === 2) {
212
+ scanSyntheticField(field.type, `${field.name}.value`, bytes, cursor, wireType, depth);
213
+ } else {
214
+ skipUnknownField(bytes, cursor, fieldNumber, wireType, depth);
215
+ }
216
+ }
217
+ }
218
+
219
+ function scanSyntheticField(type, name, bytes, cursor, wireType, depth) {
220
+ const expected = scalarWireType(type);
221
+ if (wireType !== expected) {
222
+ throw malformedWire(`field ${name} used incompatible protobuf wire type ${wireType}`);
223
+ }
224
+ if (wireType === 0) {
225
+ const value = readVarint(bytes, cursor, `truncated protobuf field ${name}`);
226
+ if (isUint32Type(type) && value > MaxUint32) {
227
+ throw malformedWire(`protobuf field ${name} exceeded uint32`);
228
+ }
229
+ } else if (wireType === 1) {
230
+ skipFixed(bytes, cursor, 8, name);
231
+ } else if (wireType === 5) {
232
+ skipFixed(bytes, cursor, 4, name);
233
+ } else if (wireType === 2) {
234
+ const value = readLengthDelimited(bytes, cursor, `truncated protobuf field ${name}`);
235
+ if (type === "string") {
236
+ validateUtf8(value, name);
237
+ }
238
+ } else {
239
+ skipUnknownField(bytes, cursor, 0, wireType, depth);
240
+ }
241
+ }
242
+
243
+ function scanPackedField(field, bytes) {
244
+ const cursor = { offset: 0 };
245
+ const wireType = scalarWireType(field.type);
246
+ while (cursor.offset < bytes.byteLength) {
247
+ if (wireType === 0) {
248
+ const value = readVarint(bytes, cursor, `truncated packed protobuf field ${field.name}`);
249
+ if (isUint32Field(field) && value > MaxUint32) {
250
+ throw malformedWire(`protobuf field ${field.name} exceeded uint32`);
251
+ }
252
+ } else if (wireType === 1) {
253
+ skipFixed(bytes, cursor, 8, field.name);
254
+ } else if (wireType === 5) {
255
+ skipFixed(bytes, cursor, 4, field.name);
256
+ } else {
257
+ throw malformedWire(`protobuf field ${field.name} cannot be packed`);
258
+ }
259
+ }
260
+ }
261
+
262
+ function acceptedWireTypes(field) {
263
+ const expected = field.resolvedType?.fieldsById != null ? 2 : scalarWireType(field.type);
264
+ return field.repeated && isPackableField(field) ? [expected, 2] : [expected];
265
+ }
266
+
267
+ function scalarWireType(type) {
268
+ switch (type) {
269
+ case "double":
270
+ case "fixed64":
271
+ case "sfixed64":
272
+ return 1;
273
+ case "string":
274
+ case "bytes":
275
+ return 2;
276
+ case "float":
277
+ case "fixed32":
278
+ case "sfixed32":
279
+ return 5;
280
+ default:
281
+ return 0;
282
+ }
283
+ }
284
+
285
+ function isPackableField(field) {
286
+ return (
287
+ field.type !== "string" && field.type !== "bytes" && field.resolvedType?.fieldsById == null
288
+ );
289
+ }
290
+
291
+ function isUint32Field(field) {
292
+ return isUint32Type(field.type) || field.resolvedType?.values != null;
293
+ }
294
+
295
+ function isUint32Type(type) {
296
+ return type === "uint32" || type === "fixed32";
297
+ }
298
+
299
+ function readVarint(bytes, cursor, message) {
300
+ let value = 0n;
301
+ for (let index = 0; index < 10; index += 1) {
302
+ if (cursor.offset >= bytes.byteLength) {
303
+ throw malformedWire(message);
304
+ }
305
+ const byte = bytes[cursor.offset++];
306
+ if (index === 9 && byte > 1) {
307
+ throw malformedWire("protobuf varint exceeded 64 bits");
308
+ }
309
+ value |= BigInt(byte & 0x7f) << BigInt(index * 7);
310
+ if (byte < 0x80) {
311
+ if (value > MaxUint64) {
312
+ throw malformedWire("protobuf varint exceeded 64 bits");
313
+ }
314
+ return value;
315
+ }
316
+ }
317
+ throw malformedWire("protobuf varint exceeded 64 bits");
318
+ }
319
+
320
+ function readLengthDelimited(bytes, cursor, message) {
321
+ const length = readVarint(bytes, cursor, message);
322
+ if (length > BigInt(Number.MAX_SAFE_INTEGER)) {
323
+ throw malformedWire(message);
324
+ }
325
+ const end = cursor.offset + Number(length);
326
+ if (!Number.isSafeInteger(end) || end > bytes.byteLength) {
327
+ throw malformedWire(message);
328
+ }
329
+ const value = bytes.subarray(cursor.offset, end);
330
+ cursor.offset = end;
331
+ return value;
332
+ }
333
+
334
+ function skipUnknownField(bytes, cursor, fieldNumber, wireType, depth) {
335
+ switch (wireType) {
336
+ case 0:
337
+ readVarint(bytes, cursor, "truncated protobuf varint");
338
+ return;
339
+ case 1:
340
+ skipFixed(bytes, cursor, 8, "unknown");
341
+ return;
342
+ case 2:
343
+ readLengthDelimited(bytes, cursor, "truncated protobuf field");
344
+ return;
345
+ case 3:
346
+ skipGroup(bytes, cursor, fieldNumber, depth + 1);
347
+ return;
348
+ case 5:
349
+ skipFixed(bytes, cursor, 4, "unknown");
350
+ return;
351
+ default:
352
+ throw malformedWire(`unsupported protobuf wire type ${wireType}`);
353
+ }
354
+ }
355
+
356
+ function skipGroup(bytes, cursor, fieldNumber, depth) {
357
+ if (depth > 64) {
358
+ throw malformedWire("protobuf group nesting exceeded limit");
359
+ }
360
+ while (cursor.offset < bytes.byteLength) {
361
+ const tag = readVarint(bytes, cursor, "truncated protobuf group tag");
362
+ const nestedField = Number(tag >> 3n);
363
+ const wireType = Number(tag & 7n);
364
+ if (wireType === 4) {
365
+ if (nestedField !== fieldNumber) {
366
+ throw malformedWire("protobuf group end tag did not match start tag");
367
+ }
368
+ return;
369
+ }
370
+ skipUnknownField(bytes, cursor, nestedField, wireType, depth);
371
+ }
372
+ throw malformedWire("unterminated protobuf group");
373
+ }
374
+
375
+ function skipFixed(bytes, cursor, length, name) {
376
+ const end = cursor.offset + length;
377
+ if (end > bytes.byteLength) {
378
+ throw malformedWire(`truncated protobuf field ${name}`);
379
+ }
380
+ cursor.offset = end;
381
+ }
382
+
383
+ function validateUtf8(bytes, name) {
384
+ try {
385
+ fatalUtf8Decoder.decode(bytes);
386
+ } catch {
387
+ throw malformedWire(`protobuf field ${name} was not valid UTF-8`);
388
+ }
389
+ }
390
+
391
+ function malformedWire(message) {
392
+ return new WireValidationError("malformed_protobuf", message);
393
+ }
394
+
395
+ function byteView(value) {
396
+ if (value instanceof Uint8Array) {
397
+ return value;
398
+ }
399
+ if (ArrayBuffer.isView(value)) {
400
+ return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
401
+ }
402
+ return new Uint8Array(value ?? 0);
403
+ }