@replit/river 0.203.1 → 0.204.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 (49) hide show
  1. package/dist/{chunk-T4WWG42M.js → chunk-3V7VMJNA.js} +19 -16
  2. package/dist/chunk-3V7VMJNA.js.map +1 -0
  3. package/dist/{chunk-TMOCPK63.js → chunk-6YUDEFCS.js} +42 -8
  4. package/dist/chunk-6YUDEFCS.js.map +1 -0
  5. package/dist/chunk-LJCR3ADI.js +1955 -0
  6. package/dist/chunk-LJCR3ADI.js.map +1 -0
  7. package/dist/{chunk-2FHJVQBE.js → chunk-QSW7AWEP.js} +5 -11
  8. package/dist/chunk-QSW7AWEP.js.map +1 -0
  9. package/dist/{chunk-7MKTFUJO.js → chunk-YODW2ZMU.js} +4 -3
  10. package/dist/chunk-YODW2ZMU.js.map +1 -0
  11. package/dist/{client-5d2e41a3.d.ts → client-0c0a4a5e.d.ts} +1 -1
  12. package/dist/{connection-11a4af0f.d.ts → connection-7b62dfec.d.ts} +1 -1
  13. package/dist/{context-d6dd8a1a.d.ts → context-3cf1ed4e.d.ts} +8 -2
  14. package/dist/router/index.cjs +23 -12
  15. package/dist/router/index.cjs.map +1 -1
  16. package/dist/router/index.d.cts +7 -7
  17. package/dist/router/index.d.ts +7 -7
  18. package/dist/router/index.js +18 -1611
  19. package/dist/router/index.js.map +1 -1
  20. package/dist/{server-e46399f9.d.ts → server-a287de55.d.ts} +1 -1
  21. package/dist/{services-56cbea0d.d.ts → services-51980ecd.d.ts} +2 -2
  22. package/dist/testUtil/index.cjs +72 -32
  23. package/dist/testUtil/index.cjs.map +1 -1
  24. package/dist/testUtil/index.d.cts +4 -4
  25. package/dist/testUtil/index.d.ts +4 -4
  26. package/dist/testUtil/index.js +8 -6
  27. package/dist/testUtil/index.js.map +1 -1
  28. package/dist/transport/impls/ws/client.cjs +72 -40
  29. package/dist/transport/impls/ws/client.cjs.map +1 -1
  30. package/dist/transport/impls/ws/client.d.cts +3 -3
  31. package/dist/transport/impls/ws/client.d.ts +3 -3
  32. package/dist/transport/impls/ws/client.js +4 -4
  33. package/dist/transport/impls/ws/server.cjs +58 -28
  34. package/dist/transport/impls/ws/server.cjs.map +1 -1
  35. package/dist/transport/impls/ws/server.d.cts +3 -3
  36. package/dist/transport/impls/ws/server.d.ts +3 -3
  37. package/dist/transport/impls/ws/server.js +4 -4
  38. package/dist/transport/index.cjs +70 -31
  39. package/dist/transport/index.cjs.map +1 -1
  40. package/dist/transport/index.d.cts +3 -3
  41. package/dist/transport/index.d.ts +3 -3
  42. package/dist/transport/index.js +4 -4
  43. package/package.json +2 -2
  44. package/dist/chunk-2FHJVQBE.js.map +0 -1
  45. package/dist/chunk-7MKTFUJO.js.map +0 -1
  46. package/dist/chunk-AK7NTFAM.js +0 -331
  47. package/dist/chunk-AK7NTFAM.js.map +0 -1
  48. package/dist/chunk-T4WWG42M.js.map +0 -1
  49. package/dist/chunk-TMOCPK63.js.map +0 -1
@@ -0,0 +1,1955 @@
1
+ // router/services.ts
2
+ import { Type as Type2, Kind as Kind2 } from "@sinclair/typebox";
3
+
4
+ // router/errors.ts
5
+ import {
6
+ Kind,
7
+ Type
8
+ } from "@sinclair/typebox";
9
+ var UNCAUGHT_ERROR_CODE = "UNCAUGHT_ERROR";
10
+ var UNEXPECTED_DISCONNECT_CODE = "UNEXPECTED_DISCONNECT";
11
+ var INVALID_REQUEST_CODE = "INVALID_REQUEST";
12
+ var CANCEL_CODE = "CANCEL";
13
+ var ErrResultSchema = (t) => Type.Object({
14
+ ok: Type.Literal(false),
15
+ payload: t
16
+ });
17
+ var ReaderErrorSchema = Type.Union([
18
+ Type.Object({
19
+ code: Type.Literal(UNCAUGHT_ERROR_CODE),
20
+ message: Type.String()
21
+ }),
22
+ Type.Object({
23
+ code: Type.Literal(UNEXPECTED_DISCONNECT_CODE),
24
+ message: Type.String()
25
+ }),
26
+ Type.Object({
27
+ code: Type.Literal(INVALID_REQUEST_CODE),
28
+ message: Type.String()
29
+ }),
30
+ Type.Object({
31
+ code: Type.Literal(CANCEL_CODE),
32
+ message: Type.String()
33
+ })
34
+ ]);
35
+ function isUnion(schema) {
36
+ return schema[Kind] === "Union";
37
+ }
38
+ function flattenErrorType(errType) {
39
+ if (!isUnion(errType)) {
40
+ return errType;
41
+ }
42
+ const flattenedTypes = [];
43
+ function flatten(type) {
44
+ if (isUnion(type)) {
45
+ for (const t of type.anyOf) {
46
+ flatten(t);
47
+ }
48
+ } else {
49
+ flattenedTypes.push(type);
50
+ }
51
+ }
52
+ flatten(errType);
53
+ return Type.Union(flattenedTypes);
54
+ }
55
+
56
+ // router/services.ts
57
+ function serializeSchemaV1Compat(services, handshakeSchema) {
58
+ const serializedServiceObject = Object.entries(services).reduce((acc, [name, value]) => {
59
+ acc[name] = value.serializeV1Compat();
60
+ return acc;
61
+ }, {});
62
+ const schema = {
63
+ services: serializedServiceObject
64
+ };
65
+ if (handshakeSchema) {
66
+ schema.handshakeSchema = Type2.Strict(handshakeSchema);
67
+ }
68
+ return schema;
69
+ }
70
+ function serializeSchema(services, handshakeSchema) {
71
+ const serializedServiceObject = Object.entries(services).reduce((acc, [name, value]) => {
72
+ acc[name] = value.serialize();
73
+ return acc;
74
+ }, {});
75
+ const schema = {
76
+ services: serializedServiceObject
77
+ };
78
+ if (handshakeSchema) {
79
+ schema.handshakeSchema = Type2.Strict(handshakeSchema);
80
+ }
81
+ return schema;
82
+ }
83
+ var ServiceSchema = class _ServiceSchema {
84
+ /**
85
+ * Factory function for creating a fresh state.
86
+ */
87
+ initializeState;
88
+ /**
89
+ * The procedures for this service.
90
+ */
91
+ procedures;
92
+ /**
93
+ * @param config - The configuration for this service.
94
+ * @param procedures - The procedures for this service.
95
+ */
96
+ constructor(config, procedures) {
97
+ this.initializeState = config.initializeState;
98
+ this.procedures = procedures;
99
+ }
100
+ /**
101
+ * Creates a {@link ServiceScaffold}, which can be used to define procedures
102
+ * that can then be merged into a {@link ServiceSchema}, via the scaffold's
103
+ * `finalize` method.
104
+ *
105
+ * There are two patterns that work well with this method. The first is using
106
+ * it to separate the definition of procedures from the definition of the
107
+ * service's configuration:
108
+ * ```ts
109
+ * const MyServiceScaffold = ServiceSchema.scaffold({
110
+ * initializeState: () => ({ count: 0 }),
111
+ * });
112
+ *
113
+ * const incrementProcedures = MyServiceScaffold.procedures({
114
+ * increment: Procedure.rpc({
115
+ * requestInit: Type.Object({ amount: Type.Number() }),
116
+ * responseData: Type.Object({ current: Type.Number() }),
117
+ * async handler(ctx, init) {
118
+ * ctx.state.count += init.amount;
119
+ * return Ok({ current: ctx.state.count });
120
+ * }
121
+ * }),
122
+ * })
123
+ *
124
+ * const MyService = MyServiceScaffold.finalize({
125
+ * ...incrementProcedures,
126
+ * // you can also directly define procedures here
127
+ * });
128
+ * ```
129
+ * This might be really handy if you have a very large service and you're
130
+ * wanting to split it over multiple files. You can define the scaffold
131
+ * in one file, and then import that scaffold in other files where you
132
+ * define procedures - and then finally import the scaffolds and your
133
+ * procedure objects in a final file where you finalize the scaffold into
134
+ * a service schema.
135
+ *
136
+ * The other way is to use it like in a builder pattern:
137
+ * ```ts
138
+ * const MyService = ServiceSchema
139
+ * .scaffold({ initializeState: () => ({ count: 0 }) })
140
+ * .finalize({
141
+ * increment: Procedure.rpc({
142
+ * requestInit: Type.Object({ amount: Type.Number() }),
143
+ * responseData: Type.Object({ current: Type.Number() }),
144
+ * async handler(ctx, init) {
145
+ * ctx.state.count += init.amount;
146
+ * return Ok({ current: ctx.state.count });
147
+ * }
148
+ * }),
149
+ * })
150
+ * ```
151
+ * Depending on your preferences, this may be a more appealing way to define
152
+ * a schema versus using the {@link ServiceSchema.define} method.
153
+ */
154
+ static scaffold(config) {
155
+ return new ServiceScaffold(config);
156
+ }
157
+ // actual implementation
158
+ static define(configOrProcedures, maybeProcedures) {
159
+ let config;
160
+ let procedures;
161
+ if ("initializeState" in configOrProcedures && typeof configOrProcedures.initializeState === "function") {
162
+ if (!maybeProcedures) {
163
+ throw new Error("Expected procedures to be defined");
164
+ }
165
+ config = configOrProcedures;
166
+ procedures = maybeProcedures;
167
+ } else {
168
+ config = { initializeState: () => ({}) };
169
+ procedures = configOrProcedures;
170
+ }
171
+ return new _ServiceSchema(config, procedures);
172
+ }
173
+ /**
174
+ * Serializes this schema's procedures into a plain object that is JSON compatible.
175
+ */
176
+ serialize() {
177
+ return {
178
+ procedures: Object.fromEntries(
179
+ Object.entries(this.procedures).map(([procName, procDef]) => [
180
+ procName,
181
+ {
182
+ init: Type2.Strict(procDef.requestInit),
183
+ output: Type2.Strict(procDef.responseData),
184
+ errors: getSerializedProcErrors(procDef),
185
+ // Only add `description` field if the type declares it.
186
+ ..."description" in procDef ? { description: procDef.description } : {},
187
+ type: procDef.type,
188
+ // Only add the `input` field if the type declares it.
189
+ ..."requestData" in procDef ? {
190
+ input: Type2.Strict(procDef.requestData)
191
+ } : {}
192
+ }
193
+ ])
194
+ )
195
+ };
196
+ }
197
+ // TODO remove once clients migrate to v2
198
+ /**
199
+ * Same as {@link ServiceSchema.serialize}, but with a format that is compatible with
200
+ * protocol v1. This is useful to be able to continue to generate schemas for older
201
+ * clients as they are still supported.
202
+ */
203
+ serializeV1Compat() {
204
+ return {
205
+ procedures: Object.fromEntries(
206
+ Object.entries(this.procedures).map(
207
+ ([procName, procDef]) => {
208
+ if (procDef.type === "rpc" || procDef.type === "subscription") {
209
+ return [
210
+ procName,
211
+ {
212
+ // BACKWARDS COMPAT: map init to input for protocolv1
213
+ // this is the only change needed to make it compatible.
214
+ input: Type2.Strict(procDef.requestInit),
215
+ output: Type2.Strict(procDef.responseData),
216
+ errors: getSerializedProcErrors(procDef),
217
+ // Only add `description` field if the type declares it.
218
+ ..."description" in procDef ? { description: procDef.description } : {},
219
+ type: procDef.type
220
+ }
221
+ ];
222
+ }
223
+ return [
224
+ procName,
225
+ {
226
+ init: Type2.Strict(procDef.requestInit),
227
+ output: Type2.Strict(procDef.responseData),
228
+ errors: getSerializedProcErrors(procDef),
229
+ // Only add `description` field if the type declares it.
230
+ ..."description" in procDef ? { description: procDef.description } : {},
231
+ type: procDef.type,
232
+ input: Type2.Strict(procDef.requestData)
233
+ }
234
+ ];
235
+ }
236
+ )
237
+ )
238
+ };
239
+ }
240
+ /**
241
+ * Instantiates this schema into a {@link Service} object.
242
+ *
243
+ * You probably don't need this, usually the River server will handle this
244
+ * for you.
245
+ */
246
+ instantiate(extendedContext) {
247
+ const state = this.initializeState(extendedContext);
248
+ const dispose = async () => {
249
+ await state[Symbol.asyncDispose]?.();
250
+ state[Symbol.dispose]?.();
251
+ };
252
+ return Object.freeze({
253
+ state,
254
+ procedures: this.procedures,
255
+ [Symbol.asyncDispose]: dispose
256
+ });
257
+ }
258
+ };
259
+ function getSerializedProcErrors(procDef) {
260
+ if (!("responseError" in procDef) || procDef.responseError[Kind2] === "Never") {
261
+ return Type2.Strict(ReaderErrorSchema);
262
+ }
263
+ const withProtocolErrors = flattenErrorType(
264
+ Type2.Union([procDef.responseError, ReaderErrorSchema])
265
+ );
266
+ return Type2.Strict(withProtocolErrors);
267
+ }
268
+ var ServiceScaffold = class {
269
+ /**
270
+ * The configuration for this service.
271
+ */
272
+ config;
273
+ /**
274
+ * @param config - The configuration for this service.
275
+ */
276
+ constructor(config) {
277
+ this.config = config;
278
+ }
279
+ /**
280
+ * Define procedures for this service. Use the {@link Procedure} constructors
281
+ * to create them. This returns the procedures object, which can then be
282
+ * passed to {@link ServiceSchema.finalize} to create a {@link ServiceSchema}.
283
+ *
284
+ * @example
285
+ * ```
286
+ * const myProcedures = MyServiceScaffold.procedures({
287
+ * myRPC: Procedure.rpc({
288
+ * // ...
289
+ * }),
290
+ * });
291
+ *
292
+ * const MyService = MyServiceScaffold.finalize({
293
+ * ...myProcedures,
294
+ * });
295
+ * ```
296
+ *
297
+ * @param procedures - The procedures for this service.
298
+ */
299
+ procedures(procedures) {
300
+ return procedures;
301
+ }
302
+ /**
303
+ * Finalizes the scaffold into a {@link ServiceSchema}. This is where you
304
+ * provide the service's procedures and get a {@link ServiceSchema} in return.
305
+ *
306
+ * You can directly define procedures here, or you can define them separately
307
+ * with the {@link ServiceScaffold.procedures} method, and then pass them here.
308
+ *
309
+ * @example
310
+ * ```
311
+ * const MyService = MyServiceScaffold.finalize({
312
+ * myRPC: Procedure.rpc({
313
+ * // ...
314
+ * }),
315
+ * // e.g. from the procedures method
316
+ * ...myOtherProcedures,
317
+ * });
318
+ * ```
319
+ */
320
+ finalize(procedures) {
321
+ return ServiceSchema.define(this.config, procedures);
322
+ }
323
+ };
324
+
325
+ // router/procedures.ts
326
+ import { Type as Type3 } from "@sinclair/typebox";
327
+ function rpc({
328
+ requestInit,
329
+ responseData,
330
+ responseError = Type3.Never(),
331
+ description,
332
+ handler
333
+ }) {
334
+ return {
335
+ ...description ? { description } : {},
336
+ type: "rpc",
337
+ requestInit,
338
+ responseData,
339
+ responseError,
340
+ handler
341
+ };
342
+ }
343
+ function upload({
344
+ requestInit,
345
+ requestData,
346
+ responseData,
347
+ responseError = Type3.Never(),
348
+ description,
349
+ handler
350
+ }) {
351
+ return {
352
+ type: "upload",
353
+ ...description ? { description } : {},
354
+ requestInit,
355
+ requestData,
356
+ responseData,
357
+ responseError,
358
+ handler
359
+ };
360
+ }
361
+ function subscription({
362
+ requestInit,
363
+ responseData,
364
+ responseError = Type3.Never(),
365
+ description,
366
+ handler
367
+ }) {
368
+ return {
369
+ type: "subscription",
370
+ ...description ? { description } : {},
371
+ requestInit,
372
+ responseData,
373
+ responseError,
374
+ handler
375
+ };
376
+ }
377
+ function stream({
378
+ requestInit,
379
+ requestData,
380
+ responseData,
381
+ responseError = Type3.Never(),
382
+ description,
383
+ handler
384
+ }) {
385
+ return {
386
+ type: "stream",
387
+ ...description ? { description } : {},
388
+ requestInit,
389
+ requestData,
390
+ responseData,
391
+ responseError,
392
+ handler
393
+ };
394
+ }
395
+ var Procedure = {
396
+ rpc,
397
+ upload,
398
+ subscription,
399
+ stream
400
+ };
401
+
402
+ // transport/message.ts
403
+ import { Type as Type4 } from "@sinclair/typebox";
404
+
405
+ // transport/id.ts
406
+ import { customAlphabet } from "nanoid";
407
+ var alphabet = customAlphabet(
408
+ "1234567890abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVXYZ"
409
+ );
410
+ var generateId = () => alphabet(12);
411
+
412
+ // transport/message.ts
413
+ var TransportMessageSchema = (t) => Type4.Object({
414
+ id: Type4.String(),
415
+ from: Type4.String(),
416
+ to: Type4.String(),
417
+ seq: Type4.Integer(),
418
+ ack: Type4.Integer(),
419
+ serviceName: Type4.Optional(Type4.String()),
420
+ procedureName: Type4.Optional(Type4.String()),
421
+ streamId: Type4.String(),
422
+ controlFlags: Type4.Integer(),
423
+ tracing: Type4.Optional(
424
+ Type4.Object({
425
+ traceparent: Type4.String(),
426
+ tracestate: Type4.String()
427
+ })
428
+ ),
429
+ payload: t
430
+ });
431
+ var ControlMessageAckSchema = Type4.Object({
432
+ type: Type4.Literal("ACK")
433
+ });
434
+ var ControlMessageCloseSchema = Type4.Object({
435
+ type: Type4.Literal("CLOSE")
436
+ });
437
+ var currentProtocolVersion = "v2.0";
438
+ var acceptedProtocolVersions = ["v1.1", currentProtocolVersion];
439
+ function isAcceptedProtocolVersion(version2) {
440
+ return acceptedProtocolVersions.includes(version2);
441
+ }
442
+ var ControlMessageHandshakeRequestSchema = Type4.Object({
443
+ type: Type4.Literal("HANDSHAKE_REQ"),
444
+ protocolVersion: Type4.String(),
445
+ sessionId: Type4.String(),
446
+ /**
447
+ * Specifies what the server's expected session state (from the pov of the client). This can be
448
+ * used by the server to know whether this is a new or a reestablished connection, and whether it
449
+ * is compatible with what it already has.
450
+ */
451
+ expectedSessionState: Type4.Object({
452
+ // what the client expects the server to send next
453
+ nextExpectedSeq: Type4.Integer(),
454
+ nextSentSeq: Type4.Integer()
455
+ }),
456
+ metadata: Type4.Optional(Type4.Unknown())
457
+ });
458
+ var HandshakeErrorRetriableResponseCodes = Type4.Union([
459
+ Type4.Literal("SESSION_STATE_MISMATCH")
460
+ ]);
461
+ var HandshakeErrorCustomHandlerFatalResponseCodes = Type4.Union([
462
+ // The custom validation handler rejected the handler because the client is unsupported.
463
+ Type4.Literal("REJECTED_UNSUPPORTED_CLIENT"),
464
+ // The custom validation handler rejected the handshake.
465
+ Type4.Literal("REJECTED_BY_CUSTOM_HANDLER")
466
+ ]);
467
+ var HandshakeErrorFatalResponseCodes = Type4.Union([
468
+ HandshakeErrorCustomHandlerFatalResponseCodes,
469
+ // The ciient sent a handshake that doesn't comply with the extended handshake metadata.
470
+ Type4.Literal("MALFORMED_HANDSHAKE_META"),
471
+ // The ciient sent a handshake that doesn't comply with ControlMessageHandshakeRequestSchema.
472
+ Type4.Literal("MALFORMED_HANDSHAKE"),
473
+ // The client's protocol version does not match the server's.
474
+ Type4.Literal("PROTOCOL_VERSION_MISMATCH")
475
+ ]);
476
+ var HandshakeErrorResponseCodes = Type4.Union([
477
+ HandshakeErrorRetriableResponseCodes,
478
+ HandshakeErrorFatalResponseCodes
479
+ ]);
480
+ var ControlMessageHandshakeResponseSchema = Type4.Object({
481
+ type: Type4.Literal("HANDSHAKE_RESP"),
482
+ status: Type4.Union([
483
+ Type4.Object({
484
+ ok: Type4.Literal(true),
485
+ sessionId: Type4.String()
486
+ }),
487
+ Type4.Object({
488
+ ok: Type4.Literal(false),
489
+ reason: Type4.String(),
490
+ code: HandshakeErrorResponseCodes
491
+ })
492
+ ])
493
+ });
494
+ var ControlMessagePayloadSchema = Type4.Union([
495
+ ControlMessageCloseSchema,
496
+ ControlMessageAckSchema,
497
+ ControlMessageHandshakeRequestSchema,
498
+ ControlMessageHandshakeResponseSchema
499
+ ]);
500
+ var OpaqueTransportMessageSchema = TransportMessageSchema(
501
+ Type4.Unknown()
502
+ );
503
+ function handshakeRequestMessage({
504
+ from,
505
+ to,
506
+ sessionId,
507
+ expectedSessionState,
508
+ metadata,
509
+ tracing
510
+ }) {
511
+ return {
512
+ id: generateId(),
513
+ from,
514
+ to,
515
+ seq: 0,
516
+ ack: 0,
517
+ streamId: generateId(),
518
+ controlFlags: 0,
519
+ tracing,
520
+ payload: {
521
+ type: "HANDSHAKE_REQ",
522
+ protocolVersion: currentProtocolVersion,
523
+ sessionId,
524
+ expectedSessionState,
525
+ metadata
526
+ }
527
+ };
528
+ }
529
+ function handshakeResponseMessage({
530
+ from,
531
+ to,
532
+ status
533
+ }) {
534
+ return {
535
+ id: generateId(),
536
+ from,
537
+ to,
538
+ seq: 0,
539
+ ack: 0,
540
+ streamId: generateId(),
541
+ controlFlags: 0,
542
+ payload: {
543
+ type: "HANDSHAKE_RESP",
544
+ status
545
+ }
546
+ };
547
+ }
548
+ function closeStreamMessage(streamId) {
549
+ return {
550
+ streamId,
551
+ controlFlags: 8 /* StreamClosedBit */,
552
+ payload: {
553
+ type: "CLOSE"
554
+ }
555
+ };
556
+ }
557
+ function cancelMessage(streamId, payload) {
558
+ return {
559
+ streamId,
560
+ controlFlags: 4 /* StreamCancelBit */,
561
+ payload
562
+ };
563
+ }
564
+ function isAck(controlFlag) {
565
+ return (controlFlag & 1 /* AckBit */) === 1 /* AckBit */;
566
+ }
567
+ function isStreamOpen(controlFlag) {
568
+ return (
569
+ /* eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison */
570
+ (controlFlag & 2 /* StreamOpenBit */) === 2 /* StreamOpenBit */
571
+ );
572
+ }
573
+ function isStreamClose(controlFlag) {
574
+ return (
575
+ /* eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison */
576
+ (controlFlag & 8 /* StreamClosedBit */) === 8 /* StreamClosedBit */
577
+ );
578
+ }
579
+ function isStreamCancel(controlFlag) {
580
+ return (
581
+ /* eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison */
582
+ (controlFlag & 4 /* StreamCancelBit */) === 4 /* StreamCancelBit */
583
+ );
584
+ }
585
+
586
+ // router/result.ts
587
+ import { Type as Type5 } from "@sinclair/typebox";
588
+ var AnyResultSchema = Type5.Union([
589
+ Type5.Object({
590
+ ok: Type5.Literal(false),
591
+ payload: Type5.Object({
592
+ code: Type5.String(),
593
+ message: Type5.String(),
594
+ extras: Type5.Optional(Type5.Unknown())
595
+ })
596
+ }),
597
+ Type5.Object({
598
+ ok: Type5.Literal(true),
599
+ payload: Type5.Unknown()
600
+ })
601
+ ]);
602
+ function Ok(payload) {
603
+ return {
604
+ ok: true,
605
+ payload
606
+ };
607
+ }
608
+ function Err(error) {
609
+ return {
610
+ ok: false,
611
+ payload: error
612
+ };
613
+ }
614
+ function unwrapOrThrow(result) {
615
+ if (result.ok) {
616
+ return result.payload;
617
+ }
618
+ throw new Error(
619
+ `Cannot non-ok result, got: ${result.payload.code} - ${result.payload.message}`
620
+ );
621
+ }
622
+
623
+ // tracing/index.ts
624
+ import {
625
+ SpanKind,
626
+ SpanStatusCode,
627
+ context,
628
+ propagation,
629
+ trace
630
+ } from "@opentelemetry/api";
631
+ function getPropagationContext(ctx) {
632
+ const tracing = {
633
+ traceparent: "",
634
+ tracestate: ""
635
+ };
636
+ propagation.inject(ctx, tracing);
637
+ return tracing;
638
+ }
639
+ function createSessionTelemetryInfo(tracer, sessionId, to, from, propagationCtx) {
640
+ const parentCtx = propagationCtx ? propagation.extract(context.active(), propagationCtx) : context.active();
641
+ const span = tracer.startSpan(
642
+ `river.session.${sessionId}`,
643
+ {
644
+ attributes: {
645
+ component: "river",
646
+ "river.session.id": sessionId,
647
+ "river.session.to": to,
648
+ "river.session.from": from
649
+ }
650
+ },
651
+ parentCtx
652
+ );
653
+ const ctx = trace.setSpan(parentCtx, span);
654
+ return { span, ctx };
655
+ }
656
+ function createConnectionTelemetryInfo(tracer, connection, info) {
657
+ const span = tracer.startSpan(
658
+ `connection ${connection.id}`,
659
+ {
660
+ attributes: {
661
+ component: "river",
662
+ "river.connection.id": connection.id
663
+ },
664
+ links: [{ context: info.span.spanContext() }]
665
+ },
666
+ info.ctx
667
+ );
668
+ const ctx = trace.setSpan(info.ctx, span);
669
+ return { span, ctx };
670
+ }
671
+ function createProcTelemetryInfo(tracer, session, kind, serviceName, procedureName, streamId) {
672
+ const baseCtx = context.active();
673
+ const span = tracer.startSpan(
674
+ `river.client.${serviceName}.${procedureName}`,
675
+ {
676
+ attributes: {
677
+ component: "river",
678
+ "river.method.kind": kind,
679
+ "river.method.service": serviceName,
680
+ "river.method.name": procedureName,
681
+ "river.streamId": streamId,
682
+ "span.kind": "client"
683
+ },
684
+ links: [{ context: session.telemetry.span.spanContext() }],
685
+ kind: SpanKind.CLIENT
686
+ },
687
+ baseCtx
688
+ );
689
+ const ctx = trace.setSpan(baseCtx, span);
690
+ const metadata = {
691
+ ...session.loggingMetadata,
692
+ transportMessage: {
693
+ procedureName,
694
+ serviceName
695
+ }
696
+ };
697
+ if (span.isRecording()) {
698
+ metadata.telemetry = {
699
+ traceId: span.spanContext().traceId,
700
+ spanId: span.spanContext().spanId
701
+ };
702
+ }
703
+ session.log?.info(`invoked ${serviceName}.${procedureName}`, metadata);
704
+ return { span, ctx };
705
+ }
706
+ function createHandlerSpan(tracer, session, kind, serviceName, procedureName, streamId, tracing, fn) {
707
+ const ctx = tracing ? propagation.extract(context.active(), tracing) : context.active();
708
+ return tracer.startActiveSpan(
709
+ `river.server.${serviceName}.${procedureName}`,
710
+ {
711
+ attributes: {
712
+ component: "river",
713
+ "river.method.kind": kind,
714
+ "river.method.service": serviceName,
715
+ "river.method.name": procedureName,
716
+ "river.streamId": streamId,
717
+ "span.kind": "server"
718
+ },
719
+ links: [{ context: session.telemetry.span.spanContext() }],
720
+ kind: SpanKind.SERVER
721
+ },
722
+ ctx,
723
+ fn
724
+ );
725
+ }
726
+ function recordRiverError(span, error) {
727
+ span.setStatus({
728
+ code: SpanStatusCode.ERROR,
729
+ message: error.message
730
+ });
731
+ span.setAttributes({
732
+ "river.error_code": error.code,
733
+ "river.error_message": error.message
734
+ });
735
+ }
736
+ function getTracer() {
737
+ return trace.getTracer("river", version);
738
+ }
739
+
740
+ // router/streams.ts
741
+ var ReadableBrokenError = {
742
+ code: "READABLE_BROKEN",
743
+ message: "Readable was broken before it is fully consumed"
744
+ };
745
+ function createPromiseWithResolvers() {
746
+ let resolve;
747
+ let reject;
748
+ const promise = new Promise((res, rej) => {
749
+ resolve = res;
750
+ reject = rej;
751
+ });
752
+ return {
753
+ promise,
754
+ // @ts-expect-error promise callbacks are sync
755
+ resolve,
756
+ // @ts-expect-error promise callbacks are sync
757
+ reject
758
+ };
759
+ }
760
+ var ReadableImpl = class {
761
+ /**
762
+ * Whether the {@link Readable} is closed.
763
+ *
764
+ * Closed {@link Readable}s are done receiving values, but that doesn't affect
765
+ * any other aspect of the {@link Readable} such as it's consumability.
766
+ */
767
+ closed = false;
768
+ /**
769
+ * Whether the {@link Readable} is locked.
770
+ *
771
+ * @see {@link Readable}'s typedoc to understand locking
772
+ */
773
+ locked = false;
774
+ /**
775
+ * Whether {@link break} was called.
776
+ *
777
+ * @see {@link break} for more information
778
+ */
779
+ broken = false;
780
+ /**
781
+ * This flag allows us to avoid emitting a {@link ReadableBrokenError} after {@link break} was called
782
+ * in cases where the {@link queue} is fully consumed and {@link ReadableImpl} is {@link closed}. This is just an
783
+ * ergonomic feature to avoid emitting an error in our iteration when we don't have to.
784
+ */
785
+ brokenWithValuesLeftToRead = false;
786
+ /**
787
+ * A list of values that have been pushed to the {@link ReadableImpl} but not yet emitted to the user.
788
+ */
789
+ queue = [];
790
+ /**
791
+ * Used by methods in the class to signal to the iterator that it
792
+ * should check for the next value.
793
+ */
794
+ next = null;
795
+ [Symbol.asyncIterator]() {
796
+ if (this.locked) {
797
+ throw new TypeError("Readable is already locked");
798
+ }
799
+ this.locked = true;
800
+ let didSignalBreak = false;
801
+ return {
802
+ next: async () => {
803
+ if (didSignalBreak) {
804
+ return {
805
+ done: true,
806
+ value: void 0
807
+ };
808
+ }
809
+ while (this.queue.length === 0) {
810
+ if (this.closed && !this.brokenWithValuesLeftToRead) {
811
+ return {
812
+ done: true,
813
+ value: void 0
814
+ };
815
+ }
816
+ if (this.broken) {
817
+ didSignalBreak = true;
818
+ return {
819
+ done: false,
820
+ value: Err(ReadableBrokenError)
821
+ };
822
+ }
823
+ if (!this.next) {
824
+ this.next = createPromiseWithResolvers();
825
+ }
826
+ await this.next.promise;
827
+ this.next = null;
828
+ }
829
+ const value = this.queue.shift();
830
+ return { done: false, value };
831
+ },
832
+ return: () => {
833
+ this.break();
834
+ return { done: true, value: void 0 };
835
+ }
836
+ };
837
+ }
838
+ async collect() {
839
+ const array = [];
840
+ for await (const value of this) {
841
+ array.push(value);
842
+ }
843
+ return array;
844
+ }
845
+ break() {
846
+ if (this.broken) {
847
+ return;
848
+ }
849
+ this.locked = true;
850
+ this.broken = true;
851
+ this.brokenWithValuesLeftToRead = this.queue.length > 0;
852
+ this.queue.length = 0;
853
+ this.next?.resolve();
854
+ }
855
+ isReadable() {
856
+ return !this.locked && !this.broken;
857
+ }
858
+ /**
859
+ * @internal meant for use within river, not exposed as a public API
860
+ *
861
+ * Pushes a value to be read.
862
+ */
863
+ _pushValue(value) {
864
+ if (this.broken) {
865
+ return;
866
+ }
867
+ if (this.closed) {
868
+ throw new Error("Cannot push to closed Readable");
869
+ }
870
+ this.queue.push(value);
871
+ this.next?.resolve();
872
+ }
873
+ /**
874
+ * @internal meant for use within river, not exposed as a public API
875
+ *
876
+ * Triggers the close of the {@link Readable}. Make sure to push all remaining
877
+ * values before calling this method.
878
+ */
879
+ _triggerClose() {
880
+ if (this.closed) {
881
+ throw new Error("Unexpected closing multiple times");
882
+ }
883
+ this.closed = true;
884
+ this.next?.resolve();
885
+ }
886
+ /**
887
+ * @internal meant for use within river, not exposed as a public API
888
+ */
889
+ _hasValuesInQueue() {
890
+ return this.queue.length > 0;
891
+ }
892
+ /**
893
+ * @internal meant for use within river, not exposed as a public API
894
+ */
895
+ isClosed() {
896
+ return this.closed;
897
+ }
898
+ };
899
+ var WritableImpl = class {
900
+ /**
901
+ * Passed via constructor to pass on calls to {@link write}
902
+ */
903
+ writeCb;
904
+ /**
905
+ * Passed via constructor to pass on calls to {@link close}
906
+ */
907
+ closeCb;
908
+ /**
909
+ * Whether {@link close} was called, and {@link Writable} is not writable anymore.
910
+ */
911
+ closed = false;
912
+ constructor(callbacks) {
913
+ this.writeCb = callbacks.writeCb;
914
+ this.closeCb = callbacks.closeCb;
915
+ }
916
+ write(value) {
917
+ if (this.closed) {
918
+ throw new Error("Cannot write to closed Writable");
919
+ }
920
+ this.writeCb(value);
921
+ }
922
+ isWritable() {
923
+ return !this.closed;
924
+ }
925
+ close() {
926
+ if (this.closed) {
927
+ return;
928
+ }
929
+ this.closed = true;
930
+ this.writeCb = () => void 0;
931
+ this.closeCb();
932
+ this.closeCb = () => void 0;
933
+ }
934
+ /**
935
+ * @internal meant for use within river, not exposed as a public API
936
+ */
937
+ isClosed() {
938
+ return this.closed;
939
+ }
940
+ };
941
+
942
+ // router/client.ts
943
+ import { Value } from "@sinclair/typebox/value";
944
+ var ReaderErrResultSchema = ErrResultSchema(ReaderErrorSchema);
945
+ var noop = () => {
946
+ };
947
+ function _createRecursiveProxy(callback, path) {
948
+ const proxy = new Proxy(noop, {
949
+ // property access, recurse and add field to path
950
+ get(_obj, key) {
951
+ if (typeof key !== "string")
952
+ return void 0;
953
+ return _createRecursiveProxy(callback, [...path, key]);
954
+ },
955
+ // hit the end, let's invoke the handler
956
+ apply(_target, _this, args) {
957
+ return callback({
958
+ path,
959
+ args
960
+ });
961
+ }
962
+ });
963
+ return proxy;
964
+ }
965
+ var defaultClientOptions = {
966
+ connectOnInvoke: true,
967
+ eagerlyConnect: true
968
+ };
969
+ function createClient(transport, serverId, providedClientOptions = {}) {
970
+ if (providedClientOptions.handshakeOptions) {
971
+ transport.extendHandshake(providedClientOptions.handshakeOptions);
972
+ }
973
+ const clientOptions = { ...defaultClientOptions, ...providedClientOptions };
974
+ if (clientOptions.eagerlyConnect) {
975
+ transport.connect(serverId);
976
+ }
977
+ return _createRecursiveProxy((opts) => {
978
+ const [serviceName, procName, procMethod] = [...opts.path];
979
+ if (!(serviceName && procName && procMethod)) {
980
+ throw new Error(
981
+ "invalid river call, ensure the service and procedure you are calling exists"
982
+ );
983
+ }
984
+ const [init, callOptions] = opts.args;
985
+ if (clientOptions.connectOnInvoke && !transport.sessions.has(serverId)) {
986
+ transport.connect(serverId);
987
+ }
988
+ if (procMethod !== "rpc" && procMethod !== "subscribe" && procMethod !== "stream" && procMethod !== "upload") {
989
+ throw new Error(
990
+ `invalid river call, unknown procedure type ${procMethod}`
991
+ );
992
+ }
993
+ return handleProc(
994
+ procMethod === "subscribe" ? "subscription" : procMethod,
995
+ transport,
996
+ serverId,
997
+ init,
998
+ serviceName,
999
+ procName,
1000
+ callOptions ? callOptions.signal : void 0
1001
+ );
1002
+ }, []);
1003
+ }
1004
+ function handleProc(procType, transport, serverId, init, serviceName, procedureName, abortSignal) {
1005
+ const session = transport.sessions.get(serverId) ?? transport.createUnconnectedSession(serverId);
1006
+ const sessionScopedSend = transport.getSessionBoundSendFn(
1007
+ serverId,
1008
+ session.id
1009
+ );
1010
+ const procClosesWithInit = procType === "rpc" || procType === "subscription";
1011
+ const streamId = generateId();
1012
+ const { span, ctx } = createProcTelemetryInfo(
1013
+ transport.tracer,
1014
+ session,
1015
+ procType,
1016
+ serviceName,
1017
+ procedureName,
1018
+ streamId
1019
+ );
1020
+ let cleanClose = true;
1021
+ const reqWritable = new WritableImpl({
1022
+ writeCb: (rawIn) => {
1023
+ sessionScopedSend({
1024
+ streamId,
1025
+ payload: rawIn,
1026
+ controlFlags: 0
1027
+ });
1028
+ },
1029
+ // close callback
1030
+ closeCb: () => {
1031
+ span.addEvent("reqWritable closed");
1032
+ if (!procClosesWithInit && cleanClose) {
1033
+ sessionScopedSend(closeStreamMessage(streamId));
1034
+ }
1035
+ if (resReadable.isClosed()) {
1036
+ cleanup();
1037
+ }
1038
+ }
1039
+ });
1040
+ const resReadable = new ReadableImpl();
1041
+ const closeReadable = () => {
1042
+ resReadable._triggerClose();
1043
+ span.addEvent("resReadable closed");
1044
+ if (reqWritable.isClosed()) {
1045
+ cleanup();
1046
+ }
1047
+ };
1048
+ function cleanup() {
1049
+ transport.removeEventListener("message", onMessage);
1050
+ transport.removeEventListener("sessionStatus", onSessionStatus);
1051
+ abortSignal?.removeEventListener("abort", onClientCancel);
1052
+ span.end();
1053
+ }
1054
+ function onClientCancel() {
1055
+ if (resReadable.isClosed() && reqWritable.isClosed()) {
1056
+ return;
1057
+ }
1058
+ span.addEvent("sending cancel");
1059
+ cleanClose = false;
1060
+ if (!resReadable.isClosed()) {
1061
+ resReadable._pushValue(
1062
+ Err({
1063
+ code: CANCEL_CODE,
1064
+ message: "cancelled by client"
1065
+ })
1066
+ );
1067
+ closeReadable();
1068
+ }
1069
+ reqWritable.close();
1070
+ sessionScopedSend(
1071
+ cancelMessage(
1072
+ streamId,
1073
+ Err({
1074
+ code: CANCEL_CODE,
1075
+ message: "cancelled by client"
1076
+ })
1077
+ )
1078
+ );
1079
+ }
1080
+ function onMessage(msg) {
1081
+ if (msg.streamId !== streamId)
1082
+ return;
1083
+ if (msg.to !== transport.clientId) {
1084
+ transport.log?.error("got stream message from unexpected client", {
1085
+ clientId: transport.clientId,
1086
+ transportMessage: msg
1087
+ });
1088
+ return;
1089
+ }
1090
+ if (isStreamCancel(msg.controlFlags)) {
1091
+ cleanClose = false;
1092
+ span.addEvent("received cancel");
1093
+ let cancelResult;
1094
+ if (Value.Check(ReaderErrResultSchema, msg.payload)) {
1095
+ cancelResult = msg.payload;
1096
+ } else {
1097
+ cancelResult = Err({
1098
+ code: CANCEL_CODE,
1099
+ message: "stream cancelled with invalid payload"
1100
+ });
1101
+ transport.log?.error(
1102
+ "got stream cancel without a valid protocol error",
1103
+ {
1104
+ clientId: transport.clientId,
1105
+ transportMessage: msg,
1106
+ validationErrors: [
1107
+ ...Value.Errors(ReaderErrResultSchema, msg.payload)
1108
+ ]
1109
+ }
1110
+ );
1111
+ }
1112
+ if (!resReadable.isClosed()) {
1113
+ resReadable._pushValue(cancelResult);
1114
+ closeReadable();
1115
+ }
1116
+ reqWritable.close();
1117
+ return;
1118
+ }
1119
+ if (resReadable.isClosed()) {
1120
+ span.recordException("received message after response stream is closed");
1121
+ transport.log?.error("received message after response stream is closed", {
1122
+ clientId: transport.clientId,
1123
+ transportMessage: msg
1124
+ });
1125
+ return;
1126
+ }
1127
+ if (!Value.Check(ControlMessageCloseSchema, msg.payload)) {
1128
+ if (Value.Check(AnyResultSchema, msg.payload)) {
1129
+ resReadable._pushValue(msg.payload);
1130
+ } else {
1131
+ transport.log?.error(
1132
+ "Got non-control payload, but was not a valid result",
1133
+ {
1134
+ clientId: transport.clientId,
1135
+ transportMessage: msg,
1136
+ validationErrors: [...Value.Errors(AnyResultSchema, msg.payload)]
1137
+ }
1138
+ );
1139
+ }
1140
+ }
1141
+ if (isStreamClose(msg.controlFlags)) {
1142
+ span.addEvent("received response close");
1143
+ if (resReadable.isClosed()) {
1144
+ transport.log?.error(
1145
+ "received stream close but readable was already closed"
1146
+ );
1147
+ } else {
1148
+ closeReadable();
1149
+ }
1150
+ }
1151
+ }
1152
+ function onSessionStatus(evt) {
1153
+ if (evt.status !== "disconnect" || evt.session.to !== serverId || session.id !== evt.session.id) {
1154
+ return;
1155
+ }
1156
+ cleanClose = false;
1157
+ if (!resReadable.isClosed()) {
1158
+ resReadable._pushValue(
1159
+ Err({
1160
+ code: UNEXPECTED_DISCONNECT_CODE,
1161
+ message: `${serverId} unexpectedly disconnected`
1162
+ })
1163
+ );
1164
+ closeReadable();
1165
+ }
1166
+ reqWritable.close();
1167
+ }
1168
+ abortSignal?.addEventListener("abort", onClientCancel);
1169
+ transport.addEventListener("message", onMessage);
1170
+ transport.addEventListener("sessionStatus", onSessionStatus);
1171
+ sessionScopedSend({
1172
+ streamId,
1173
+ serviceName,
1174
+ procedureName,
1175
+ tracing: getPropagationContext(ctx),
1176
+ payload: init,
1177
+ controlFlags: procClosesWithInit ? 2 /* StreamOpenBit */ | 8 /* StreamClosedBit */ : 2 /* StreamOpenBit */
1178
+ });
1179
+ if (procClosesWithInit) {
1180
+ reqWritable.close();
1181
+ }
1182
+ if (procType === "subscription") {
1183
+ return {
1184
+ resReadable
1185
+ };
1186
+ }
1187
+ if (procType === "rpc") {
1188
+ return getSingleMessage(resReadable, transport.log);
1189
+ }
1190
+ if (procType === "upload") {
1191
+ let didFinalize = false;
1192
+ return {
1193
+ reqWritable,
1194
+ finalize: () => {
1195
+ if (didFinalize) {
1196
+ throw new Error("upload stream already finalized");
1197
+ }
1198
+ didFinalize = true;
1199
+ if (!reqWritable.isClosed()) {
1200
+ reqWritable.close();
1201
+ }
1202
+ return getSingleMessage(resReadable, transport.log);
1203
+ }
1204
+ };
1205
+ }
1206
+ return {
1207
+ resReadable,
1208
+ reqWritable
1209
+ };
1210
+ }
1211
+ async function getSingleMessage(resReadable, log) {
1212
+ const ret = await resReadable.collect();
1213
+ if (ret.length > 1) {
1214
+ log?.error("Expected single message from server, got multiple");
1215
+ }
1216
+ return ret[0];
1217
+ }
1218
+
1219
+ // router/server.ts
1220
+ import { Type as Type6 } from "@sinclair/typebox";
1221
+ import { Value as Value2 } from "@sinclair/typebox/value";
1222
+
1223
+ // transport/stringifyError.ts
1224
+ function coerceErrorString(err) {
1225
+ if (err instanceof Error) {
1226
+ return err.message || "unknown reason";
1227
+ }
1228
+ return `[coerced to error] ${String(err)}`;
1229
+ }
1230
+
1231
+ // router/server.ts
1232
+ var CancelResultSchema = ErrResultSchema(
1233
+ Type6.Object({
1234
+ code: Type6.Literal(CANCEL_CODE),
1235
+ message: Type6.String()
1236
+ })
1237
+ );
1238
+ var RiverServer = class {
1239
+ transport;
1240
+ contextMap;
1241
+ log;
1242
+ /**
1243
+ * We create a tombstones for streams cancelled by the server
1244
+ * so that we don't hit errors when the client has inflight
1245
+ * requests it sent before it saw the cancel.
1246
+ * We track cancelled streams for every client separately, so
1247
+ * that bad clients don't affect good clients.
1248
+ */
1249
+ serverCancelledStreams;
1250
+ maxCancelledStreamTombstonesPerSession;
1251
+ streams;
1252
+ services;
1253
+ unregisterTransportListeners;
1254
+ constructor(transport, services, handshakeOptions, extendedContext, maxCancelledStreamTombstonesPerSession = 200) {
1255
+ const instances = {};
1256
+ this.services = instances;
1257
+ this.contextMap = /* @__PURE__ */ new Map();
1258
+ for (const [name, service] of Object.entries(services)) {
1259
+ const instance = service.instantiate(extendedContext ?? {});
1260
+ instances[name] = instance;
1261
+ this.contextMap.set(instance, {
1262
+ ...extendedContext,
1263
+ state: instance.state
1264
+ });
1265
+ }
1266
+ if (handshakeOptions) {
1267
+ transport.extendHandshake(handshakeOptions);
1268
+ }
1269
+ this.transport = transport;
1270
+ this.streams = /* @__PURE__ */ new Map();
1271
+ this.serverCancelledStreams = /* @__PURE__ */ new Map();
1272
+ this.maxCancelledStreamTombstonesPerSession = maxCancelledStreamTombstonesPerSession;
1273
+ this.log = transport.log;
1274
+ const handleCreatingNewStreams = (message) => {
1275
+ if (message.to !== this.transport.clientId) {
1276
+ this.log?.info(
1277
+ `got msg with destination that isn't this server, ignoring`,
1278
+ {
1279
+ clientId: this.transport.clientId,
1280
+ transportMessage: message
1281
+ }
1282
+ );
1283
+ return;
1284
+ }
1285
+ const streamId = message.streamId;
1286
+ const stream2 = this.streams.get(streamId);
1287
+ if (stream2) {
1288
+ stream2.handleMsg(message);
1289
+ return;
1290
+ }
1291
+ if (this.serverCancelledStreams.get(message.from)?.has(streamId)) {
1292
+ return;
1293
+ }
1294
+ const newStreamProps = this.validateNewProcStream(message);
1295
+ if (!newStreamProps) {
1296
+ return;
1297
+ }
1298
+ createHandlerSpan(
1299
+ transport.tracer,
1300
+ newStreamProps.initialSession,
1301
+ newStreamProps.procedure.type,
1302
+ newStreamProps.serviceName,
1303
+ newStreamProps.procedureName,
1304
+ newStreamProps.streamId,
1305
+ newStreamProps.tracingCtx,
1306
+ (span) => {
1307
+ this.createNewProcStream(span, newStreamProps);
1308
+ }
1309
+ );
1310
+ };
1311
+ const handleSessionStatus = (evt) => {
1312
+ if (evt.status !== "disconnect")
1313
+ return;
1314
+ const disconnectedClientId = evt.session.to;
1315
+ this.log?.info(
1316
+ `got session disconnect from ${disconnectedClientId}, cleaning up streams`,
1317
+ evt.session.loggingMetadata
1318
+ );
1319
+ for (const stream2 of this.streams.values()) {
1320
+ if (stream2.from === disconnectedClientId) {
1321
+ stream2.handleSessionDisconnect();
1322
+ }
1323
+ }
1324
+ this.serverCancelledStreams.delete(disconnectedClientId);
1325
+ };
1326
+ const handleTransportStatus = (evt) => {
1327
+ if (evt.status !== "closed")
1328
+ return;
1329
+ this.unregisterTransportListeners();
1330
+ };
1331
+ this.unregisterTransportListeners = () => {
1332
+ this.transport.removeEventListener("message", handleCreatingNewStreams);
1333
+ this.transport.removeEventListener("sessionStatus", handleSessionStatus);
1334
+ this.transport.removeEventListener(
1335
+ "transportStatus",
1336
+ handleTransportStatus
1337
+ );
1338
+ };
1339
+ this.transport.addEventListener("message", handleCreatingNewStreams);
1340
+ this.transport.addEventListener("sessionStatus", handleSessionStatus);
1341
+ this.transport.addEventListener("transportStatus", handleTransportStatus);
1342
+ }
1343
+ createNewProcStream(span, props) {
1344
+ const {
1345
+ streamId,
1346
+ initialSession,
1347
+ procedureName,
1348
+ serviceName,
1349
+ procedure,
1350
+ sessionMetadata,
1351
+ serviceContext,
1352
+ initPayload,
1353
+ procClosesWithInit,
1354
+ passInitAsDataForBackwardsCompat
1355
+ } = props;
1356
+ const {
1357
+ to: from,
1358
+ loggingMetadata,
1359
+ protocolVersion,
1360
+ id: sessionId
1361
+ } = initialSession;
1362
+ loggingMetadata.telemetry = {
1363
+ traceId: span.spanContext().traceId,
1364
+ spanId: span.spanContext().spanId
1365
+ };
1366
+ let cleanClose = true;
1367
+ const onMessage = (msg) => {
1368
+ if (msg.from !== from) {
1369
+ this.log?.error("got stream message from unexpected client", {
1370
+ ...loggingMetadata,
1371
+ transportMessage: msg,
1372
+ tags: ["invariant-violation"]
1373
+ });
1374
+ return;
1375
+ }
1376
+ if (isStreamCancelBackwardsCompat(msg.controlFlags, protocolVersion)) {
1377
+ let cancelResult;
1378
+ if (Value2.Check(CancelResultSchema, msg.payload)) {
1379
+ cancelResult = msg.payload;
1380
+ } else {
1381
+ cancelResult = Err({
1382
+ code: CANCEL_CODE,
1383
+ message: "stream cancelled, client sent invalid payload"
1384
+ });
1385
+ this.log?.warn("got stream cancel without a valid protocol error", {
1386
+ ...loggingMetadata,
1387
+ transportMessage: msg,
1388
+ validationErrors: [
1389
+ ...Value2.Errors(CancelResultSchema, msg.payload)
1390
+ ],
1391
+ tags: ["invalid-request"]
1392
+ });
1393
+ }
1394
+ if (!reqReadable.isClosed()) {
1395
+ reqReadable._pushValue(cancelResult);
1396
+ closeReadable();
1397
+ }
1398
+ resWritable.close();
1399
+ return;
1400
+ }
1401
+ if (reqReadable.isClosed()) {
1402
+ this.log?.warn("received message after request stream is closed", {
1403
+ ...loggingMetadata,
1404
+ transportMessage: msg,
1405
+ tags: ["invalid-request"]
1406
+ });
1407
+ onServerCancel({
1408
+ code: INVALID_REQUEST_CODE,
1409
+ message: "received message after request stream is closed"
1410
+ });
1411
+ return;
1412
+ }
1413
+ if ("requestData" in procedure && Value2.Check(procedure.requestData, msg.payload)) {
1414
+ reqReadable._pushValue(Ok(msg.payload));
1415
+ if (isStreamCloseBackwardsCompat(msg.controlFlags, protocolVersion)) {
1416
+ closeReadable();
1417
+ }
1418
+ return;
1419
+ }
1420
+ if (Value2.Check(ControlMessagePayloadSchema, msg.payload) && isStreamCloseBackwardsCompat(msg.controlFlags, protocolVersion)) {
1421
+ closeReadable();
1422
+ return;
1423
+ }
1424
+ let validationErrors;
1425
+ let errMessage;
1426
+ if ("requestData" in procedure) {
1427
+ errMessage = "expected requestData or control payload";
1428
+ validationErrors = [
1429
+ ...Value2.Errors(procedure.responseData, msg.payload)
1430
+ ];
1431
+ } else {
1432
+ validationErrors = [
1433
+ ...Value2.Errors(ControlMessagePayloadSchema, msg.payload)
1434
+ ];
1435
+ errMessage = "expected control payload";
1436
+ }
1437
+ this.log?.warn(errMessage, {
1438
+ ...loggingMetadata,
1439
+ transportMessage: msg,
1440
+ validationErrors,
1441
+ tags: ["invalid-request"]
1442
+ });
1443
+ onServerCancel({
1444
+ code: INVALID_REQUEST_CODE,
1445
+ message: errMessage
1446
+ });
1447
+ };
1448
+ const finishedController = new AbortController();
1449
+ const procStream = {
1450
+ from,
1451
+ streamId,
1452
+ procedureName,
1453
+ serviceName,
1454
+ sessionMetadata,
1455
+ procedure,
1456
+ handleMsg: onMessage,
1457
+ handleSessionDisconnect: () => {
1458
+ cleanClose = false;
1459
+ const errPayload = {
1460
+ code: UNEXPECTED_DISCONNECT_CODE,
1461
+ message: "client unexpectedly disconnected"
1462
+ };
1463
+ if (!reqReadable.isClosed()) {
1464
+ reqReadable._pushValue(Err(errPayload));
1465
+ closeReadable();
1466
+ }
1467
+ resWritable.close();
1468
+ }
1469
+ };
1470
+ const sessionScopedSend = this.transport.getSessionBoundSendFn(
1471
+ from,
1472
+ sessionId
1473
+ );
1474
+ const cancelStream = (streamId2, payload) => {
1475
+ this.cancelStream(from, sessionScopedSend, streamId2, payload);
1476
+ };
1477
+ const onServerCancel = (e) => {
1478
+ recordRiverError(span, e);
1479
+ if (reqReadable.isClosed() && resWritable.isClosed()) {
1480
+ return;
1481
+ }
1482
+ cleanClose = false;
1483
+ const result = Err(e);
1484
+ if (!reqReadable.isClosed()) {
1485
+ reqReadable._pushValue(result);
1486
+ closeReadable();
1487
+ }
1488
+ resWritable.close();
1489
+ cancelStream(streamId, result);
1490
+ };
1491
+ const cleanup = () => {
1492
+ finishedController.abort();
1493
+ this.streams.delete(streamId);
1494
+ };
1495
+ const procClosesWithResponse = procedure.type === "rpc" || procedure.type === "upload";
1496
+ const reqReadable = new ReadableImpl();
1497
+ const closeReadable = () => {
1498
+ reqReadable._triggerClose();
1499
+ if (protocolVersion === "v1.1") {
1500
+ if (!procClosesWithResponse && !resWritable.isClosed()) {
1501
+ resWritable.close();
1502
+ }
1503
+ }
1504
+ if (resWritable.isClosed()) {
1505
+ cleanup();
1506
+ }
1507
+ };
1508
+ if (passInitAsDataForBackwardsCompat) {
1509
+ reqReadable._pushValue(Ok(initPayload));
1510
+ }
1511
+ const resWritable = new WritableImpl({
1512
+ writeCb: (response) => {
1513
+ if (!response.ok) {
1514
+ recordRiverError(span, response.payload);
1515
+ }
1516
+ sessionScopedSend({
1517
+ streamId,
1518
+ controlFlags: procClosesWithResponse ? getStreamCloseBackwardsCompat(protocolVersion) : 0,
1519
+ payload: response
1520
+ });
1521
+ if (procClosesWithResponse) {
1522
+ resWritable.close();
1523
+ }
1524
+ },
1525
+ // close callback
1526
+ closeCb: () => {
1527
+ if (!procClosesWithResponse && cleanClose) {
1528
+ const message = closeStreamMessage(streamId);
1529
+ message.controlFlags = getStreamCloseBackwardsCompat(protocolVersion);
1530
+ sessionScopedSend(message);
1531
+ }
1532
+ if (protocolVersion === "v1.1") {
1533
+ if (!reqReadable.isClosed()) {
1534
+ closeReadable();
1535
+ }
1536
+ }
1537
+ if (reqReadable.isClosed()) {
1538
+ cleanup();
1539
+ }
1540
+ }
1541
+ });
1542
+ const onHandlerError = (err, span2) => {
1543
+ const errorMsg = coerceErrorString(err);
1544
+ span2.recordException(err instanceof Error ? err : new Error(errorMsg));
1545
+ this.log?.error(
1546
+ `${serviceName}.${procedureName} handler threw an uncaught error`,
1547
+ {
1548
+ ...loggingMetadata,
1549
+ transportMessage: {
1550
+ procedureName,
1551
+ serviceName
1552
+ },
1553
+ extras: {
1554
+ error: errorMsg,
1555
+ originalException: err
1556
+ },
1557
+ tags: ["uncaught-handler-error"]
1558
+ }
1559
+ );
1560
+ onServerCancel({
1561
+ code: UNCAUGHT_ERROR_CODE,
1562
+ message: errorMsg
1563
+ });
1564
+ };
1565
+ if (procClosesWithInit) {
1566
+ closeReadable();
1567
+ }
1568
+ const handlerContextWithSpan = (span2) => ({
1569
+ ...serviceContext,
1570
+ from,
1571
+ sessionId,
1572
+ metadata: sessionMetadata,
1573
+ span: span2,
1574
+ cancel: () => {
1575
+ onServerCancel({
1576
+ code: CANCEL_CODE,
1577
+ message: "cancelled by server procedure handler"
1578
+ });
1579
+ },
1580
+ signal: finishedController.signal
1581
+ });
1582
+ switch (procedure.type) {
1583
+ case "rpc":
1584
+ void (async () => {
1585
+ try {
1586
+ const responsePayload = await procedure.handler({
1587
+ ctx: handlerContextWithSpan(span),
1588
+ reqInit: initPayload
1589
+ });
1590
+ if (resWritable.isClosed()) {
1591
+ return;
1592
+ }
1593
+ resWritable.write(responsePayload);
1594
+ } catch (err) {
1595
+ onHandlerError(err, span);
1596
+ } finally {
1597
+ span.end();
1598
+ }
1599
+ })();
1600
+ break;
1601
+ case "stream":
1602
+ void (async () => {
1603
+ try {
1604
+ await procedure.handler({
1605
+ ctx: handlerContextWithSpan(span),
1606
+ reqInit: initPayload,
1607
+ reqReadable,
1608
+ resWritable
1609
+ });
1610
+ } catch (err) {
1611
+ onHandlerError(err, span);
1612
+ } finally {
1613
+ span.end();
1614
+ }
1615
+ })();
1616
+ break;
1617
+ case "subscription":
1618
+ void (async () => {
1619
+ try {
1620
+ await procedure.handler({
1621
+ ctx: handlerContextWithSpan(span),
1622
+ reqInit: initPayload,
1623
+ resWritable
1624
+ });
1625
+ } catch (err) {
1626
+ onHandlerError(err, span);
1627
+ } finally {
1628
+ span.end();
1629
+ }
1630
+ })();
1631
+ break;
1632
+ case "upload":
1633
+ void (async () => {
1634
+ try {
1635
+ const responsePayload = await procedure.handler({
1636
+ ctx: handlerContextWithSpan(span),
1637
+ reqInit: initPayload,
1638
+ reqReadable
1639
+ });
1640
+ if (resWritable.isClosed()) {
1641
+ return;
1642
+ }
1643
+ resWritable.write(responsePayload);
1644
+ } catch (err) {
1645
+ onHandlerError(err, span);
1646
+ } finally {
1647
+ span.end();
1648
+ }
1649
+ })();
1650
+ break;
1651
+ }
1652
+ if (!finishedController.signal.aborted) {
1653
+ this.streams.set(streamId, procStream);
1654
+ }
1655
+ }
1656
+ getContext(service, serviceName) {
1657
+ const context2 = this.contextMap.get(service);
1658
+ if (!context2) {
1659
+ const err = `no context found for ${serviceName}`;
1660
+ this.log?.error(err, {
1661
+ clientId: this.transport.clientId,
1662
+ tags: ["invariant-violation"]
1663
+ });
1664
+ throw new Error(err);
1665
+ }
1666
+ return context2;
1667
+ }
1668
+ validateNewProcStream(initMessage) {
1669
+ const session = this.transport.sessions.get(initMessage.from);
1670
+ if (!session) {
1671
+ this.log?.error(`couldn't find session for ${initMessage.from}`, {
1672
+ clientId: this.transport.clientId,
1673
+ transportMessage: initMessage,
1674
+ tags: ["invariant-violation"]
1675
+ });
1676
+ return null;
1677
+ }
1678
+ const sessionScopedSend = this.transport.getSessionBoundSendFn(
1679
+ initMessage.from,
1680
+ session.id
1681
+ );
1682
+ const cancelStream = (streamId, payload) => {
1683
+ this.cancelStream(initMessage.from, sessionScopedSend, streamId, payload);
1684
+ };
1685
+ const sessionMetadata = this.transport.sessionHandshakeMetadata.get(
1686
+ session.to
1687
+ );
1688
+ if (!sessionMetadata) {
1689
+ const errMessage = `session doesn't have handshake metadata`;
1690
+ this.log?.error(errMessage, {
1691
+ ...session.loggingMetadata,
1692
+ tags: ["invariant-violation"]
1693
+ });
1694
+ cancelStream(
1695
+ initMessage.streamId,
1696
+ Err({
1697
+ code: UNCAUGHT_ERROR_CODE,
1698
+ message: errMessage
1699
+ })
1700
+ );
1701
+ return null;
1702
+ }
1703
+ if (!isStreamOpen(initMessage.controlFlags)) {
1704
+ const errMessage = `can't create a new procedure stream from a message that doesn't have the stream open bit set`;
1705
+ this.log?.warn(errMessage, {
1706
+ ...session.loggingMetadata,
1707
+ clientId: this.transport.clientId,
1708
+ transportMessage: initMessage,
1709
+ tags: ["invalid-request"]
1710
+ });
1711
+ cancelStream(
1712
+ initMessage.streamId,
1713
+ Err({
1714
+ code: INVALID_REQUEST_CODE,
1715
+ message: errMessage
1716
+ })
1717
+ );
1718
+ return null;
1719
+ }
1720
+ if (!initMessage.serviceName) {
1721
+ const errMessage = `missing service name in stream open message`;
1722
+ this.log?.warn(errMessage, {
1723
+ ...session.loggingMetadata,
1724
+ transportMessage: initMessage,
1725
+ tags: ["invalid-request"]
1726
+ });
1727
+ cancelStream(
1728
+ initMessage.streamId,
1729
+ Err({
1730
+ code: INVALID_REQUEST_CODE,
1731
+ message: errMessage
1732
+ })
1733
+ );
1734
+ return null;
1735
+ }
1736
+ if (!initMessage.procedureName) {
1737
+ const errMessage = `missing procedure name in stream open message`;
1738
+ this.log?.warn(errMessage, {
1739
+ ...session.loggingMetadata,
1740
+ transportMessage: initMessage,
1741
+ tags: ["invalid-request"]
1742
+ });
1743
+ cancelStream(
1744
+ initMessage.streamId,
1745
+ Err({
1746
+ code: INVALID_REQUEST_CODE,
1747
+ message: errMessage
1748
+ })
1749
+ );
1750
+ return null;
1751
+ }
1752
+ if (!(initMessage.serviceName in this.services)) {
1753
+ const errMessage = `couldn't find service ${initMessage.serviceName}`;
1754
+ this.log?.warn(errMessage, {
1755
+ ...session.loggingMetadata,
1756
+ clientId: this.transport.clientId,
1757
+ transportMessage: initMessage,
1758
+ tags: ["invalid-request"]
1759
+ });
1760
+ cancelStream(
1761
+ initMessage.streamId,
1762
+ Err({
1763
+ code: INVALID_REQUEST_CODE,
1764
+ message: errMessage
1765
+ })
1766
+ );
1767
+ return null;
1768
+ }
1769
+ const service = this.services[initMessage.serviceName];
1770
+ if (!(initMessage.procedureName in service.procedures)) {
1771
+ const errMessage = `couldn't find a matching procedure for ${initMessage.serviceName}.${initMessage.procedureName}`;
1772
+ this.log?.warn(errMessage, {
1773
+ ...session.loggingMetadata,
1774
+ transportMessage: initMessage,
1775
+ tags: ["invalid-request"]
1776
+ });
1777
+ cancelStream(
1778
+ initMessage.streamId,
1779
+ Err({
1780
+ code: INVALID_REQUEST_CODE,
1781
+ message: errMessage
1782
+ })
1783
+ );
1784
+ return null;
1785
+ }
1786
+ const serviceContext = this.getContext(service, initMessage.serviceName);
1787
+ const procedure = service.procedures[initMessage.procedureName];
1788
+ if (!["rpc", "upload", "stream", "subscription"].includes(procedure.type)) {
1789
+ this.log?.error(
1790
+ `got request for invalid procedure type ${procedure.type} at ${initMessage.serviceName}.${initMessage.procedureName}`,
1791
+ {
1792
+ ...session.loggingMetadata,
1793
+ transportMessage: initMessage,
1794
+ tags: ["invariant-violation"]
1795
+ }
1796
+ );
1797
+ return null;
1798
+ }
1799
+ let passInitAsDataForBackwardsCompat = false;
1800
+ if (session.protocolVersion === "v1.1" && (procedure.type === "upload" || procedure.type === "stream") && Value2.Check(procedure.requestData, initMessage.payload) && Value2.Check(procedure.requestInit, {})) {
1801
+ passInitAsDataForBackwardsCompat = true;
1802
+ } else if (!Value2.Check(procedure.requestInit, initMessage.payload)) {
1803
+ const errMessage = `procedure init failed validation`;
1804
+ this.log?.warn(errMessage, {
1805
+ ...session.loggingMetadata,
1806
+ clientId: this.transport.clientId,
1807
+ transportMessage: initMessage,
1808
+ tags: ["invalid-request"]
1809
+ });
1810
+ cancelStream(
1811
+ initMessage.streamId,
1812
+ Err({
1813
+ code: INVALID_REQUEST_CODE,
1814
+ message: errMessage
1815
+ })
1816
+ );
1817
+ return null;
1818
+ }
1819
+ return {
1820
+ initialSession: session,
1821
+ streamId: initMessage.streamId,
1822
+ procedureName: initMessage.procedureName,
1823
+ serviceName: initMessage.serviceName,
1824
+ tracingCtx: initMessage.tracing,
1825
+ initPayload: initMessage.payload,
1826
+ sessionMetadata,
1827
+ procedure,
1828
+ serviceContext,
1829
+ procClosesWithInit: isStreamCloseBackwardsCompat(
1830
+ initMessage.controlFlags,
1831
+ session.protocolVersion
1832
+ ),
1833
+ passInitAsDataForBackwardsCompat
1834
+ };
1835
+ }
1836
+ cancelStream(to, sessionScopedSend, streamId, payload) {
1837
+ let cancelledStreamsInSession = this.serverCancelledStreams.get(to);
1838
+ if (!cancelledStreamsInSession) {
1839
+ cancelledStreamsInSession = new LRUSet(
1840
+ this.maxCancelledStreamTombstonesPerSession
1841
+ );
1842
+ this.serverCancelledStreams.set(to, cancelledStreamsInSession);
1843
+ }
1844
+ cancelledStreamsInSession.add(streamId);
1845
+ const msg = cancelMessage(streamId, payload);
1846
+ sessionScopedSend(msg);
1847
+ }
1848
+ async close() {
1849
+ this.unregisterTransportListeners();
1850
+ for (const serviceName of Object.keys(this.services)) {
1851
+ const service = this.services[serviceName];
1852
+ await service[Symbol.asyncDispose]();
1853
+ }
1854
+ }
1855
+ };
1856
+ var LRUSet = class {
1857
+ items;
1858
+ maxItems;
1859
+ constructor(maxItems) {
1860
+ this.items = /* @__PURE__ */ new Set();
1861
+ this.maxItems = maxItems;
1862
+ }
1863
+ add(item) {
1864
+ if (this.items.has(item)) {
1865
+ this.items.delete(item);
1866
+ } else if (this.items.size >= this.maxItems) {
1867
+ const first = this.items.values().next();
1868
+ if (!first.done) {
1869
+ this.items.delete(first.value);
1870
+ }
1871
+ }
1872
+ this.items.add(item);
1873
+ }
1874
+ has(item) {
1875
+ return this.items.has(item);
1876
+ }
1877
+ };
1878
+ function isStreamCancelBackwardsCompat(controlFlags, protocolVersion) {
1879
+ if (protocolVersion === "v1.1") {
1880
+ return false;
1881
+ }
1882
+ return isStreamCancel(controlFlags);
1883
+ }
1884
+ function isStreamCloseBackwardsCompat(controlFlags, protocolVersion) {
1885
+ if (protocolVersion === "v1.1") {
1886
+ return isStreamCancel(controlFlags);
1887
+ }
1888
+ return isStreamClose(controlFlags);
1889
+ }
1890
+ function getStreamCloseBackwardsCompat(protocolVersion) {
1891
+ if (protocolVersion === "v1.1") {
1892
+ return 4 /* StreamCancelBit */;
1893
+ }
1894
+ return 8 /* StreamClosedBit */;
1895
+ }
1896
+ function createServer(transport, services, providedServerOptions) {
1897
+ return new RiverServer(
1898
+ transport,
1899
+ services,
1900
+ providedServerOptions?.handshakeOptions,
1901
+ providedServerOptions?.extendedContext,
1902
+ providedServerOptions?.maxCancelledStreamTombstonesPerSession
1903
+ );
1904
+ }
1905
+
1906
+ // router/handshake.ts
1907
+ function createClientHandshakeOptions(schema, construct) {
1908
+ return { schema, construct };
1909
+ }
1910
+ function createServerHandshakeOptions(schema, validate) {
1911
+ return { schema, validate };
1912
+ }
1913
+
1914
+ // package.json
1915
+ var version = "0.204.0";
1916
+
1917
+ export {
1918
+ UNCAUGHT_ERROR_CODE,
1919
+ UNEXPECTED_DISCONNECT_CODE,
1920
+ INVALID_REQUEST_CODE,
1921
+ CANCEL_CODE,
1922
+ ReaderErrorSchema,
1923
+ flattenErrorType,
1924
+ serializeSchemaV1Compat,
1925
+ serializeSchema,
1926
+ ServiceSchema,
1927
+ Procedure,
1928
+ generateId,
1929
+ TransportMessageSchema,
1930
+ currentProtocolVersion,
1931
+ acceptedProtocolVersions,
1932
+ isAcceptedProtocolVersion,
1933
+ ControlMessageHandshakeRequestSchema,
1934
+ HandshakeErrorRetriableResponseCodes,
1935
+ HandshakeErrorCustomHandlerFatalResponseCodes,
1936
+ ControlMessageHandshakeResponseSchema,
1937
+ OpaqueTransportMessageSchema,
1938
+ handshakeRequestMessage,
1939
+ handshakeResponseMessage,
1940
+ isAck,
1941
+ Ok,
1942
+ Err,
1943
+ unwrapOrThrow,
1944
+ getPropagationContext,
1945
+ createSessionTelemetryInfo,
1946
+ createConnectionTelemetryInfo,
1947
+ getTracer,
1948
+ createClient,
1949
+ coerceErrorString,
1950
+ createServer,
1951
+ createClientHandshakeOptions,
1952
+ createServerHandshakeOptions,
1953
+ version
1954
+ };
1955
+ //# sourceMappingURL=chunk-LJCR3ADI.js.map