@replit/river 0.18.4 → 0.19.1

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 (45) hide show
  1. package/dist/{chunk-K7CUSLWL.js → chunk-4PFUC4QB.js} +1 -1
  2. package/dist/{chunk-VH3NGOXQ.js → chunk-D5PVGZPQ.js} +5 -3
  3. package/dist/{chunk-UABIFWM7.js → chunk-FILQOCCK.js} +219 -104
  4. package/dist/{chunk-TF7XT3LV.js → chunk-LO6MRYNI.js} +20 -2
  5. package/dist/{chunk-PUX3U2SZ.js → chunk-ZNXZ7QPT.js} +1 -1
  6. package/dist/{connection-893bd769.d.ts → connection-d625aa8d.d.ts} +1 -1
  7. package/dist/{connection-89918b74.d.ts → connection-ddc757b9.d.ts} +1 -1
  8. package/dist/index-2941de8c.d.ts +341 -0
  9. package/dist/logging/index.d.cts +2 -1
  10. package/dist/logging/index.d.ts +2 -1
  11. package/dist/router/index.cjs +24 -4
  12. package/dist/router/index.d.cts +7 -381
  13. package/dist/router/index.d.ts +7 -381
  14. package/dist/router/index.js +5 -3
  15. package/dist/services-d47ce743.d.ts +736 -0
  16. package/dist/services-ee45322f.d.ts +736 -0
  17. package/dist/transport/impls/uds/client.cjs +80 -53
  18. package/dist/transport/impls/uds/client.d.cts +5 -5
  19. package/dist/transport/impls/uds/client.d.ts +5 -5
  20. package/dist/transport/impls/uds/client.js +3 -3
  21. package/dist/transport/impls/uds/server.cjs +151 -62
  22. package/dist/transport/impls/uds/server.d.cts +4 -4
  23. package/dist/transport/impls/uds/server.d.ts +4 -4
  24. package/dist/transport/impls/uds/server.js +3 -3
  25. package/dist/transport/impls/ws/client.cjs +80 -53
  26. package/dist/transport/impls/ws/client.d.cts +3 -3
  27. package/dist/transport/impls/ws/client.d.ts +3 -3
  28. package/dist/transport/impls/ws/client.js +3 -3
  29. package/dist/transport/impls/ws/server.cjs +151 -62
  30. package/dist/transport/impls/ws/server.d.cts +4 -4
  31. package/dist/transport/impls/ws/server.d.ts +4 -4
  32. package/dist/transport/impls/ws/server.js +3 -3
  33. package/dist/transport/index.cjs +188 -71
  34. package/dist/transport/index.d.cts +295 -3
  35. package/dist/transport/index.d.ts +295 -3
  36. package/dist/transport/index.js +2 -3
  37. package/dist/util/testHelpers.cjs +410 -401
  38. package/dist/util/testHelpers.d.cts +3 -3
  39. package/dist/util/testHelpers.d.ts +3 -3
  40. package/dist/util/testHelpers.js +3 -4
  41. package/package.json +1 -1
  42. package/dist/chunk-RPIDSIQG.js +0 -0
  43. package/dist/index-46ed19d8.d.ts +0 -111
  44. package/dist/index-d412ca83.d.ts +0 -420
  45. package/dist/procedures-bfffcb0b.d.ts +0 -324
@@ -0,0 +1,341 @@
1
+ import * as _sinclair_typebox from '@sinclair/typebox';
2
+ import { TSchema } from '@sinclair/typebox';
3
+ import { C as Codec } from './types-3e5768ec.js';
4
+
5
+ declare const LoggingLevels: {
6
+ readonly debug: -1;
7
+ readonly info: 0;
8
+ readonly warn: 1;
9
+ readonly error: 2;
10
+ };
11
+ type LoggingLevel = keyof typeof LoggingLevels;
12
+ type LogFn = (msg: string, ctx?: MessageMetadata, level?: LoggingLevel) => void;
13
+ type Logger = {
14
+ [key in LoggingLevel]: LogFn;
15
+ };
16
+ type MessageMetadata = Record<string, unknown> & Partial<{
17
+ protocolVersion: string;
18
+ clientId: string;
19
+ connectedTo: string;
20
+ sessionId: string;
21
+ connId: string;
22
+ fullTransportMessage: OpaqueTransportMessage;
23
+ partialTransportMessage: Partial<PartialTransportMessage>;
24
+ }>;
25
+ declare const stringLogger: LogFn;
26
+ declare const coloredStringLogger: LogFn;
27
+ declare const jsonLogger: LogFn;
28
+ declare function bindLogger(fn: LogFn | Logger | undefined, level?: LoggingLevel): Logger | undefined;
29
+
30
+ /**
31
+ * A connection is the actual raw underlying transport connection.
32
+ * It’s responsible for dispatching to/from the actual connection itself
33
+ * This should be instantiated as soon as the client/server has a connection
34
+ * It’s tied to the lifecycle of the underlying transport connection (i.e. if the WS drops, this connection should be deleted)
35
+ */
36
+ declare abstract class Connection {
37
+ debugId: string;
38
+ constructor();
39
+ /**
40
+ * Handle adding a callback for when a message is received.
41
+ * @param msg The message that was received.
42
+ */
43
+ abstract addDataListener(cb: (msg: Uint8Array) => void): void;
44
+ abstract removeDataListener(cb: (msg: Uint8Array) => void): void;
45
+ /**
46
+ * Handle adding a callback for when the connection is closed.
47
+ * This should also be called if an error happens.
48
+ * @param cb The callback to call when the connection is closed.
49
+ */
50
+ abstract addCloseListener(cb: () => void): void;
51
+ /**
52
+ * Handle adding a callback for when an error is received.
53
+ * This should only be used for logging errors, all cleanup
54
+ * should be delegated to addCloseListener.
55
+ *
56
+ * The implementer should take care such that the implemented
57
+ * connection will call both the close and error callbacks
58
+ * on an error.
59
+ *
60
+ * @param cb The callback to call when an error is received.
61
+ */
62
+ abstract addErrorListener(cb: (err: Error) => void): void;
63
+ /**
64
+ * Sends a message over the connection.
65
+ * @param msg The message to send.
66
+ * @returns true if the message was sent, false otherwise.
67
+ */
68
+ abstract send(msg: Uint8Array): boolean;
69
+ /**
70
+ * Closes the connection.
71
+ */
72
+ abstract close(): void;
73
+ }
74
+ interface SessionOptions {
75
+ /**
76
+ * Frequency at which to send heartbeat acknowledgements
77
+ */
78
+ heartbeatIntervalMs: number;
79
+ /**
80
+ * Number of elapsed heartbeats without a response message before we consider
81
+ * the connection dead.
82
+ */
83
+ heartbeatsUntilDead: number;
84
+ /**
85
+ * Duration to wait between connection disconnect and actual session disconnect
86
+ */
87
+ sessionDisconnectGraceMs: number;
88
+ /**
89
+ * The codec to use for encoding/decoding messages over the wire
90
+ */
91
+ codec: Codec;
92
+ }
93
+ /**
94
+ * A session is a higher-level abstraction that operates over the span of potentially multiple transport-level connections
95
+ * - It’s responsible for tracking any metadata for a particular client that might need to be persisted across connections (i.e. the sendBuffer, ack, seq)
96
+ * - This will only be considered disconnected if
97
+ * - the server tells the client that we’ve reconnected but it doesn’t recognize us anymore (server definitely died) or
98
+ * - we hit a grace period after a connection disconnect
99
+ */
100
+ declare class Session<ConnType extends Connection> {
101
+ private codec;
102
+ private options;
103
+ /**
104
+ * The buffer of messages that have been sent but not yet acknowledged.
105
+ */
106
+ private sendBuffer;
107
+ /**
108
+ * The active connection associated with this session
109
+ */
110
+ connection?: ConnType;
111
+ readonly from: TransportClientId;
112
+ readonly to: TransportClientId;
113
+ /**
114
+ * The unique ID of this session.
115
+ */
116
+ id: string;
117
+ /**
118
+ * What the other side advertised as their session ID
119
+ * for this session.
120
+ */
121
+ advertisedSessionId?: string;
122
+ /**
123
+ * The metadata for this session, as parsed from the handshake.
124
+ *
125
+ * Will only ever be populated on the server side.
126
+ */
127
+ metadata?: ParsedHandshakeMetadata;
128
+ /**
129
+ * Number of messages we've sent along this session (excluding handshake and acks)
130
+ */
131
+ private seq;
132
+ /**
133
+ * Number of unique messages we've received this session (excluding handshake and acks)
134
+ */
135
+ private ack;
136
+ /**
137
+ * The grace period between when the inner connection is disconnected
138
+ * and when we should consider the entire session disconnected.
139
+ */
140
+ private disconnectionGrace?;
141
+ /**
142
+ * Number of heartbeats we've sent without a response.
143
+ */
144
+ private heartbeatMisses;
145
+ /**
146
+ * The interval for sending heartbeats.
147
+ */
148
+ private heartbeat;
149
+ constructor(conn: ConnType | undefined, from: TransportClientId, to: TransportClientId, options: SessionOptions);
150
+ get loggingMetadata(): Omit<MessageMetadata, 'parsedMsg'>;
151
+ /**
152
+ * Sends a message over the session's connection.
153
+ * If the connection is not ready or the message fails to send, the message can be buffered for retry unless skipped.
154
+ *
155
+ * @param msg The partial message to be sent, which will be constructed into a full message.
156
+ * @param addToSendBuff Whether to add the message to the send buffer for retry.
157
+ * @returns The full transport ID of the message that was attempted to be sent.
158
+ */
159
+ send(msg: PartialTransportMessage): string;
160
+ sendHeartbeat(): void;
161
+ resetBufferedMessages(): void;
162
+ sendBufferedMessages(conn: ConnType): void;
163
+ updateBookkeeping(ack: number, seq: number): void;
164
+ closeStaleConnection(conn?: ConnType): void;
165
+ replaceWithNewConnection(newConn: ConnType): void;
166
+ beginGrace(cb: () => void): void;
167
+ cancelGrace(): void;
168
+ close(): void;
169
+ get connected(): boolean;
170
+ get nextExpectedSeq(): number;
171
+ constructMsg<Payload>(partialMsg: PartialTransportMessage<Payload>): TransportMessage<Payload>;
172
+ inspectSendBuffer(): ReadonlyArray<OpaqueTransportMessage>;
173
+ }
174
+
175
+ /**
176
+ * Metadata associated with a handshake request, as sent by the client.
177
+ *
178
+ * You should use declaration merging to extend this interface
179
+ * with whatever you need. For example, if you need to store an
180
+ * identifier for the client, you could do:
181
+ * ```
182
+ * declare module '@replit/river' {
183
+ * interface HandshakeMetadataClient {
184
+ * id: string;
185
+ * }
186
+ * }
187
+ * ```
188
+ */
189
+ interface HandshakeRequestMetadata {
190
+ }
191
+ /**
192
+ * Metadata associated with a handshake response, after the server
193
+ * has processed the data in {@link HandshakeRequestMetadata}. This
194
+ * is a separate interface for multiple reasons, but one of the main
195
+ * ones is that the server should remove any sensitive data from the
196
+ * client's request metadata before storing it in the session, that
197
+ * way no secrets are persisted in memory.
198
+ *
199
+ * You should use declaration merging to extend this interface
200
+ * with whatever you need. For example, if you need to store an
201
+ * identifier for the client, you could do:
202
+ * ```
203
+ * declare module '@replit/river' {
204
+ * interface HandshakeMetadataServer {
205
+ * id: string;
206
+ * }
207
+ * }
208
+ * ```
209
+ */
210
+ interface ParsedHandshakeMetadata {
211
+ }
212
+ /**
213
+ * Options for extending the client handshake process.
214
+ */
215
+ interface ClientHandshakeOptions {
216
+ /**
217
+ * Schema for the metadata that the client sends to the server
218
+ * during the handshake.
219
+ *
220
+ * Needs to match {@link HandshakeRequestMetadata}.
221
+ */
222
+ schema: TSchema;
223
+ /**
224
+ * Gets the {@link HandshakeRequestMetadata} to send to the server.
225
+ */
226
+ get: () => HandshakeRequestMetadata | Promise<HandshakeRequestMetadata>;
227
+ }
228
+ /**
229
+ * Options for extending the server handshake process.
230
+ */
231
+ interface ServerHandshakeOptions {
232
+ /**
233
+ * Schema for the metadata that the server receives from the client
234
+ * during the handshake.
235
+ *
236
+ * Needs to match {@link HandshakeRequestMetadata}.
237
+ */
238
+ requestSchema: TSchema;
239
+ /**
240
+ * Schema for the transformed metadata that is then associated with the
241
+ * client's session.
242
+ *
243
+ * Needs to match {@link ParsedHandshakeMetadata}.
244
+ */
245
+ parsedSchema: TSchema;
246
+ /**
247
+ * Parses the {@link HandshakeRequestMetadata} sent by the client, transforming
248
+ * it into {@link ParsedHandshakeMetadata}.
249
+ *
250
+ * May return `false` if the client should be rejected.
251
+ *
252
+ * @param metadata - The metadata sent by the client.
253
+ * @param session - The session that the client would be associated with.
254
+ * @param isReconnect - Whether the client is reconnecting to the session,
255
+ * or if this is a new session.
256
+ */
257
+ parse: (metadata: HandshakeRequestMetadata, session: Session<Connection>, isReconnect: boolean) => false | ParsedHandshakeMetadata | Promise<false | ParsedHandshakeMetadata>;
258
+ }
259
+ /**
260
+ * Generic Typebox schema for a transport message.
261
+ * @template T The type of the payload.
262
+ * @param {T} t The payload schema.
263
+ * @returns The transport message schema.
264
+ */
265
+ declare const TransportMessageSchema: <T extends TSchema>(t: T) => _sinclair_typebox.TObject<{
266
+ id: _sinclair_typebox.TString;
267
+ from: _sinclair_typebox.TString;
268
+ to: _sinclair_typebox.TString;
269
+ seq: _sinclair_typebox.TInteger;
270
+ ack: _sinclair_typebox.TInteger;
271
+ serviceName: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
272
+ procedureName: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
273
+ streamId: _sinclair_typebox.TString;
274
+ controlFlags: _sinclair_typebox.TInteger;
275
+ payload: T;
276
+ }>;
277
+ /**
278
+ * Defines the schema for an opaque transport message that is agnostic to any
279
+ * procedure/service.
280
+ * @returns The transport message schema.
281
+ */
282
+ declare const OpaqueTransportMessageSchema: _sinclair_typebox.TObject<{
283
+ id: _sinclair_typebox.TString;
284
+ from: _sinclair_typebox.TString;
285
+ to: _sinclair_typebox.TString;
286
+ seq: _sinclair_typebox.TInteger;
287
+ ack: _sinclair_typebox.TInteger;
288
+ serviceName: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
289
+ procedureName: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
290
+ streamId: _sinclair_typebox.TString;
291
+ controlFlags: _sinclair_typebox.TInteger;
292
+ payload: _sinclair_typebox.TUnknown;
293
+ }>;
294
+ /**
295
+ * Represents a transport message. This is the same type as {@link TransportMessageSchema} but
296
+ * we can't statically infer generics from generic Typebox schemas so we have to define it again here.
297
+ *
298
+ * TypeScript can't enforce types when a bitmask is involved, so these are the semantics of
299
+ * `controlFlags`:
300
+ * * If `controlFlags & StreamOpenBit == StreamOpenBit`, `streamId` must be set to a unique value
301
+ * (suggestion: use `nanoid`).
302
+ * * If `controlFlags & StreamOpenBit == StreamOpenBit`, `serviceName` and `procedureName` must be set.
303
+ * * If `controlFlags & StreamClosedBit == StreamClosedBit` and the kind is `stream` or `subscription`,
304
+ * `payload` should be discarded (usually contains a control message).
305
+ * * If `controlFlags & AckBit == AckBit`, the message is an explicit acknowledgement message and doesn't
306
+ * contain any payload that is relevant to the application so should not be delivered.
307
+ * @template Payload The type of the payload.
308
+ */
309
+ interface TransportMessage<Payload = unknown> {
310
+ id: string;
311
+ from: string;
312
+ to: string;
313
+ seq: number;
314
+ ack: number;
315
+ serviceName?: string;
316
+ procedureName?: string;
317
+ streamId: string;
318
+ controlFlags: number;
319
+ payload: Payload;
320
+ }
321
+ type PartialTransportMessage<Payload = unknown> = Omit<TransportMessage<Payload>, 'id' | 'from' | 'to' | 'seq' | 'ack'>;
322
+ /**
323
+ * A type alias for a transport message with an opaque payload.
324
+ * @template T - The type of the opaque payload.
325
+ */
326
+ type OpaqueTransportMessage = TransportMessage;
327
+ type TransportClientId = string;
328
+ /**
329
+ * Checks if the given control flag (usually found in msg.controlFlag) is a stream open message.
330
+ * @param controlFlag - The control flag to check.
331
+ * @returns True if the control flag contains the StreamOpenBit, false otherwise.
332
+ */
333
+ declare function isStreamOpen(controlFlag: number): boolean;
334
+ /**
335
+ * Checks if the given control flag (usually found in msg.controlFlag) is a stream close message.
336
+ * @param controlFlag - The control flag to check.
337
+ * @returns True if the control flag contains the StreamCloseBit, false otherwise.
338
+ */
339
+ declare function isStreamClose(controlFlag: number): boolean;
340
+
341
+ export { Connection as C, HandshakeRequestMetadata as H, Logger as L, MessageMetadata as M, OpaqueTransportMessage as O, PartialTransportMessage as P, SessionOptions as S, TransportClientId as T, Session as a, ParsedHandshakeMetadata as b, TransportMessageSchema as c, OpaqueTransportMessageSchema as d, TransportMessage as e, ClientHandshakeOptions as f, ServerHandshakeOptions as g, isStreamClose as h, isStreamOpen as i, coloredStringLogger as j, jsonLogger as k, bindLogger as l, LogFn as m, stringLogger as s };
@@ -1,2 +1,3 @@
1
- export { g as LogFn, L as Logger, M as MessageMetadata, f as bindLogger, e as coloredStringLogger, j as jsonLogger, s as stringLogger } from '../index-46ed19d8.js';
1
+ export { m as LogFn, L as Logger, M as MessageMetadata, l as bindLogger, j as coloredStringLogger, k as jsonLogger, s as stringLogger } from '../index-2941de8c.js';
2
2
  import '@sinclair/typebox';
3
+ import '../types-3e5768ec.js';
@@ -1,2 +1,3 @@
1
- export { g as LogFn, L as Logger, M as MessageMetadata, f as bindLogger, e as coloredStringLogger, j as jsonLogger, s as stringLogger } from '../index-46ed19d8.js';
1
+ export { m as LogFn, L as Logger, M as MessageMetadata, l as bindLogger, j as coloredStringLogger, k as jsonLogger, s as stringLogger } from '../index-2941de8c.js';
2
2
  import '@sinclair/typebox';
3
+ import '../types-3e5768ec.js';
@@ -28,12 +28,22 @@ __export(router_exports, {
28
28
  ServiceSchema: () => ServiceSchema,
29
29
  UNCAUGHT_ERROR: () => UNCAUGHT_ERROR,
30
30
  createClient: () => createClient,
31
- createServer: () => createServer
31
+ createServer: () => createServer,
32
+ serializeSchema: () => serializeSchema
32
33
  });
33
34
  module.exports = __toCommonJS(router_exports);
34
35
 
35
36
  // router/services.ts
36
37
  var import_typebox = require("@sinclair/typebox");
38
+ function serializeSchema(services) {
39
+ return Object.entries(services).reduce(
40
+ (acc, [name, value]) => {
41
+ acc[name] = value.serialize();
42
+ return acc;
43
+ },
44
+ {}
45
+ );
46
+ }
37
47
  var ServiceSchema = class _ServiceSchema {
38
48
  /**
39
49
  * Factory function for creating a fresh state.
@@ -618,7 +628,8 @@ var ControlMessageCloseSchema = import_typebox3.Type.Object({
618
628
  var ControlMessageHandshakeRequestSchema = import_typebox3.Type.Object({
619
629
  type: import_typebox3.Type.Literal("HANDSHAKE_REQ"),
620
630
  protocolVersion: import_typebox3.Type.String(),
621
- sessionId: import_typebox3.Type.String()
631
+ sessionId: import_typebox3.Type.String(),
632
+ metadata: import_typebox3.Type.Optional(import_typebox3.Type.Unknown())
622
633
  });
623
634
  var ControlMessageHandshakeResponseSchema = import_typebox3.Type.Object({
624
635
  type: import_typebox3.Type.Literal("HANDSHAKE_RESP"),
@@ -1152,6 +1163,13 @@ var RiverServer = class {
1152
1163
  })
1153
1164
  );
1154
1165
  };
1166
+ if (session.metadata === void 0) {
1167
+ log?.error(
1168
+ `(invariant violation) session doesn't have handshake metadata`,
1169
+ session.loggingMetadata
1170
+ );
1171
+ return;
1172
+ }
1155
1173
  let inputHandler;
1156
1174
  const procHasInitMessage = "init" in procedure;
1157
1175
  const serviceContextWithTransportInfo = {
@@ -1159,6 +1177,7 @@ var RiverServer = class {
1159
1177
  to: message.to,
1160
1178
  from: message.from,
1161
1179
  streamId: message.streamId,
1180
+ // we've already validated that the session has handshake metadata
1162
1181
  session
1163
1182
  };
1164
1183
  switch (procedure.type) {
@@ -1334,7 +1353,7 @@ function createServer(transport, services, extendedContext) {
1334
1353
  }
1335
1354
 
1336
1355
  // package.json
1337
- var version = "0.18.4";
1356
+ var version = "0.19.1";
1338
1357
  // Annotate the CommonJS export names for ESM import in node:
1339
1358
  0 && (module.exports = {
1340
1359
  Err,
@@ -1345,5 +1364,6 @@ var version = "0.18.4";
1345
1364
  ServiceSchema,
1346
1365
  UNCAUGHT_ERROR,
1347
1366
  createClient,
1348
- createServer
1367
+ createServer,
1368
+ serializeSchema
1349
1369
  });