Simple, single-user event aggregation platform, for use in personal websites and other related places.
event-streaming single-user events event-aggregation

✨ Implement GET /states, /states/:source and /states/:source/:key

Jo a75083ff fa0ad7f3

+122 -67
+1 -1
package.json
··· 1 1 { 2 2 "name": "snowflake", 3 3 "version": "0.0.1", 4 - "description": "Simple, single-user event aggregation platform, for use in personal websites and other related places.", 4 + "description": "Simple, single-user event aggregation platform, for use in personal websites, analytics and other related places.", 5 5 "keywords": [ 6 6 "event-aggregation", 7 7 "event-streaming",
+4 -50
src/lib/stores.ts
··· 1 - // ------------------------------------------------------------ 2 - // Snowflake 3 - // ------------------------------------------------------------ 4 - 5 - export interface ISnowflake< 6 - Type extends string, 7 - Template extends object = object, 8 - > { 9 - id: string; 10 - type: Type; 11 - data: Template; 12 - createdAt: number; 13 - } 14 - 15 - export class Snowflake< 16 - Type extends string, 17 - Template extends object = object, 18 - > implements ISnowflake<Type, Template> { 19 - constructor( 20 - public id: string, 21 - public type: Type, 22 - public data: Template, 23 - public createdAt: number = Date.now(), 24 - ) {} 25 - } 26 - 27 - // ------------------------------------------------------------ 28 - // Event 29 - // ------------------------------------------------------------ 30 - 31 - export interface IEvent<T extends object = object> { 32 - source: string; 33 - name: string; 34 - data: T; 35 - } 1 + import type { Event } from "$lib/utils/Event"; 2 + import type { State } from "$lib/utils/State"; 36 3 37 - export class Event<T extends object = object> implements IEvent<T> { 38 - constructor( 39 - public source: string, 40 - public name: string, 41 - public data: T = {} as T, 42 - ) {} 43 - } 44 - 45 - export const events: Snowflake<"event", IEvent>[] = []; 46 - 47 - // ------------------------------------------------------------ 48 - // State 49 - // ------------------------------------------------------------ 50 - 51 - export const states: Snowflake<"state", object>[] = []; 4 + export const events: Event[] = []; 5 + export const states: State[] = [];
+10
src/lib/utils/Event.ts
··· 1 + export class Event<T extends object = object> { 2 + public id: string = crypto.randomUUID(); 3 + public createdAt: number = Date.now(); 4 + 5 + constructor( 6 + public source: string, // The service name, such as "hytale" 7 + public name: string, // The event name, such as "server.connected" 8 + public data: T = {} as T, // Event specific data, such as { server: { id, name }, user: { id, name } } 9 + ) {} 10 + }
+11
src/lib/utils/State.ts
··· 1 + export class State { 2 + public id: string = crypto.randomUUID(); 3 + public createdAt: number = Date.now(); 4 + public updatedAt: number = Date.now(); 5 + 6 + constructor( 7 + public source: string, // The service name this state connects to, such as "hytale" 8 + public key: string, // The key of the state, such as "current.status", "current.gamemode" or "current.server.name" 9 + public value: string, // The value of the state, such as "online", "offline", "survival", "creative", "server1", "server2" 10 + ) {} 11 + }
+10 -16
src/routes/events/index.ts
··· 3 3 // ------------------------------------------------------------ 4 4 5 5 import { Hono } from "hono"; 6 - import { Snowflake, Event, events } from "../../lib/stores"; 6 + import { events } from "$lib/stores"; 7 + import { Event } from "$lib/utils/Event"; 7 8 8 9 const app = new Hono(); 9 10 ··· 12 13 // ------------------------------------------------------------ 13 14 14 15 app.get("/", async (c) => { 15 - events.push( 16 - new Snowflake( 17 - crypto.randomUUID(), 18 - "event", 19 - new Event("internal", "endpoint.events.get"), 20 - ), 21 - ); 16 + events.push(new Event("internal", "endpoint.events.get")); 22 17 23 18 return c.json(events); 24 19 }); 25 20 26 21 app.post("/", async (c) => { 27 22 const body = await c.req.json(); 28 - if (!body.source || !body.name || !body.data) { 29 - return c.json({ error: "Invalid event" }, 400); 23 + if (!body.source || !body.name) { 24 + return c.json( 25 + { error: "Missing required parameter 'source' or 'name'" }, 26 + 422, 27 + ); 30 28 } 31 29 32 - // Build event and wrap it in a snowflake 33 - const event = new Snowflake( 34 - crypto.randomUUID(), 35 - "event", 36 - new Event(body.source, body.name, body.data), 37 - ); 30 + // Build event 31 + const event = new Event(body.source, body.name, body.data ?? {}); 38 32 39 33 events.push(event); 40 34 return c.json(body);
+1
src/routes/index.ts
··· 66 66 ); 67 67 68 68 app.route("/events", (await import("./events")).default); 69 + app.route("/states", (await import("./states")).default); 69 70 70 71 // ------------------------------------------------------------ 71 72 // Exports
+54
src/routes/states/index.ts
··· 1 + // ------------------------------------------------------------ 2 + // Imports & Initialization 3 + // ------------------------------------------------------------ 4 + 5 + import { Hono } from "hono"; 6 + import { states, events } from "$lib/stores"; 7 + import { Event } from "$lib/utils/Event"; 8 + 9 + const app = new Hono(); 10 + 11 + // ------------------------------------------------------------ 12 + // Endpoints 13 + // ------------------------------------------------------------ 14 + 15 + app.get("/", async (c) => { 16 + events.push(new Event("internal", "endpoint.states.get")); 17 + 18 + return c.json(states); 19 + }); 20 + 21 + app.get("/:source", async (c) => { 22 + const source = c.req.param("source"); 23 + const state = states.find((state) => state.source === source); 24 + 25 + if (!state) { 26 + return c.json({ error: "State not found" }, 404); 27 + } 28 + 29 + events.push(new Event("internal", "endpoint.states.get", { source })); 30 + 31 + return c.json(state); 32 + }); 33 + 34 + app.get("/:source/:key", async (c) => { 35 + const source = c.req.param("source"); 36 + const key = c.req.param("key"); 37 + const state = states.find( 38 + (state) => state.source === source && state.key === key, 39 + ); 40 + 41 + if (!state) { 42 + return c.json({ error: "State not found" }, 404); 43 + } 44 + 45 + events.push(new Event("internal", "endpoint.states.get", { source, key })); 46 + 47 + return c.json(state); 48 + }); 49 + 50 + // ------------------------------------------------------------ 51 + // Exports 52 + // ------------------------------------------------------------ 53 + 54 + export default app;
+31
tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "moduleResolution": "bundler", 4 + "module": "esnext", 5 + "target": "esnext", 6 + 7 + "strict": true, 8 + "allowSyntheticDefaultImports": true, 9 + "allowUnreachableCode": false, 10 + "allowUnusedLabels": false, 11 + "noFallthroughCasesInSwitch": true, 12 + "noImplicitAny": true, 13 + "strictNullChecks": true, 14 + "strictFunctionTypes": true, 15 + "strictBindCallApply": true, 16 + "strictPropertyInitialization": true, 17 + "noImplicitReturns": true, 18 + "noImplicitThis": true, 19 + "noUnusedLocals": false, 20 + "noUnusedParameters": true, 21 + "alwaysStrict": true, 22 + 23 + "baseUrl": "src", 24 + "paths": { 25 + "$lib/*": ["lib/*"], 26 + }, 27 + }, 28 + 29 + "include": ["src"], 30 + "exclude": ["node_modules", ".git"], 31 + }