decentralised sync engine

feat: use zod

serenity c9466b3d 7365e077

+2 -1
package.json
···
"typescript-eslint": "^8.46.0"
},
"dependencies": {
-
"ws": "^8.18.3"
+
"ws": "^8.18.3",
+
"zod": "^4.1.12"
}
}
+8
pnpm-lock.yaml
···
ws:
specifier: ^8.18.3
version: 8.18.3
+
zod:
+
specifier: ^4.1.12
+
version: 4.1.12
devDependencies:
'@eslint/js':
specifier: ^9.37.0
···
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
+
zod@4.1.12:
+
resolution: {integrity: sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==}
+
snapshots:
'@cspotcode/source-map-support@0.8.1':
···
yn@3.1.1: {}
yocto-queue@0.1.0: {}
+
+
zod@4.1.12: {}
+7 -13
src/index.ts
···
+
import type { ShardMessage } from "@/lib/types/messages";
import { rawDataToString } from "@/lib/utils";
-
import { validateShardMessage, type ShardMessage } from "@/lib/validator";
+
import { validateNewMessage } from "@/lib/validators";
import type { RawData } from "ws";
import WebSocket from "ws";
const wss = new WebSocket.Server({ port: 8080 });
-
const messages: ShardMessage[] = []; // Store message history
+
const messages: ShardMessage[] = [];
const clients = new Set<WebSocket>();
wss.on("connection", (ws) => {
clients.add(ws);
-
// Send history to new client
ws.send(
JSON.stringify({
type: "shard/history",
···
ws.on("message", (data: RawData) => {
const jsonText = rawDataToString(data);
const jsonData: unknown = JSON.parse(jsonText);
-
const {
-
success,
-
error,
-
data: shardMessage,
-
} = validateShardMessage(jsonData);
-
if (!success) {
-
console.log(error);
-
} else {
-
messages.push(shardMessage);
-
}
+
const shardMessage = validateNewMessage(jsonData);
+
if (!shardMessage) return;
+
+
messages.push(shardMessage);
clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
+22
src/lib/types/messages.ts
···
+
import { z } from "zod";
+
+
export const websocketMessageSchema = z.object({
+
type: z.union([z.literal("shard/message"), z.literal("shard/history")]),
+
});
+
+
export type WebsocketMessage = z.infer<typeof websocketMessageSchema>;
+
+
export const shardMessageSchema = websocketMessageSchema.extend({
+
type: z.literal("shard/message"),
+
text: z.string(),
+
timestamp: z.coerce.date(),
+
});
+
+
export type ShardMessage = z.infer<typeof shardMessageSchema>;
+
+
export const historyMessageSchema = websocketMessageSchema.extend({
+
type: z.literal("shard/history"),
+
messages: z.optional(z.array(shardMessageSchema)),
+
});
+
+
export type HistoryMessage = z.infer<typeof historyMessageSchema>;
-39
src/lib/validator.ts
···
-
export function assertShardMessage(
-
json: unknown,
-
): asserts json is ShardMessage {
-
if (typeof json !== "object") {
-
throw new Error("not a js object");
-
}
-
-
const candidate = json as Record<string, unknown>;
-
-
if (candidate.type !== "shard/message") {
-
throw new Error("Invalid type");
-
}
-
-
if (typeof candidate.text !== "string") {
-
throw new Error("Invalid text");
-
}
-
-
const timestamp = new Date(candidate.timestamp as string);
-
-
if (!(timestamp instanceof Date)) {
-
throw new Error("Invalid timestamp");
-
}
-
}
-
-
// example. we will use zod in the future.
-
export const validateShardMessage = (json: unknown) => {
-
try {
-
assertShardMessage(json);
-
return { success: true, data: json };
-
} catch (e: unknown) {
-
return { error: e };
-
}
-
};
-
-
export interface ShardMessage {
-
type: "shard/message";
-
text: string;
-
timestamp: Date;
-
}
+64
src/lib/validators.ts
···
+
import {
+
historyMessageSchema,
+
shardMessageSchema,
+
websocketMessageSchema,
+
} from "@/lib/types/messages";
+
import { z } from "zod";
+
+
export const validateWsMessageString = (data: unknown) => {
+
const { success, error, data: message } = z.string().safeParse(data);
+
if (!success) {
+
console.error("Error decoding websocket message");
+
console.error(error);
+
return;
+
}
+
return message;
+
};
+
+
export const validateWsMessageType = (data: unknown) => {
+
const {
+
success: wsMessageSuccess,
+
error: wsMessageError,
+
data: wsMessage,
+
} = websocketMessageSchema.loose().safeParse(data);
+
if (!wsMessageSuccess) {
+
console.error(
+
"Error parsing websocket message. The data might be the wrong shape.",
+
);
+
console.error(wsMessageError);
+
return;
+
}
+
return wsMessage;
+
};
+
+
export const validateHistoryMessage = (data: unknown) => {
+
const {
+
success: historySuccess,
+
error: historyError,
+
data: history,
+
} = historyMessageSchema.safeParse(data);
+
if (!historySuccess) {
+
console.error(
+
"History message schema parsing failed. Did your type drift?",
+
);
+
console.error(historyError);
+
return;
+
}
+
return history;
+
};
+
+
export const validateNewMessage = (data: unknown) => {
+
const {
+
success: messageSuccess,
+
error: messageError,
+
data: message,
+
} = shardMessageSchema.safeParse(data);
+
if (!messageSuccess) {
+
console.error(
+
"New message schema parsing failed. Did your type drift?",
+
);
+
console.error(messageError);
+
return;
+
}
+
return message;
+
};