infoalloggi-monorepo/apps/infoalloggi/src/server/utils/sse_zod.ts

62 lines
2 KiB
TypeScript
Raw Normal View History

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-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
}