@hecto/doc-server
v0.2.0
Published
Operation-log server and ephemeral presence for collaborative documents.
Readme
@hecto/doc-server
Operation-log HTTP/SSE server and ephemeral presence for collaborative documents. Authorisation is a required host callback with no permissive default.
pnpm add @hecto/doc-serverimport { patch } from '@hecto/diff'
import { createDocServer, createMemoryDocStorage } from '@hecto/doc-server'
const server = createDocServer({
storage: createMemoryDocStorage(),
authorize: ({ request }) => {
if (request.headers.get('authorization') !== 'Bearer demo') return undefined
return { userId: 'ada', displayName: 'Ada', sessionId: 'session-ada' }
},
})
const event = {
id: { site: 'alice', seq: 1 },
parents: [] as const,
patch: patch([{ op: 'splice', path: [], index: 0, removed: '', insert: 'hello' }]),
}
const posted = await server.fetch(
new Request('http://docs.test/docs/note-1/events', {
method: 'POST',
headers: {
authorization: 'Bearer demo',
'content-type': 'application/json',
},
body: JSON.stringify({ clientId: 'alice', events: [event] }),
}),
)
await posted.json()
// { ok: true, stateVector: { alice: 1 } }createDocServer is a fetch handler. It does not depend on Elysia.
Mount server.fetch from Hono, Bun, Deno, or another Web-standard
handler. Omit authorize and the factory throws.
Routes
| Method | Path | Auth action | Body / query |
| ------ | ---- | ----------- | ------------ |
| POST | /docs/:id/events | append | { clientId, events } |
| GET | /docs/:id/stream | read | ?sv= URI-encoded JSON state vector |
| POST | /docs/:id/presence | presence | { activity, cursor } only |
A subscriber must not receive the events they just appended as a live
frame. Send clientId on the append body and again as
x-hecto-client-id or ?clientId= on the stream (browser EventSource
cannot set the header). Catch-up since is not filtered.
Unknown parents → HTTP 409 { _tag: 'UnknownParent', event, parent }.
Unauthorised → HTTP 403 { _tag: 'Unauthorized' }. Oversized input →
HTTP 413 { _tag: 'PayloadTooLarge' } (MAX_EVENTS_PER_APPEND 256,
MAX_EVENT_JSON_BYTES 64 KiB, MAX_PRESENCE_JSON_BYTES 4 KiB).
Presence
Ephemeral, per document, never written to storage. Identity
(sessionId, userId, displayName) is taken from the authorised
principal; body fields with those names are ignored. Entries expire
after PRESENCE_STALE_AFTER_MS (45_000) unless presenceTtlMs is set.
Storage
Port { append, since, snapshot }.
createMemoryDocStorage()createPostgresDocStorage(sql)plusensureDocEventsSchema(sql)
sql is a tagged-template + begin port compatible with Bun.SQL. This
package does not depend on a Postgres client.
Pair with createDocClient from @hecto/crdt/client.
