decentralised sync engine
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: use zod

serenity c9466b3d 7365e077

+103 -53
+2 -1
package.json
··· 27 27 "typescript-eslint": "^8.46.0" 28 28 }, 29 29 "dependencies": { 30 - "ws": "^8.18.3" 30 + "ws": "^8.18.3", 31 + "zod": "^4.1.12" 31 32 } 32 33 }
+8
pnpm-lock.yaml
··· 11 11 ws: 12 12 specifier: ^8.18.3 13 13 version: 8.18.3 14 + zod: 15 + specifier: ^4.1.12 16 + version: 4.1.12 14 17 devDependencies: 15 18 '@eslint/js': 16 19 specifier: ^9.37.0 ··· 731 734 resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} 732 735 engines: {node: '>=10'} 733 736 737 + zod@4.1.12: 738 + resolution: {integrity: sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==} 739 + 734 740 snapshots: 735 741 736 742 '@cspotcode/source-map-support@0.8.1': ··· 1401 1407 yn@3.1.1: {} 1402 1408 1403 1409 yocto-queue@0.1.0: {} 1410 + 1411 + zod@4.1.12: {}
+7 -13
src/index.ts
··· 1 + import type { ShardMessage } from "@/lib/types/messages"; 1 2 import { rawDataToString } from "@/lib/utils"; 2 - import { validateShardMessage, type ShardMessage } from "@/lib/validator"; 3 + import { validateNewMessage } from "@/lib/validators"; 3 4 import type { RawData } from "ws"; 4 5 import WebSocket from "ws"; 5 6 6 7 const wss = new WebSocket.Server({ port: 8080 }); 7 8 8 - const messages: ShardMessage[] = []; // Store message history 9 + const messages: ShardMessage[] = []; 9 10 const clients = new Set<WebSocket>(); 10 11 11 12 wss.on("connection", (ws) => { 12 13 clients.add(ws); 13 14 14 - // Send history to new client 15 15 ws.send( 16 16 JSON.stringify({ 17 17 type: "shard/history", ··· 22 22 ws.on("message", (data: RawData) => { 23 23 const jsonText = rawDataToString(data); 24 24 const jsonData: unknown = JSON.parse(jsonText); 25 - const { 26 - success, 27 - error, 28 - data: shardMessage, 29 - } = validateShardMessage(jsonData); 30 25 31 - if (!success) { 32 - console.log(error); 33 - } else { 34 - messages.push(shardMessage); 35 - } 26 + const shardMessage = validateNewMessage(jsonData); 27 + if (!shardMessage) return; 28 + 29 + messages.push(shardMessage); 36 30 37 31 clients.forEach((client) => { 38 32 if (client.readyState === WebSocket.OPEN) {
+22
src/lib/types/messages.ts
··· 1 + import { z } from "zod"; 2 + 3 + export const websocketMessageSchema = z.object({ 4 + type: z.union([z.literal("shard/message"), z.literal("shard/history")]), 5 + }); 6 + 7 + export type WebsocketMessage = z.infer<typeof websocketMessageSchema>; 8 + 9 + export const shardMessageSchema = websocketMessageSchema.extend({ 10 + type: z.literal("shard/message"), 11 + text: z.string(), 12 + timestamp: z.coerce.date(), 13 + }); 14 + 15 + export type ShardMessage = z.infer<typeof shardMessageSchema>; 16 + 17 + export const historyMessageSchema = websocketMessageSchema.extend({ 18 + type: z.literal("shard/history"), 19 + messages: z.optional(z.array(shardMessageSchema)), 20 + }); 21 + 22 + export type HistoryMessage = z.infer<typeof historyMessageSchema>;
-39
src/lib/validator.ts
··· 1 - export function assertShardMessage( 2 - json: unknown, 3 - ): asserts json is ShardMessage { 4 - if (typeof json !== "object") { 5 - throw new Error("not a js object"); 6 - } 7 - 8 - const candidate = json as Record<string, unknown>; 9 - 10 - if (candidate.type !== "shard/message") { 11 - throw new Error("Invalid type"); 12 - } 13 - 14 - if (typeof candidate.text !== "string") { 15 - throw new Error("Invalid text"); 16 - } 17 - 18 - const timestamp = new Date(candidate.timestamp as string); 19 - 20 - if (!(timestamp instanceof Date)) { 21 - throw new Error("Invalid timestamp"); 22 - } 23 - } 24 - 25 - // example. we will use zod in the future. 26 - export const validateShardMessage = (json: unknown) => { 27 - try { 28 - assertShardMessage(json); 29 - return { success: true, data: json }; 30 - } catch (e: unknown) { 31 - return { error: e }; 32 - } 33 - }; 34 - 35 - export interface ShardMessage { 36 - type: "shard/message"; 37 - text: string; 38 - timestamp: Date; 39 - }
+64
src/lib/validators.ts
··· 1 + import { 2 + historyMessageSchema, 3 + shardMessageSchema, 4 + websocketMessageSchema, 5 + } from "@/lib/types/messages"; 6 + import { z } from "zod"; 7 + 8 + export const validateWsMessageString = (data: unknown) => { 9 + const { success, error, data: message } = z.string().safeParse(data); 10 + if (!success) { 11 + console.error("Error decoding websocket message"); 12 + console.error(error); 13 + return; 14 + } 15 + return message; 16 + }; 17 + 18 + export const validateWsMessageType = (data: unknown) => { 19 + const { 20 + success: wsMessageSuccess, 21 + error: wsMessageError, 22 + data: wsMessage, 23 + } = websocketMessageSchema.loose().safeParse(data); 24 + if (!wsMessageSuccess) { 25 + console.error( 26 + "Error parsing websocket message. The data might be the wrong shape.", 27 + ); 28 + console.error(wsMessageError); 29 + return; 30 + } 31 + return wsMessage; 32 + }; 33 + 34 + export const validateHistoryMessage = (data: unknown) => { 35 + const { 36 + success: historySuccess, 37 + error: historyError, 38 + data: history, 39 + } = historyMessageSchema.safeParse(data); 40 + if (!historySuccess) { 41 + console.error( 42 + "History message schema parsing failed. Did your type drift?", 43 + ); 44 + console.error(historyError); 45 + return; 46 + } 47 + return history; 48 + }; 49 + 50 + export const validateNewMessage = (data: unknown) => { 51 + const { 52 + success: messageSuccess, 53 + error: messageError, 54 + data: message, 55 + } = shardMessageSchema.safeParse(data); 56 + if (!messageSuccess) { 57 + console.error( 58 + "New message schema parsing failed. Did your type drift?", 59 + ); 60 + console.error(messageError); 61 + return; 62 + } 63 + return message; 64 + };