diff --git a/content/projects/zod-bird.mdx b/content/projects/zod-bird.mdx new file mode 100644 index 0000000..3d37898 --- /dev/null +++ b/content/projects/zod-bird.mdx @@ -0,0 +1,172 @@ +--- +title: "Zod Bird" +description: Zodbird is an e2e typed tinybird.co client library for typescript, leveraging zod for type safety and transformations +repository: chronark/zod-bird +date: "2023-05-21" +published: true + +--- + +- typesafe +- handles building the url params for you +- easy transformation of resulting data +- built in cache directives for nextjs +- built in retry logic for ratelimited requests + + +```ts +import { Tinybird } from "@chronark/zod-bird"; +import { z } from "zod"; + +const tb = new Tinybird({ token: "token" }); + +export const getEvents = tb.buildPipe({ + pipe: "get_events__v1", + parameters: z.object({ + tenantId: z.string(), + }), + data: z.object({ + event: z.string(), + time: z.number().transform((t) => new Date(t)), + }), +}); + + +const res = await getEvents({ tenantId: "chronark" }) + +// res.data = {event: string, time: Date}[] +``` + +## Install + +``` +npm i @chronark/zod-bird +``` + + +## Ingesting Data + +```ts +// lib/tinybird.ts +import { Tinybird } from "./client"; +import { z } from "zod"; + +const tb = new Tinybird({ token: process.env.TINYBIRD_TOKEN! }); + +export const publishEvent = tb.buildIngestEndpoint({ + datasource: "events__v1", + event: z.object({ + id: z.string(), + tenantId: z.string(), + channelId: z.string(), + time: z.number().int(), + event: z.string(), + content: z.string().optional().default(""), + metadata: z.string().optional().default(""), + }), +}); +``` + +```ts +// somewhere.ts +import { publishEvent } from "./lib/tinybird"; + +await publishEvent({ + id: "1", + tenantId: "1", + channelId: "1", + time: Date.now(), + event: "test", + content: "test", + metadata: JSON.stringify({ test: "test" }), +}); +``` + + + +## Querying Endpoints + +```ts +// lib/tinybird.ts +import { Tinybird } from "./client"; +import { z } from "zod"; + +const tb = new Tinybird({ token: process.env.TINYBIRD_TOKEN! }); + +export const getChannelActivity = tb.buildPipe({ + pipe: "get_channel_activity__v1", + parameters: z.object({ + tenantId: z.string(), + channelId: z.string().optional(), + start: z.number(), + end: z.number().optional(), + granularity: z.enum(["1m", "1h", "1d", "1w", "1M"]), + }), + data: z.object({ + time: z.string().transform((t) => new Date(t).getTime()), + count: z + .number() + .nullable() + .optional() + .transform((v) => (typeof v === "number" ? v : 0)), + }), +}); +``` + +```ts +// somewhere.ts +import { getChannelActivity } from "./lib/tinybird"; + + +const res = await getChannelActivity({ + tenantId: "1", + channelId: "1", + start: 123, + end: 1234, + granularity: "1h" +}) + +``` +`res` is the response from the tinybird endpoint, but now fully typed and the data has been parsed according to the schema defined in `data`. + +## Advanced + +### Caching + +You can add cache directives to each pipe. + + +#### Disable caching (useful in Next.js where everything is cached by default) + +```ts +tb.buildPipe({ + pipe: "some_pipe__v1", + parameters: z.object({ + hello: z.string() + }), + data: z.object({ + response: z.string() + }), + opts: { + cache: "no-store" // <-------- Add this + }; +}); + +``` + +#### Cache for 15 minutes + +```ts + +tb.buildPipe({ + pipe: "some_pipe__v1", + parameters: z.object({ + hello: z.string() + }), + data: z.object({ + response: z.string() + }), + opts: { + revalidate: 900 ;// <-------- Add this + }; +});