| import { z } from "zod"; |
|
|
| |
| |
| |
| const ISO_TIMESTAMP_PATTERN = |
| /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/; |
|
|
| const isoTimestamp = z |
| .string() |
| .refine((value) => ISO_TIMESTAMP_PATTERN.test(value) && !Number.isNaN(Date.parse(value)), { |
| error: "not an ISO timestamp", |
| }); |
|
|
| const authorSchema = z |
| .looseObject({ |
| id: z.string().optional(), |
| username: z.string().min(1), |
| display_name: z.string().optional(), |
| }) |
| .readonly(); |
|
|
| |
| |
| |
| |
| |
| |
| |
| export const tweetSchema = z.looseObject({ |
| id: z.string().min(1), |
| url: z.string().min(1), |
| text: z.string(), |
| captured_at: isoTimestamp, |
| created_at: isoTimestamp.optional(), |
| author: authorSchema, |
| }); |
|
|
| export type Tweet = z.infer<typeof tweetSchema>; |
|
|
| |
| export type PooledTweet = Tweet & { |
| contributed_by: string; |
| pooled_at: string; |
| }; |
|
|
| export type TweetValidationResult = { ok: true; tweet: Tweet } | { ok: false; reason: string }; |
|
|
| |
| export function validateTweet(candidate: unknown): TweetValidationResult { |
| const parsed = tweetSchema.safeParse(candidate); |
| if (parsed.success) { |
| return { ok: true, tweet: parsed.data }; |
| } |
| const first = parsed.error.issues[0]; |
| const reason = |
| first === undefined ? "invalid tweet" : `${first.path.join(".")}: ${first.message}`; |
| return { ok: false, reason }; |
| } |
|
|
| |
| |
| |
| |
| export function stampTweet(tweet: Tweet, contributedBy: string, pooledAt: Date): PooledTweet { |
| return { ...tweet, contributed_by: contributedBy, pooled_at: pooledAt.toISOString() }; |
| } |
|
|
| |
| export function dayKey(capturedAt: string): string { |
| const day = new Date(capturedAt).toISOString().slice(0, 10); |
| return day; |
| } |
|
|
| |
| |
| |
| |
| export function datasetPathFor(contributedBy: string, capturedAt: string): string { |
| const day = dayKey(capturedAt); |
| const [year, month] = [day.slice(0, 4), day.slice(5, 7)]; |
| return `data/${contributedBy}/${year}/${month}/tweets-${day}.jsonl`; |
| } |
|
|