2025-08-28 18:27:07 +02:00
|
|
|
/** biome-ignore-all lint/suspicious/noExplicitAny: <intended> */
|
2025-08-04 17:45:44 +02:00
|
|
|
import type { TrackedEnvelope } from "@trpc/server";
|
|
|
|
|
import { isTrackedEnvelope, tracked } from "@trpc/server";
|
|
|
|
|
import { z } from "zod";
|
2025-08-28 18:27:07 +02:00
|
|
|
|
2025-08-04 17:45:44 +02:00
|
|
|
function isAsyncIterable<TValue, TReturn = unknown>(
|
2025-08-28 18:27:07 +02:00
|
|
|
value: unknown,
|
2025-08-04 17:45:44 +02:00
|
|
|
): value is AsyncIterable<TValue, TReturn> {
|
2025-08-28 18:27:07 +02:00
|
|
|
return !!value && typeof value === "object" && Symbol.asyncIterator in value;
|
2025-08-04 17:45:44 +02:00
|
|
|
}
|
|
|
|
|
const trackedEnvelopeSchema =
|
2025-08-28 18:27:07 +02:00
|
|
|
z.custom<TrackedEnvelope<unknown>>(isTrackedEnvelope);
|
2025-08-04 17:45:44 +02:00
|
|
|
/**
|
|
|
|
|
* A Zod schema helper designed specifically for validating async iterables. This schema ensures that:
|
|
|
|
|
* 1. The value being validated is an async iterable.
|
|
|
|
|
* 2. Each item yielded by the async iterable conforms to a specified type.
|
|
|
|
|
* 3. The return value of the async iterable, if any, also conforms to a specified type.
|
|
|
|
|
*/
|
|
|
|
|
export function zAsyncIterable<
|
2025-08-28 18:27:07 +02:00
|
|
|
TYield,
|
|
|
|
|
TReturn = void,
|
|
|
|
|
Tracked extends boolean = false,
|
2025-08-04 17:45:44 +02:00
|
|
|
>(opts: {
|
2025-08-28 18:27:07 +02:00
|
|
|
yield: z.ZodType<TYield, any, any>;
|
|
|
|
|
return?: z.ZodType<TReturn, any, any>;
|
|
|
|
|
tracked?: Tracked;
|
2025-08-04 17:45:44 +02:00
|
|
|
}) {
|
2025-08-28 18:27:07 +02:00
|
|
|
const baseSchema = z.custom<
|
|
|
|
|
AsyncIterable<
|
|
|
|
|
Tracked extends true
|
|
|
|
|
? TrackedEnvelope<z.input<typeof opts.yield>>
|
|
|
|
|
: z.input<typeof opts.yield>,
|
|
|
|
|
z.input<typeof opts.return>
|
|
|
|
|
>
|
|
|
|
|
>((val: unknown): val is AsyncIterable<any, any> => isAsyncIterable(val), {
|
|
|
|
|
message: "Expected AsyncIterable",
|
|
|
|
|
});
|
2025-08-07 14:50:40 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
return baseSchema.transform(async function* (iter) {
|
|
|
|
|
const iterator = iter[Symbol.asyncIterator]();
|
|
|
|
|
try {
|
|
|
|
|
// biome-ignore lint/suspicious/noImplicitAnyLet: <intended>
|
|
|
|
|
let next;
|
|
|
|
|
// biome-ignore lint/suspicious/noAssignInExpressions: <intended>
|
|
|
|
|
while ((next = await iterator.next()) && !next.done) {
|
|
|
|
|
if (opts.tracked) {
|
|
|
|
|
const [id, data] = await trackedEnvelopeSchema.parseAsync(next.value);
|
|
|
|
|
yield tracked(id, await opts.yield.parseAsync(data));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
yield await opts.yield.parseAsync(next.value);
|
|
|
|
|
}
|
|
|
|
|
if (opts.return) {
|
|
|
|
|
return (await opts.return.parseAsync(next.value)) as TReturn;
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
} finally {
|
|
|
|
|
await iterator.return?.();
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-08-04 17:45:44 +02:00
|
|
|
}
|