{"record":{"id":"2ef4575264711adc","repo":"vitejs/vite","slug":"transport-must-implement-send-and-connect-when-inv","errorCode":null,"errorMessage":"transport must implement send and connect when invoke is not implemented","messagePattern":"transport must implement send and connect when invoke is not implemented","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/vite/src/shared/moduleRunnerTransport.ts","lineNumber":68,"sourceCode":"        const result = await transport.invoke!({\n          type: 'custom',\n          event: 'vite:invoke',\n          data: {\n            id: 'send',\n            name,\n            data,\n          } satisfies InvokeSendData,\n        } satisfies CustomPayload)\n        if ('error' in result) {\n          throw reviveInvokeError(result.error)\n        }\n        return result.result\n      },\n    }\n  }\n\n  if (!transport.send || !transport.connect) {\n    throw new Error(\n      'transport must implement send and connect when invoke is not implemented',\n    )\n  }\n\n  const rpcPromises = new Map<\n    string,\n    {\n      resolve: (data: any) => void\n      reject: (data: any) => void\n      name: string\n      timeoutId?: ReturnType<typeof setTimeout>\n    }\n  >()\n\n  return {\n    ...transport,\n    connect({ onMessage, onDisconnection }) {\n      return transport.connect!({","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/vitejs/vite/blob/b4d66fee14d970f45b8a6f3d7d6aee73ca9b88ab/packages/vite/src/shared/moduleRunnerTransport.ts#L50-L86","documentation":"Thrown when normalizing a ModuleRunnerTransport that defines neither invoke nor both send+connect. The transport contract requires either an invoke method (direct RPC) or a send+connect pair (so Vite can synthesize invoke over the message channel); providing only one of send/connect, or neither, is invalid.","triggerScenarios":"Constructing a ModuleRunnerTransport with only send (no connect), only connect (no send), or neither. The check is at moduleRunnerTransport.ts:67 inside createInvokeableTransport, after confirming transport.invoke is absent.","commonSituations":"Writing a custom transport for the SSR module runner and forgetting connect. Partially copying createWebSocketModuleRunnerTransport and dropping a method. Async transport setup where one method is assigned later but the object is passed before that.","solutions":["Provide invoke on the transport if you handle RPC yourself.","Otherwise provide both connect(handlers) and send(payload) — the pair is required to synthesize invoke.","Use createWebSocketModuleRunnerTransport as the template; it correctly implements connect+disconnect+send."],"exampleFix":"// before\nconst transport = { send(payload) { ws.send(JSON.stringify(payload)) } } // missing connect -> throws\n// after\nconst transport = {\n  connect({ onMessage }) { ws.addEventListener('message', e => onMessage(JSON.parse(e.data))) },\n  send(payload) { ws.send(JSON.stringify(payload)) },\n}","handlingStrategy":"type-guard","validationCode":"function isValidTransport(t: ModuleRunnerTransport): boolean {\n  return typeof t.invoke === 'function' || (typeof t.send === 'function' && typeof t.connect === 'function')\n}","typeGuard":"function hasInvokeOrSendConnect(t: ModuleRunnerTransport): boolean {\n  return typeof t.invoke === 'function'\n    || (typeof t.send === 'function' && typeof t.connect === 'function')\n}","tryCatchPattern":null,"preventionTips":["Start from createWebSocketModuleRunnerTransport and modify rather than building a transport from scratch.","Always implement both connect and send together, or implement invoke."],"tags":["module-runner","transport","configuration"],"backgroundTag":null,"analyzedSha":"b4d66fee14d970f45b8a6f3d7d6aee73ca9b88ab","analyzedAt":"2026-08-11T11:49:19.515Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}