@replit/river 0.200.0-rc.2 → 0.200.0-rc.3

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 (84) hide show
  1. package/README.md +21 -20
  2. package/dist/{chunk-S5RL45KH.js → chunk-3CCOX55A.js} +76 -42
  3. package/dist/chunk-3CCOX55A.js.map +1 -0
  4. package/dist/chunk-GWYJZFCW.js +653 -0
  5. package/dist/chunk-GWYJZFCW.js.map +1 -0
  6. package/dist/chunk-O4O2E6DJ.js +277 -0
  7. package/dist/chunk-O4O2E6DJ.js.map +1 -0
  8. package/dist/chunk-QXLXDJD5.js +382 -0
  9. package/dist/chunk-QXLXDJD5.js.map +1 -0
  10. package/dist/{chunk-4VNY34QG.js → chunk-UFMDEG44.js} +24 -18
  11. package/dist/chunk-UFMDEG44.js.map +1 -0
  12. package/dist/{chunk-7CKIN3JT.js → chunk-UIYGPURD.js} +31 -484
  13. package/dist/chunk-UIYGPURD.js.map +1 -0
  14. package/dist/{chunk-QMM35C3H.js → chunk-VXYHC666.js} +1 -1
  15. package/dist/chunk-VXYHC666.js.map +1 -0
  16. package/dist/chunk-WSCAA7VY.js +50 -0
  17. package/dist/chunk-WSCAA7VY.js.map +1 -0
  18. package/dist/chunk-ZQVKFLAB.js +399 -0
  19. package/dist/chunk-ZQVKFLAB.js.map +1 -0
  20. package/dist/client-a84783be.d.ts +49 -0
  21. package/dist/{connection-f900e390.d.ts → connection-320fb130.d.ts} +1 -5
  22. package/dist/connection-d0b488e6.d.ts +11 -0
  23. package/dist/context-c9668e95.d.ts +527 -0
  24. package/dist/logging/index.cjs.map +1 -1
  25. package/dist/logging/index.d.cts +1 -1
  26. package/dist/logging/index.d.ts +1 -1
  27. package/dist/logging/index.js +1 -1
  28. package/dist/{index-10ebd26a.d.ts → message-fd349b27.d.ts} +31 -31
  29. package/dist/router/index.cjs +83 -496
  30. package/dist/router/index.cjs.map +1 -1
  31. package/dist/router/index.d.cts +11 -46
  32. package/dist/router/index.d.ts +11 -46
  33. package/dist/router/index.js +2 -4
  34. package/dist/server-dbad597e.d.ts +42 -0
  35. package/dist/{services-970f97bb.d.ts → services-690e5553.d.ts} +5 -602
  36. package/dist/transport/impls/uds/client.cjs +1240 -1237
  37. package/dist/transport/impls/uds/client.cjs.map +1 -1
  38. package/dist/transport/impls/uds/client.d.cts +4 -3
  39. package/dist/transport/impls/uds/client.d.ts +4 -3
  40. package/dist/transport/impls/uds/client.js +7 -13
  41. package/dist/transport/impls/uds/client.js.map +1 -1
  42. package/dist/transport/impls/uds/server.cjs +1302 -1168
  43. package/dist/transport/impls/uds/server.cjs.map +1 -1
  44. package/dist/transport/impls/uds/server.d.cts +4 -4
  45. package/dist/transport/impls/uds/server.d.ts +4 -4
  46. package/dist/transport/impls/uds/server.js +6 -6
  47. package/dist/transport/impls/ws/client.cjs +981 -986
  48. package/dist/transport/impls/ws/client.cjs.map +1 -1
  49. package/dist/transport/impls/ws/client.d.cts +6 -5
  50. package/dist/transport/impls/ws/client.d.ts +6 -5
  51. package/dist/transport/impls/ws/client.js +6 -7
  52. package/dist/transport/impls/ws/client.js.map +1 -1
  53. package/dist/transport/impls/ws/server.cjs +1183 -1064
  54. package/dist/transport/impls/ws/server.cjs.map +1 -1
  55. package/dist/transport/impls/ws/server.d.cts +4 -4
  56. package/dist/transport/impls/ws/server.d.ts +4 -4
  57. package/dist/transport/impls/ws/server.js +6 -6
  58. package/dist/transport/index.cjs +1435 -1377
  59. package/dist/transport/index.cjs.map +1 -1
  60. package/dist/transport/index.d.cts +4 -26
  61. package/dist/transport/index.d.ts +4 -26
  62. package/dist/transport/index.js +9 -9
  63. package/dist/util/testHelpers.cjs +738 -302
  64. package/dist/util/testHelpers.cjs.map +1 -1
  65. package/dist/util/testHelpers.d.cts +9 -4
  66. package/dist/util/testHelpers.d.ts +9 -4
  67. package/dist/util/testHelpers.js +32 -8
  68. package/dist/util/testHelpers.js.map +1 -1
  69. package/package.json +1 -1
  70. package/dist/chunk-47TFNAY2.js +0 -476
  71. package/dist/chunk-47TFNAY2.js.map +0 -1
  72. package/dist/chunk-4VNY34QG.js.map +0 -1
  73. package/dist/chunk-7CKIN3JT.js.map +0 -1
  74. package/dist/chunk-CZP4LK3F.js +0 -335
  75. package/dist/chunk-CZP4LK3F.js.map +0 -1
  76. package/dist/chunk-DJCW3SKT.js +0 -59
  77. package/dist/chunk-DJCW3SKT.js.map +0 -1
  78. package/dist/chunk-NQWDT6GS.js +0 -347
  79. package/dist/chunk-NQWDT6GS.js.map +0 -1
  80. package/dist/chunk-ONUXWVRC.js +0 -492
  81. package/dist/chunk-ONUXWVRC.js.map +0 -1
  82. package/dist/chunk-QMM35C3H.js.map +0 -1
  83. package/dist/chunk-S5RL45KH.js.map +0 -1
  84. package/dist/connection-3f117047.d.ts +0 -17
@@ -4,14 +4,14 @@ import {
4
4
  ReadStreamImpl,
5
5
  UNCAUGHT_ERROR_CODE,
6
6
  WriteStreamImpl
7
- } from "../chunk-7CKIN3JT.js";
7
+ } from "../chunk-UIYGPURD.js";
8
8
  import {
9
- Session,
9
+ SessionStateGraph,
10
10
  defaultTransportOptions
11
- } from "../chunk-NQWDT6GS.js";
11
+ } from "../chunk-GWYJZFCW.js";
12
12
  import {
13
13
  coerceErrorString
14
- } from "../chunk-S5RL45KH.js";
14
+ } from "../chunk-3CCOX55A.js";
15
15
  import "../chunk-4PVU7J25.js";
16
16
 
17
17
  // util/testHelpers.ts
@@ -84,18 +84,22 @@ function catchProcError(err) {
84
84
  }
85
85
  var testingSessionOptions = defaultTransportOptions;
86
86
  function dummySession() {
87
- return new Session(
88
- void 0,
87
+ return SessionStateGraph.entrypoints.NoConnection(
89
88
  "client",
90
89
  "server",
90
+ {
91
+ onSessionGracePeriodElapsed: () => {
92
+ }
93
+ },
91
94
  testingSessionOptions
92
95
  );
93
96
  }
94
97
  function dummyCtx(state, session, extendedContext) {
95
98
  return {
96
99
  ...extendedContext,
97
- from: session.from,
98
100
  state,
101
+ sessionId: session.id,
102
+ from: session.from,
99
103
  metadata: {},
100
104
  abortController: new AbortController(),
101
105
  clientAbortSignal: new AbortController().signal,
@@ -173,20 +177,40 @@ function asClientUpload(state, proc, init, extendedContext, session = dummySessi
173
177
  return [inputPipe.writer, () => result];
174
178
  }
175
179
  var getUnixSocketPath = () => {
176
- return process.platform === "win32" ? `\\\\?\\pipe\\${nanoid()}` : `/tmp/${nanoid()}.sock`;
180
+ return `/tmp/${nanoid()}.sock`;
177
181
  };
182
+ function getTransportConnections(transport) {
183
+ const connections = [];
184
+ for (const session of transport.sessions.values()) {
185
+ if (session.state === "Connected" /* Connected */) {
186
+ connections.push(session.conn);
187
+ }
188
+ }
189
+ return connections;
190
+ }
191
+ function numberOfConnections(transport) {
192
+ return getTransportConnections(transport).length;
193
+ }
194
+ function closeAllConnections(transport) {
195
+ for (const conn of getTransportConnections(transport)) {
196
+ conn.close();
197
+ }
198
+ }
178
199
  export {
179
200
  asClientRpc,
180
201
  asClientStream,
181
202
  asClientSubscription,
182
203
  asClientUpload,
204
+ closeAllConnections,
183
205
  createDummyTransportMessage,
184
206
  createLocalWebSocketClient,
185
207
  createWebSocketServer,
186
208
  dummySession,
187
209
  getIteratorFromStream,
210
+ getTransportConnections,
188
211
  getUnixSocketPath,
189
212
  iterNext,
213
+ numberOfConnections,
190
214
  onUdsServeReady,
191
215
  onWsServerReady,
192
216
  payloadToTransportMessage,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../util/testHelpers.ts"],"sourcesContent":["import NodeWs, { WebSocketServer } from 'ws';\nimport http from 'node:http';\nimport {\n Err,\n Ok,\n PayloadType,\n Procedure,\n Result,\n ProcedureErrorSchemaType,\n InputReaderErrorSchema,\n OutputReaderErrorSchema,\n ServiceContext,\n ProcedureHandlerContext,\n UNCAUGHT_ERROR_CODE,\n} from '../router';\nimport { Static } from '@sinclair/typebox';\nimport { nanoid } from 'nanoid';\nimport net from 'node:net';\nimport {\n OpaqueTransportMessage,\n PartialTransportMessage,\n} from '../transport/message';\nimport { coerceErrorString } from './stringify';\nimport { Connection, Session, SessionOptions } from '../transport/session';\nimport { Transport } from '../transport/transport';\nimport {\n ReadStream,\n ReadStreamImpl,\n WriteStream,\n WriteStreamImpl,\n} from '../router/streams';\nimport { WsLike } from '../transport/impls/ws/wslike';\nimport { defaultTransportOptions } from '../transport/options';\nimport { BaseErrorSchemaType } from '../router/result';\n\n/**\n * Creates a WebSocket client that connects to a local server at the specified port.\n * This should only be used for testing.\n * @param port - The port number to connect to.\n * @returns A Promise that resolves to a WebSocket instance.\n */\nexport function createLocalWebSocketClient(port: number): WsLike {\n const sock = new NodeWs(`ws://localhost:${port}`);\n sock.binaryType = 'arraybuffer';\n\n return sock;\n}\n\n/**\n * Creates a WebSocket server instance using the provided HTTP server.\n * Only used as helper for testing.\n * @param server - The HTTP server instance to use for the WebSocket server.\n * @returns A Promise that resolves to the created WebSocket server instance.\n */\nexport function createWebSocketServer(server: http.Server) {\n return new WebSocketServer({ server });\n}\n\n/**\n * Starts listening on the given server and returns the automatically allocated port number.\n * This should only be used for testing.\n * @param server - The http server to listen on.\n * @returns A promise that resolves with the allocated port number.\n * @throws An error if a port cannot be allocated.\n */\nexport function onWsServerReady(server: http.Server): Promise<number> {\n return new Promise((resolve, reject) => {\n server.listen(() => {\n const addr = server.address();\n if (typeof addr === 'object' && addr) {\n resolve(addr.port);\n } else {\n reject(new Error(\"couldn't find a port to allocate\"));\n }\n });\n });\n}\n\nexport function onUdsServeReady(\n server: net.Server,\n path: string,\n): Promise<void> {\n return new Promise<void>((resolve) => {\n server.listen(path, resolve);\n });\n}\n\nexport function getIteratorFromStream<T, E extends Static<BaseErrorSchemaType>>(\n readStream: ReadStream<T, E>,\n) {\n return readStream[Symbol.asyncIterator]();\n}\n\n/**\n * Retrieves the next value from an async iterable iterator.\n * @param iter The async iterable iterator.\n * @returns A promise that resolves to the next value from the iterator.\n */\nexport async function iterNext<T>(iter: {\n next(): Promise<\n | {\n done: false;\n value: T;\n }\n | {\n done: true;\n value: undefined;\n }\n >;\n}) {\n return await iter.next().then((res) => res.value as T);\n}\n\nexport function payloadToTransportMessage<Payload>(\n payload: Payload,\n): PartialTransportMessage<Payload> {\n return {\n streamId: 'stream',\n controlFlags: 0,\n payload,\n };\n}\n\nexport function createDummyTransportMessage() {\n return payloadToTransportMessage({\n msg: 'cool',\n test: Math.random(),\n });\n}\n\n/**\n * Waits for a message on the transport.\n * @param {Transport} t - The transport to listen to.\n * @param filter - An optional filter function to apply to the received messages.\n * @returns A promise that resolves with the payload of the first message that passes the filter.\n */\nexport async function waitForMessage(\n t: Transport<Connection>,\n filter?: (msg: OpaqueTransportMessage) => boolean,\n rejectMismatch?: boolean,\n) {\n return new Promise((resolve, reject) => {\n function cleanup() {\n t.removeEventListener('message', onMessage);\n }\n\n function onMessage(msg: OpaqueTransportMessage) {\n if (!filter || filter(msg)) {\n cleanup();\n resolve(msg.payload);\n } else if (rejectMismatch) {\n cleanup();\n reject(new Error('message didnt match the filter'));\n }\n }\n\n t.addEventListener('message', onMessage);\n });\n}\n\nfunction catchProcError(err: unknown) {\n const errorMsg = coerceErrorString(err);\n return Err({ code: UNCAUGHT_ERROR_CODE, message: errorMsg });\n}\n\nexport const testingSessionOptions: SessionOptions = defaultTransportOptions;\n\nexport function dummySession() {\n return new Session<Connection>(\n undefined,\n 'client',\n 'server',\n testingSessionOptions,\n );\n}\n\nfunction dummyCtx<State>(\n state: State,\n session: Session<Connection>,\n extendedContext?: Omit<ServiceContext, 'state'>,\n): ProcedureHandlerContext<State> {\n return {\n ...extendedContext,\n from: session.from,\n state,\n metadata: {},\n abortController: new AbortController(),\n clientAbortSignal: new AbortController().signal,\n onRequestFinished: () => undefined,\n };\n}\n\nexport function asClientRpc<\n State extends object,\n Init extends PayloadType,\n Output extends PayloadType,\n Err extends ProcedureErrorSchemaType,\n>(\n state: State,\n proc: Procedure<State, 'rpc', Init, null, Output, Err>,\n extendedContext?: Omit<ServiceContext, 'state'>,\n session: Session<Connection> = dummySession(),\n) {\n return async (\n msg: Static<Init>,\n ): Promise<\n Result<Static<Output>, Static<Err> | Static<typeof OutputReaderErrorSchema>>\n > => {\n return proc\n .handler(dummyCtx(state, session, extendedContext), msg)\n .catch(catchProcError);\n };\n}\n\nfunction createOutputPipe<\n Output extends PayloadType,\n Err extends ProcedureErrorSchemaType,\n>(): {\n reader: ReadStream<\n Static<Output>,\n Static<Err> | Static<typeof OutputReaderErrorSchema>\n >;\n writer: WriteStream<Result<Static<Output>, Static<Err>>>;\n} {\n const reader = new ReadStreamImpl<\n Static<Output>,\n Static<Err> | Static<typeof OutputReaderErrorSchema>\n >(() => {\n // Make it async to simulate request going over the wire\n // using promises so that we don't get affected by fake timers.\n void Promise.resolve().then(() => {\n writer.triggerCloseRequest();\n });\n });\n const writer = new WriteStreamImpl<Result<Static<Output>, Static<Err>>>(\n (v) => {\n reader.pushValue(v);\n },\n );\n writer.onClose(() => {\n // Make it async to simulate request going over the wire\n // using promises so that we don't get affected by fake timers.\n void Promise.resolve().then(() => {\n reader.triggerClose();\n });\n });\n\n return { reader, writer };\n}\n\nfunction createInputPipe<Input extends PayloadType>(): {\n reader: ReadStream<Static<Input>, Static<typeof InputReaderErrorSchema>>;\n writer: WriteStream<Static<Input>>;\n} {\n const reader = new ReadStreamImpl<\n Static<Input>,\n Static<typeof InputReaderErrorSchema>\n >(() => {\n // Make it async to simulate request going over the wire\n // using promises so that we don't get affected by fake timers.\n void Promise.resolve().then(() => {\n writer.triggerCloseRequest();\n });\n });\n const writer = new WriteStreamImpl<Static<Input>>((v) => {\n reader.pushValue(Ok(v));\n });\n writer.onClose(() => {\n // Make it async to simulate request going over the wire\n // using promises so that we don't get affected by fake timers.\n void Promise.resolve().then(() => {\n reader.triggerClose();\n });\n });\n\n return { reader, writer };\n}\n\nexport function asClientStream<\n State extends object,\n Init extends PayloadType,\n Input extends PayloadType,\n Output extends PayloadType,\n Err extends ProcedureErrorSchemaType,\n>(\n state: State,\n proc: Procedure<State, 'stream', Init, Input, Output, Err>,\n init?: Static<Init>,\n extendedContext?: Omit<ServiceContext, 'state'>,\n session: Session<Connection> = dummySession(),\n): [WriteStream<Static<Input>>, ReadStream<Static<Output>, Static<Err>>] {\n const inputPipe = createInputPipe<Input>();\n const outputPipe = createOutputPipe<Output, Err>();\n\n void proc\n .handler(\n dummyCtx(state, session, extendedContext),\n init ?? {},\n inputPipe.reader,\n outputPipe.writer,\n )\n .catch((err: unknown) => outputPipe.writer.write(catchProcError(err)));\n\n return [inputPipe.writer, outputPipe.reader];\n}\n\nexport function asClientSubscription<\n State extends object,\n Init extends PayloadType,\n Output extends PayloadType,\n Err extends ProcedureErrorSchemaType,\n>(\n state: State,\n proc: Procedure<State, 'subscription', Init, null, Output, Err>,\n extendedContext?: Omit<ServiceContext, 'state'>,\n session: Session<Connection> = dummySession(),\n): (msg: Static<Init>) => ReadStream<Static<Output>, Static<Err>> {\n const outputPipe = createOutputPipe<Output, Err>();\n\n return (msg: Static<Init>) => {\n void proc\n .handler(\n dummyCtx(state, session, extendedContext),\n msg,\n outputPipe.writer,\n )\n .catch((err: unknown) => outputPipe.writer.write(catchProcError(err)));\n\n return outputPipe.reader;\n };\n}\n\nexport function asClientUpload<\n State extends object,\n Init extends PayloadType,\n Input extends PayloadType,\n Output extends PayloadType,\n Err extends ProcedureErrorSchemaType,\n>(\n state: State,\n proc: Procedure<State, 'upload', Init, Input, Output, Err>,\n init?: Static<Init>,\n extendedContext?: Omit<ServiceContext, 'state'>,\n session: Session<Connection> = dummySession(),\n): [\n WriteStream<Static<Input>>,\n () => Promise<Result<Static<Output>, Static<Err>>>,\n] {\n const inputPipe = createInputPipe<Input>();\n const result = proc\n .handler(\n dummyCtx(state, session, extendedContext),\n init ?? {},\n inputPipe.reader,\n )\n .catch(catchProcError);\n\n return [inputPipe.writer, () => result];\n}\n\nexport const getUnixSocketPath = () => {\n // https://nodejs.org/api/net.html#identifying-paths-for-ipc-connections\n return process.platform === 'win32'\n ? `\\\\\\\\?\\\\pipe\\\\${nanoid()}`\n : `/tmp/${nanoid()}.sock`;\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../util/testHelpers.ts"],"sourcesContent":["import NodeWs, { WebSocketServer } from 'ws';\nimport http from 'node:http';\nimport {\n Err,\n Ok,\n PayloadType,\n Procedure,\n Result,\n ProcedureErrorSchemaType,\n InputReaderErrorSchema,\n OutputReaderErrorSchema,\n ServiceContext,\n ProcedureHandlerContext,\n UNCAUGHT_ERROR_CODE,\n} from '../router';\nimport { Static } from '@sinclair/typebox';\nimport { nanoid } from 'nanoid';\nimport net from 'node:net';\nimport {\n OpaqueTransportMessage,\n PartialTransportMessage,\n} from '../transport/message';\nimport { coerceErrorString } from './stringify';\nimport { Transport } from '../transport/transport';\nimport {\n ReadStream,\n ReadStreamImpl,\n WriteStream,\n WriteStreamImpl,\n} from '../router/streams';\nimport { WsLike } from '../transport/impls/ws/wslike';\nimport { defaultTransportOptions } from '../transport/options';\nimport { BaseErrorSchemaType } from '../router/result';\nimport { Connection } from '../transport/connection';\nimport {\n Session,\n SessionOptions,\n SessionState,\n} from '../transport/sessionStateMachine/common';\nimport { SessionStateGraph } from '../transport/sessionStateMachine';\n\n/**\n * Creates a WebSocket client that connects to a local server at the specified port.\n * This should only be used for testing.\n * @param port - The port number to connect to.\n * @returns A Promise that resolves to a WebSocket instance.\n */\nexport function createLocalWebSocketClient(port: number): WsLike {\n const sock = new NodeWs(`ws://localhost:${port}`);\n sock.binaryType = 'arraybuffer';\n\n return sock;\n}\n\n/**\n * Creates a WebSocket server instance using the provided HTTP server.\n * Only used as helper for testing.\n * @param server - The HTTP server instance to use for the WebSocket server.\n * @returns A Promise that resolves to the created WebSocket server instance.\n */\nexport function createWebSocketServer(server: http.Server) {\n return new WebSocketServer({ server });\n}\n\n/**\n * Starts listening on the given server and returns the automatically allocated port number.\n * This should only be used for testing.\n * @param server - The http server to listen on.\n * @returns A promise that resolves with the allocated port number.\n * @throws An error if a port cannot be allocated.\n */\nexport function onWsServerReady(server: http.Server): Promise<number> {\n return new Promise((resolve, reject) => {\n server.listen(() => {\n const addr = server.address();\n if (typeof addr === 'object' && addr) {\n resolve(addr.port);\n } else {\n reject(new Error(\"couldn't find a port to allocate\"));\n }\n });\n });\n}\n\nexport function onUdsServeReady(\n server: net.Server,\n path: string,\n): Promise<void> {\n return new Promise<void>((resolve) => {\n server.listen(path, resolve);\n });\n}\n\nexport function getIteratorFromStream<T, E extends Static<BaseErrorSchemaType>>(\n readStream: ReadStream<T, E>,\n) {\n return readStream[Symbol.asyncIterator]();\n}\n\n/**\n * Retrieves the next value from an async iterable iterator.\n * @param iter The async iterable iterator.\n * @returns A promise that resolves to the next value from the iterator.\n */\nexport async function iterNext<T>(iter: {\n next(): Promise<\n | {\n done: false;\n value: T;\n }\n | {\n done: true;\n value: undefined;\n }\n >;\n}) {\n return await iter.next().then((res) => res.value as T);\n}\n\nexport function payloadToTransportMessage<Payload>(\n payload: Payload,\n): PartialTransportMessage<Payload> {\n return {\n streamId: 'stream',\n controlFlags: 0,\n payload,\n };\n}\n\nexport function createDummyTransportMessage() {\n return payloadToTransportMessage({\n msg: 'cool',\n test: Math.random(),\n });\n}\n\n/**\n * Waits for a message on the transport.\n * @param {Transport} t - The transport to listen to.\n * @param filter - An optional filter function to apply to the received messages.\n * @returns A promise that resolves with the payload of the first message that passes the filter.\n */\nexport async function waitForMessage(\n t: Transport<Connection>,\n filter?: (msg: OpaqueTransportMessage) => boolean,\n rejectMismatch?: boolean,\n) {\n return new Promise((resolve, reject) => {\n function cleanup() {\n t.removeEventListener('message', onMessage);\n }\n\n function onMessage(msg: OpaqueTransportMessage) {\n if (!filter || filter(msg)) {\n cleanup();\n resolve(msg.payload);\n } else if (rejectMismatch) {\n cleanup();\n reject(new Error('message didnt match the filter'));\n }\n }\n\n t.addEventListener('message', onMessage);\n });\n}\n\nfunction catchProcError(err: unknown) {\n const errorMsg = coerceErrorString(err);\n return Err({ code: UNCAUGHT_ERROR_CODE, message: errorMsg });\n}\n\nexport const testingSessionOptions: SessionOptions = defaultTransportOptions;\n\nexport function dummySession() {\n return SessionStateGraph.entrypoints.NoConnection(\n 'client',\n 'server',\n {\n onSessionGracePeriodElapsed: () => {\n /* noop */\n },\n },\n testingSessionOptions,\n );\n}\n\nfunction dummyCtx<State>(\n state: State,\n session: Session<Connection>,\n extendedContext?: Omit<ServiceContext, 'state'>,\n): ProcedureHandlerContext<State> {\n return {\n ...extendedContext,\n state,\n sessionId: session.id,\n from: session.from,\n metadata: {},\n abortController: new AbortController(),\n clientAbortSignal: new AbortController().signal,\n onRequestFinished: () => undefined,\n };\n}\n\nexport function asClientRpc<\n State extends object,\n Init extends PayloadType,\n Output extends PayloadType,\n Err extends ProcedureErrorSchemaType,\n>(\n state: State,\n proc: Procedure<State, 'rpc', Init, null, Output, Err>,\n extendedContext?: Omit<ServiceContext, 'state'>,\n session: Session<Connection> = dummySession(),\n) {\n return async (\n msg: Static<Init>,\n ): Promise<\n Result<Static<Output>, Static<Err> | Static<typeof OutputReaderErrorSchema>>\n > => {\n return proc\n .handler(dummyCtx(state, session, extendedContext), msg)\n .catch(catchProcError);\n };\n}\n\nfunction createOutputPipe<\n Output extends PayloadType,\n Err extends ProcedureErrorSchemaType,\n>(): {\n reader: ReadStream<\n Static<Output>,\n Static<Err> | Static<typeof OutputReaderErrorSchema>\n >;\n writer: WriteStream<Result<Static<Output>, Static<Err>>>;\n} {\n const reader = new ReadStreamImpl<\n Static<Output>,\n Static<Err> | Static<typeof OutputReaderErrorSchema>\n >(() => {\n // Make it async to simulate request going over the wire\n // using promises so that we don't get affected by fake timers.\n void Promise.resolve().then(() => {\n writer.triggerCloseRequest();\n });\n });\n const writer = new WriteStreamImpl<Result<Static<Output>, Static<Err>>>(\n (v) => {\n reader.pushValue(v);\n },\n );\n writer.onClose(() => {\n // Make it async to simulate request going over the wire\n // using promises so that we don't get affected by fake timers.\n void Promise.resolve().then(() => {\n reader.triggerClose();\n });\n });\n\n return { reader, writer };\n}\n\nfunction createInputPipe<Input extends PayloadType>(): {\n reader: ReadStream<Static<Input>, Static<typeof InputReaderErrorSchema>>;\n writer: WriteStream<Static<Input>>;\n} {\n const reader = new ReadStreamImpl<\n Static<Input>,\n Static<typeof InputReaderErrorSchema>\n >(() => {\n // Make it async to simulate request going over the wire\n // using promises so that we don't get affected by fake timers.\n void Promise.resolve().then(() => {\n writer.triggerCloseRequest();\n });\n });\n const writer = new WriteStreamImpl<Static<Input>>((v) => {\n reader.pushValue(Ok(v));\n });\n writer.onClose(() => {\n // Make it async to simulate request going over the wire\n // using promises so that we don't get affected by fake timers.\n void Promise.resolve().then(() => {\n reader.triggerClose();\n });\n });\n\n return { reader, writer };\n}\n\nexport function asClientStream<\n State extends object,\n Init extends PayloadType,\n Input extends PayloadType,\n Output extends PayloadType,\n Err extends ProcedureErrorSchemaType,\n>(\n state: State,\n proc: Procedure<State, 'stream', Init, Input, Output, Err>,\n init?: Static<Init>,\n extendedContext?: Omit<ServiceContext, 'state'>,\n session: Session<Connection> = dummySession(),\n): [WriteStream<Static<Input>>, ReadStream<Static<Output>, Static<Err>>] {\n const inputPipe = createInputPipe<Input>();\n const outputPipe = createOutputPipe<Output, Err>();\n\n void proc\n .handler(\n dummyCtx(state, session, extendedContext),\n init ?? {},\n inputPipe.reader,\n outputPipe.writer,\n )\n .catch((err: unknown) => outputPipe.writer.write(catchProcError(err)));\n\n return [inputPipe.writer, outputPipe.reader];\n}\n\nexport function asClientSubscription<\n State extends object,\n Init extends PayloadType,\n Output extends PayloadType,\n Err extends ProcedureErrorSchemaType,\n>(\n state: State,\n proc: Procedure<State, 'subscription', Init, null, Output, Err>,\n extendedContext?: Omit<ServiceContext, 'state'>,\n session: Session<Connection> = dummySession(),\n): (msg: Static<Init>) => ReadStream<Static<Output>, Static<Err>> {\n const outputPipe = createOutputPipe<Output, Err>();\n\n return (msg: Static<Init>) => {\n void proc\n .handler(\n dummyCtx(state, session, extendedContext),\n msg,\n outputPipe.writer,\n )\n .catch((err: unknown) => outputPipe.writer.write(catchProcError(err)));\n\n return outputPipe.reader;\n };\n}\n\nexport function asClientUpload<\n State extends object,\n Init extends PayloadType,\n Input extends PayloadType,\n Output extends PayloadType,\n Err extends ProcedureErrorSchemaType,\n>(\n state: State,\n proc: Procedure<State, 'upload', Init, Input, Output, Err>,\n init?: Static<Init>,\n extendedContext?: Omit<ServiceContext, 'state'>,\n session: Session<Connection> = dummySession(),\n): [\n WriteStream<Static<Input>>,\n () => Promise<Result<Static<Output>, Static<Err>>>,\n] {\n const inputPipe = createInputPipe<Input>();\n const result = proc\n .handler(\n dummyCtx(state, session, extendedContext),\n init ?? {},\n inputPipe.reader,\n )\n .catch(catchProcError);\n\n return [inputPipe.writer, () => result];\n}\n\nexport const getUnixSocketPath = () => {\n return `/tmp/${nanoid()}.sock`;\n};\n\nexport function getTransportConnections<ConnType extends Connection>(\n transport: Transport<ConnType>,\n): Array<ConnType> {\n const connections = [];\n for (const session of transport.sessions.values()) {\n if (session.state === SessionState.Connected) {\n connections.push(session.conn);\n }\n }\n\n return connections;\n}\n\nexport function numberOfConnections<ConnType extends Connection>(\n transport: Transport<ConnType>,\n): number {\n return getTransportConnections(transport).length;\n}\n\nexport function closeAllConnections<ConnType extends Connection>(\n transport: Transport<ConnType>,\n) {\n for (const conn of getTransportConnections(transport)) {\n conn.close();\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@replit/river",
3
3
  "description": "It's like tRPC but... with JSON Schema Support, duplex streaming and support for service multiplexing. Transport agnostic!",
4
- "version": "0.200.0-rc.2",
4
+ "version": "0.200.0-rc.3",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  ".": {
@@ -1,476 +0,0 @@
1
- import {
2
- ProtocolError,
3
- Transport
4
- } from "./chunk-ONUXWVRC.js";
5
- import {
6
- defaultClientTransportOptions
7
- } from "./chunk-NQWDT6GS.js";
8
- import {
9
- ControlMessageHandshakeResponseSchema,
10
- SESSION_STATE_MISMATCH,
11
- coerceErrorString,
12
- getPropagationContext,
13
- handshakeRequestMessage,
14
- tracing_default
15
- } from "./chunk-S5RL45KH.js";
16
-
17
- // transport/client.ts
18
- import { SpanStatusCode } from "@opentelemetry/api";
19
-
20
- // transport/rateLimit.ts
21
- var LeakyBucketRateLimit = class {
22
- budgetConsumed;
23
- intervalHandles;
24
- options;
25
- constructor(options) {
26
- this.options = options;
27
- this.budgetConsumed = /* @__PURE__ */ new Map();
28
- this.intervalHandles = /* @__PURE__ */ new Map();
29
- }
30
- getBackoffMs(user) {
31
- if (!this.budgetConsumed.has(user))
32
- return 0;
33
- const exponent = Math.max(0, this.getBudgetConsumed(user) - 1);
34
- const jitter = Math.floor(Math.random() * this.options.maxJitterMs);
35
- const backoffMs = Math.min(
36
- this.options.baseIntervalMs * 2 ** exponent,
37
- this.options.maxBackoffMs
38
- );
39
- return backoffMs + jitter;
40
- }
41
- get totalBudgetRestoreTime() {
42
- return this.options.budgetRestoreIntervalMs * this.options.attemptBudgetCapacity;
43
- }
44
- consumeBudget(user) {
45
- this.stopLeak(user);
46
- this.budgetConsumed.set(user, this.getBudgetConsumed(user) + 1);
47
- }
48
- getBudgetConsumed(user) {
49
- return this.budgetConsumed.get(user) ?? 0;
50
- }
51
- hasBudget(user) {
52
- return this.getBudgetConsumed(user) < this.options.attemptBudgetCapacity;
53
- }
54
- startRestoringBudget(user) {
55
- if (this.intervalHandles.has(user)) {
56
- return;
57
- }
58
- const restoreBudgetForUser = () => {
59
- const currentBudget = this.budgetConsumed.get(user);
60
- if (!currentBudget) {
61
- this.stopLeak(user);
62
- return;
63
- }
64
- const newBudget = currentBudget - 1;
65
- if (newBudget === 0) {
66
- this.budgetConsumed.delete(user);
67
- return;
68
- }
69
- this.budgetConsumed.set(user, newBudget);
70
- };
71
- const intervalHandle = setInterval(
72
- restoreBudgetForUser,
73
- this.options.budgetRestoreIntervalMs
74
- );
75
- this.intervalHandles.set(user, intervalHandle);
76
- }
77
- stopLeak(user) {
78
- if (!this.intervalHandles.has(user)) {
79
- return;
80
- }
81
- clearInterval(this.intervalHandles.get(user));
82
- this.intervalHandles.delete(user);
83
- }
84
- close() {
85
- for (const user of this.intervalHandles.keys()) {
86
- this.stopLeak(user);
87
- }
88
- }
89
- };
90
-
91
- // transport/client.ts
92
- import { Value } from "@sinclair/typebox/value";
93
- var ClientTransport = class extends Transport {
94
- /**
95
- * The options for this transport.
96
- */
97
- options;
98
- /**
99
- * The map of reconnect promises for each client ID.
100
- */
101
- inflightConnectionPromises;
102
- retryBudget;
103
- /**
104
- * A flag indicating whether the transport should automatically reconnect
105
- * when a connection is dropped.
106
- * Realistically, this should always be true for clients unless you are writing
107
- * tests or a special case where you don't want to reconnect.
108
- */
109
- reconnectOnConnectionDrop = true;
110
- /**
111
- * Optional handshake options for this client.
112
- */
113
- handshakeExtensions;
114
- constructor(clientId, providedOptions) {
115
- super(clientId, providedOptions);
116
- this.options = {
117
- ...defaultClientTransportOptions,
118
- ...providedOptions
119
- };
120
- this.inflightConnectionPromises = /* @__PURE__ */ new Map();
121
- this.retryBudget = new LeakyBucketRateLimit(this.options);
122
- }
123
- extendHandshake(options) {
124
- this.handshakeExtensions = options;
125
- }
126
- handleConnection(conn, to) {
127
- if (this.getStatus() !== "open")
128
- return;
129
- let session = void 0;
130
- const handshakeTimeout = setTimeout(() => {
131
- if (session)
132
- return;
133
- this.log?.warn(
134
- `connection to ${to} timed out waiting for handshake, closing`,
135
- { ...conn.loggingMetadata, clientId: this.clientId, connectedTo: to }
136
- );
137
- conn.close();
138
- }, this.options.sessionDisconnectGraceMs);
139
- const handshakeHandler = (data) => {
140
- const maybeSession = this.receiveHandshakeResponseMessage(data, conn);
141
- clearTimeout(handshakeTimeout);
142
- if (!maybeSession) {
143
- conn.close();
144
- return;
145
- } else {
146
- session = maybeSession;
147
- }
148
- conn.removeDataListener(handshakeHandler);
149
- conn.addDataListener((data2) => {
150
- const parsed = this.parseMsg(data2, conn);
151
- if (!parsed) {
152
- conn.telemetry?.span.setStatus({
153
- code: SpanStatusCode.ERROR,
154
- message: "message parse failure"
155
- });
156
- conn.close();
157
- return;
158
- }
159
- this.handleMsg(parsed, conn);
160
- });
161
- };
162
- conn.addDataListener(handshakeHandler);
163
- conn.addCloseListener(() => {
164
- if (session) {
165
- this.onDisconnect(conn, session);
166
- }
167
- const willReconnect = this.reconnectOnConnectionDrop && this.getStatus() === "open";
168
- this.log?.info(
169
- `connection to ${to} disconnected` + (willReconnect ? ", reconnecting" : ""),
170
- {
171
- ...conn.loggingMetadata,
172
- ...session?.loggingMetadata,
173
- clientId: this.clientId,
174
- connectedTo: to
175
- }
176
- );
177
- this.inflightConnectionPromises.delete(to);
178
- if (this.reconnectOnConnectionDrop) {
179
- void this.connect(to);
180
- }
181
- });
182
- conn.addErrorListener((err) => {
183
- conn.telemetry?.span.setStatus({
184
- code: SpanStatusCode.ERROR,
185
- message: "connection error"
186
- });
187
- this.log?.warn(
188
- `error in connection to ${to}: ${coerceErrorString(err)}`,
189
- {
190
- ...conn.loggingMetadata,
191
- ...session?.loggingMetadata,
192
- clientId: this.clientId,
193
- connectedTo: to
194
- }
195
- );
196
- });
197
- }
198
- receiveHandshakeResponseMessage(data, conn) {
199
- const parsed = this.parseMsg(data, conn);
200
- if (!parsed) {
201
- conn.telemetry?.span.setStatus({
202
- code: SpanStatusCode.ERROR,
203
- message: "non-transport message"
204
- });
205
- this.protocolError(
206
- ProtocolError.HandshakeFailed,
207
- "received non-transport message"
208
- );
209
- return false;
210
- }
211
- if (!Value.Check(ControlMessageHandshakeResponseSchema, parsed.payload)) {
212
- conn.telemetry?.span.setStatus({
213
- code: SpanStatusCode.ERROR,
214
- message: "invalid handshake response"
215
- });
216
- this.log?.warn(`received invalid handshake resp`, {
217
- ...conn.loggingMetadata,
218
- clientId: this.clientId,
219
- connectedTo: parsed.from,
220
- transportMessage: parsed,
221
- validationErrors: [
222
- ...Value.Errors(
223
- ControlMessageHandshakeResponseSchema,
224
- parsed.payload
225
- )
226
- ]
227
- });
228
- this.protocolError(
229
- ProtocolError.HandshakeFailed,
230
- "invalid handshake resp"
231
- );
232
- return false;
233
- }
234
- const previousSession = this.sessions.get(parsed.from);
235
- if (!parsed.payload.status.ok) {
236
- if (parsed.payload.status.reason === SESSION_STATE_MISMATCH) {
237
- if (previousSession) {
238
- this.deleteSession({
239
- session: previousSession,
240
- closeHandshakingConnection: true
241
- });
242
- }
243
- conn.telemetry?.span.setStatus({
244
- code: SpanStatusCode.ERROR,
245
- message: parsed.payload.status.reason
246
- });
247
- } else {
248
- conn.telemetry?.span.setStatus({
249
- code: SpanStatusCode.ERROR,
250
- message: "handshake rejected"
251
- });
252
- }
253
- this.log?.warn(
254
- `received handshake rejection: ${parsed.payload.status.reason}`,
255
- {
256
- ...conn.loggingMetadata,
257
- clientId: this.clientId,
258
- connectedTo: parsed.from,
259
- transportMessage: parsed
260
- }
261
- );
262
- this.protocolError(
263
- ProtocolError.HandshakeFailed,
264
- parsed.payload.status.reason
265
- );
266
- return false;
267
- }
268
- if (previousSession?.advertisedSessionId && previousSession.advertisedSessionId !== parsed.payload.status.sessionId) {
269
- this.deleteSession({
270
- session: previousSession,
271
- closeHandshakingConnection: true
272
- });
273
- conn.telemetry?.span.setStatus({
274
- code: SpanStatusCode.ERROR,
275
- message: "session id mismatch"
276
- });
277
- this.log?.warn(`handshake from ${parsed.from} session id mismatch`, {
278
- ...conn.loggingMetadata,
279
- clientId: this.clientId,
280
- connectedTo: parsed.from,
281
- transportMessage: parsed
282
- });
283
- this.protocolError(ProtocolError.HandshakeFailed, "session id mismatch");
284
- return false;
285
- }
286
- this.log?.debug(`handshake from ${parsed.from} ok`, {
287
- ...conn.loggingMetadata,
288
- clientId: this.clientId,
289
- connectedTo: parsed.from,
290
- transportMessage: parsed
291
- });
292
- const { session, isTransparentReconnect } = this.getOrCreateSession({
293
- to: parsed.from,
294
- conn,
295
- sessionId: parsed.payload.status.sessionId
296
- });
297
- this.onConnect(conn, session, isTransparentReconnect);
298
- this.retryBudget.startRestoringBudget(session.to);
299
- return session;
300
- }
301
- /**
302
- * Manually attempts to connect to a client.
303
- * @param to The client ID of the node to connect to.
304
- */
305
- async connect(to) {
306
- if (this.connections.has(to)) {
307
- this.log?.info(`already connected to ${to}, skipping connect attempt`, {
308
- clientId: this.clientId,
309
- connectedTo: to
310
- });
311
- return;
312
- }
313
- const canProceedWithConnection = () => this.getStatus() === "open";
314
- if (!canProceedWithConnection()) {
315
- this.log?.info(
316
- `transport state is no longer open, cancelling attempt to connect to ${to}`,
317
- { clientId: this.clientId, connectedTo: to }
318
- );
319
- return;
320
- }
321
- let reconnectPromise = this.inflightConnectionPromises.get(to);
322
- if (!reconnectPromise) {
323
- if (!this.retryBudget.hasBudget(to)) {
324
- const budgetConsumed = this.retryBudget.getBudgetConsumed(to);
325
- const errMsg = `tried to connect to ${to} but retry budget exceeded (more than ${budgetConsumed} attempts in the last ${this.retryBudget.totalBudgetRestoreTime}ms)`;
326
- this.log?.error(errMsg, { clientId: this.clientId, connectedTo: to });
327
- this.protocolError(ProtocolError.RetriesExceeded, errMsg);
328
- return;
329
- }
330
- let sleep = Promise.resolve();
331
- const backoffMs = this.retryBudget.getBackoffMs(to);
332
- if (backoffMs > 0) {
333
- sleep = new Promise((resolve) => setTimeout(resolve, backoffMs));
334
- }
335
- this.log?.info(
336
- `attempting connection to ${to} (${backoffMs}ms backoff)`,
337
- {
338
- clientId: this.clientId,
339
- connectedTo: to
340
- }
341
- );
342
- this.retryBudget.consumeBudget(to);
343
- reconnectPromise = tracing_default.startActiveSpan("connect", async (span) => {
344
- try {
345
- span.addEvent("backoff", { backoffMs });
346
- await sleep;
347
- if (!canProceedWithConnection()) {
348
- throw new Error("transport state is no longer open");
349
- }
350
- span.addEvent("connecting");
351
- const conn = await this.createNewOutgoingConnection(to);
352
- if (!canProceedWithConnection()) {
353
- this.log?.info(
354
- `transport state is no longer open, closing pre-handshake connection to ${to}`,
355
- {
356
- ...conn.loggingMetadata,
357
- clientId: this.clientId,
358
- connectedTo: to
359
- }
360
- );
361
- conn.close();
362
- throw new Error("transport state is no longer open");
363
- }
364
- span.addEvent("sending handshake");
365
- const ok = await this.sendHandshake(to, conn);
366
- if (!ok) {
367
- conn.close();
368
- throw new Error("failed to send handshake");
369
- }
370
- return conn;
371
- } catch (err) {
372
- const errStr = coerceErrorString(err);
373
- span.recordException(errStr);
374
- span.setStatus({ code: SpanStatusCode.ERROR });
375
- throw err;
376
- } finally {
377
- span.end();
378
- }
379
- });
380
- this.inflightConnectionPromises.set(to, reconnectPromise);
381
- } else {
382
- this.log?.info(
383
- `attempting connection to ${to} (reusing previous attempt)`,
384
- {
385
- clientId: this.clientId,
386
- connectedTo: to
387
- }
388
- );
389
- }
390
- try {
391
- await reconnectPromise;
392
- } catch (error) {
393
- this.inflightConnectionPromises.delete(to);
394
- const errStr = coerceErrorString(error);
395
- if (!this.reconnectOnConnectionDrop || !canProceedWithConnection()) {
396
- this.log?.warn(`connection to ${to} failed (${errStr})`, {
397
- clientId: this.clientId,
398
- connectedTo: to
399
- });
400
- } else {
401
- this.log?.warn(`connection to ${to} failed (${errStr}), retrying`, {
402
- clientId: this.clientId,
403
- connectedTo: to
404
- });
405
- await this.connect(to);
406
- }
407
- }
408
- }
409
- deleteSession({
410
- session,
411
- closeHandshakingConnection,
412
- handshakingConn
413
- }) {
414
- this.inflightConnectionPromises.delete(session.to);
415
- super.deleteSession({
416
- session,
417
- closeHandshakingConnection,
418
- handshakingConn
419
- });
420
- }
421
- async sendHandshake(to, conn) {
422
- let metadata = void 0;
423
- if (this.handshakeExtensions) {
424
- metadata = await this.handshakeExtensions.construct();
425
- if (!Value.Check(this.handshakeExtensions.schema, metadata)) {
426
- this.log?.error(`constructed handshake metadata did not match schema`, {
427
- ...conn.loggingMetadata,
428
- clientId: this.clientId,
429
- connectedTo: to,
430
- validationErrors: [
431
- ...Value.Errors(this.handshakeExtensions.schema, metadata)
432
- ],
433
- tags: ["invariant-violation"]
434
- });
435
- this.protocolError(
436
- ProtocolError.HandshakeFailed,
437
- "handshake metadata did not match schema"
438
- );
439
- conn.telemetry?.span.setStatus({
440
- code: SpanStatusCode.ERROR,
441
- message: "handshake meta mismatch"
442
- });
443
- return false;
444
- }
445
- }
446
- const { session } = this.getOrCreateSession({ to, handshakingConn: conn });
447
- const requestMsg = handshakeRequestMessage({
448
- from: this.clientId,
449
- to,
450
- sessionId: session.id,
451
- expectedSessionState: {
452
- reconnect: session.advertisedSessionId !== void 0,
453
- nextExpectedSeq: session.nextExpectedSeq
454
- },
455
- metadata,
456
- tracing: getPropagationContext(session.telemetry.ctx)
457
- });
458
- this.log?.debug(`sending handshake request to ${to}`, {
459
- ...conn.loggingMetadata,
460
- clientId: this.clientId,
461
- connectedTo: to,
462
- transportMessage: requestMsg
463
- });
464
- conn.send(this.codec.toBuffer(requestMsg));
465
- return true;
466
- }
467
- close() {
468
- this.retryBudget.close();
469
- super.close();
470
- }
471
- };
472
-
473
- export {
474
- ClientTransport
475
- };
476
- //# sourceMappingURL=chunk-47TFNAY2.js.map