Skip to content

Instantly share code, notes, and snippets.

@nmabhinandan
Created March 10, 2025 05:40
Show Gist options
  • Save nmabhinandan/4a8283cd1076983ffb5e9de66129d019 to your computer and use it in GitHub Desktop.
Save nmabhinandan/4a8283cd1076983ffb5e9de66129d019 to your computer and use it in GitHub Desktop.
Datastar SDK implementation for Hono
import { ServerSentEventGenerator as AbstractSSEGenerator } from '@starfederation/datastar-sdk/src/abstractServerSentEventGenerator'
import type { DatastarEventOptions, EventType, MergeFragmentsOptions } from '@starfederation/datastar-sdk/src/types'
import type { Context } from 'hono'
import type { JSX } from 'hono/jsx/jsx-runtime'
import type { SSEStreamingApi } from 'hono/streaming'
import { streamSSE } from 'hono/streaming'
import type { Jsonifiable } from 'type-fest'
export function isRecord(obj: unknown): obj is Record<string, Jsonifiable> {
return typeof obj === 'object' && obj !== null
}
export class ServerSentEventGenerator extends AbstractSSEGenerator {
private c: Context
private stream: SSEStreamingApi | undefined
private constructor(c: Context) {
super()
this.c = c
}
private setStream(stream: SSEStreamingApi) {
this.stream = stream
}
static async readSignals(c: Context): Promise<Record<string, Jsonifiable>> {
try {
if (c.req.method === 'GET') {
const params = c.req.param()
if (params['datastar'] !== undefined) {
const signals = JSON.parse(params['datastar'])
if (isRecord(signals)) {
return signals
} else {
//throw
}
} else {
//throw
}
}
const signals = await c.req.json()
if (isRecord(signals)) {
return signals
} else {
//throw
}
} catch (err: unknown) {
if (isRecord(err) && 'message' in err && typeof err.message === 'string') {
//throw
}
}
//throw
}
static async stream(
c: Context,
onStart: (stream: ServerSentEventGenerator) => Promise<void>,
options?: Partial<{
onError?: (e: Error, stream: ServerSentEventGenerator) => Promise<void>
}>,
): Promise<Response> {
const generator = new ServerSentEventGenerator(c)
const streamFn = async (stream: SSEStreamingApi) => {
generator.setStream(stream)
await onStart(generator)
await stream.close()
}
const streamErrFn = async (e: Error, stream: SSEStreamingApi) => {
generator.setStream(stream)
if (options?.onError !== undefined) {
await options.onError(e, generator)
} else {
//throw
}
}
try {
return streamSSE(c, streamFn, streamErrFn)
} catch (err: unknown) {
if (isRecord(err) && 'message' in err && typeof err.message === 'string') {
//throw
}
}
//throw
}
protected override send(
event: EventType,
dataLines: string[],
options: DatastarEventOptions,
): string[] {
const eventLines = super.send(event, dataLines, options)
if (this.stream !== undefined) {
eventLines.forEach((line) => {
this.stream?.write(line)
})
} else {
//throw
}
return eventLines
}
public mergeJsx(
data: JSX.Element,
options?: MergeFragmentsOptions,
): ReturnType<typeof this.send> {
return this.mergeFragments(data.toString(), options)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment