{"record":{"id":"b8c125acc442f224","repo":"trpc/trpc","slug":"expected-an-async-iterable","errorCode":null,"errorMessage":"Expected an async iterable","messagePattern":"Expected an async iterable","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/client/src/links/localLink.ts","lineNumber":195,"sourceCode":"              });\n              let lastEventId: string | undefined = undefined;\n\n              using _finally = makeResource({}, async () => {\n                observer.complete();\n\n                connectionState.next({\n                  type: 'state',\n                  state: 'idle',\n                  error: null,\n                });\n                connectionSub.unsubscribe();\n              });\n              while (true) {\n                const result = await runProcedure(\n                  inputWithTrackedEventId(op.input, lastEventId),\n                );\n                if (!isAsyncIterable(result)) {\n                  throw new Error('Expected an async iterable');\n                }\n                await using iterator = iteratorResource(result);\n\n                observer.next({\n                  result: {\n                    type: 'started',\n                  },\n                });\n                connectionState.next({\n                  type: 'state',\n                  state: 'pending',\n                  error: null,\n                });\n\n                // Use a while loop to handle errors and reconnects\n                while (true) {\n                  let res;\n                  try {","sourceCodeStart":177,"sourceCodeEnd":213,"githubUrl":"https://github.com/trpc/trpc/blob/acff82332de8b4562d3e6975fa8d94ab1a6de5e0/packages/client/src/links/localLink.ts#L177-L213","documentation":"The local (in-process) link handles subscriptions by awaiting the procedure result and iterating it. After `runProcedure(...)` it asserts `isAsyncIterable(result)`; the subscription contract requires an AsyncIterable/AsyncGenerator that yields values over time. A non-iterable return (plain value, Promise, array, Observable object) violates the contract and throws.","triggerScenarios":"Defining a `.subscription(...)` whose resolver returns a plain value, a `Promise`, a raw Observable, or anything that isn't an async iterable, then invoking it through a local caller (server-to-server, RSC, or `createTRPCClient` with a local link).","commonSituations":"Pre-v11 subscriptions that returned `observable(...)` not converted to async iterables, or returning `pubsub.asyncIterator()` from a lib whose API changed.","solutions":["Return an async iterable from the subscription: an `async function*` or an object implementing `Symbol.asyncIterator`.","Convert an existing Observable via the library's `observableToAsyncIterable` helper (or rxjs `firstValueFrom` loop).","For event sources, wrap with an async generator that yields on each event."],"exampleFix":"// before\nsubscription: t.procedure.subscription(() => someObservable) // throws\n\n// after\nsubscription: t.procedure.subscription(async function* () {\n  for await (const evt of eventStream) yield evt;\n})","handlingStrategy":"type-guard","validationCode":"const isAsyncIterable = (v: unknown): v is AsyncIterable<unknown> =>\n  v != null && typeof (v as AsyncIterable<unknown>)[Symbol.asyncIterator] === 'function';\nconst result = await runProcedure(input);\nif (!isAsyncIterable(result)) throw new TypeError('Subscription must return an async iterable');","typeGuard":"const isAsyncIterable = <T>(v: unknown): v is AsyncIterable<T> =>\n  v != null && typeof (v as AsyncIterable<T>)[Symbol.asyncIterator] === 'function';","tryCatchPattern":"try {\n  for await (const v of subscription) handle(v);\n} catch (e) {\n  if (e instanceof Error && e.message === 'Expected an async iterable') {\n    // convert the resolver to an async generator\n  }\n  throw e;\n}","preventionTips":["Always return an `async function*` from subscription resolvers.","Unit-test subscription resolvers for AsyncIterable compliance."],"tags":["subscriptions","async-iterable","local-link","rxjs"],"backgroundTag":null,"analyzedSha":"acff82332de8b4562d3e6975fa8d94ab1a6de5e0","analyzedAt":"2026-08-12T22:04:41.179Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}