@shadow-js/realtime 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/README.md +114 -2
- package/dist/dev/adapter.d.ts +75 -0
- package/dist/dev/client/core.d.ts +24 -0
- package/dist/dev/client/core.js +845 -0
- package/dist/dev/client/core.js.map +1 -0
- package/dist/dev/client/index.d.ts +5 -0
- package/dist/dev/client/index.js +3 -0
- package/dist/dev/client/presence.js +23 -0
- package/dist/dev/client/presence.js.map +1 -0
- package/dist/dev/client/sse.js +76 -0
- package/dist/dev/client/sse.js.map +1 -0
- package/dist/dev/client/types.d.ts +256 -0
- package/dist/dev/client/util.js +30 -0
- package/dist/dev/client/util.js.map +1 -0
- package/dist/dev/client/ws.js +39 -0
- package/dist/dev/client/ws.js.map +1 -0
- package/dist/dev/cloudflare/adapter.d.ts +80 -0
- package/dist/dev/cloudflare/adapter.js +347 -0
- package/dist/dev/cloudflare/adapter.js.map +1 -0
- package/dist/dev/cloudflare/core.js +701 -0
- package/dist/dev/cloudflare/core.js.map +1 -0
- package/dist/dev/cloudflare/link.js +397 -0
- package/dist/dev/cloudflare/link.js.map +1 -0
- package/dist/dev/cloudflare/object.d.ts +82 -0
- package/dist/dev/cloudflare/object.js +214 -0
- package/dist/dev/cloudflare/object.js.map +1 -0
- package/dist/dev/cloudflare/types.d.ts +100 -0
- package/dist/dev/cloudflare/wire.js +284 -0
- package/dist/dev/cloudflare/wire.js.map +1 -0
- package/dist/dev/cloudflare.d.ts +4 -0
- package/dist/dev/cloudflare.js +3 -0
- package/dist/dev/errors.d.ts +25 -0
- package/dist/dev/errors.js +24 -0
- package/dist/dev/errors.js.map +1 -0
- package/dist/dev/hooks.d.ts +110 -0
- package/dist/dev/hooks.js +162 -0
- package/dist/dev/hooks.js.map +1 -0
- package/dist/dev/index.d.ts +4 -0
- package/dist/dev/index.js +3 -0
- package/dist/dev/memory.d.ts +33 -0
- package/dist/dev/memory.js +255 -0
- package/dist/dev/memory.js.map +1 -0
- package/dist/dev/protocol.d.ts +211 -0
- package/dist/dev/protocol.js +400 -0
- package/dist/dev/protocol.js.map +1 -0
- package/dist/dev/redis/client.d.ts +139 -0
- package/dist/dev/redis/client.js +146 -0
- package/dist/dev/redis/client.js.map +1 -0
- package/dist/dev/redis/scripts.js +275 -0
- package/dist/dev/redis/scripts.js.map +1 -0
- package/dist/dev/redis/topics.d.ts +8 -0
- package/dist/dev/redis/topics.js +377 -0
- package/dist/dev/redis/topics.js.map +1 -0
- package/dist/dev/redis.d.ts +68 -0
- package/dist/dev/redis.js +422 -0
- package/dist/dev/redis.js.map +1 -0
- package/dist/dev/server/http.d.ts +48 -0
- package/dist/dev/server/http.js +286 -0
- package/dist/dev/server/http.js.map +1 -0
- package/dist/dev/server/hub.d.ts +114 -0
- package/dist/dev/server/hub.js +827 -0
- package/dist/dev/server/hub.js.map +1 -0
- package/dist/dev/server/index.d.ts +47 -0
- package/dist/dev/server/index.js +41 -0
- package/dist/dev/server/index.js.map +1 -0
- package/dist/dev/server/limits.d.ts +54 -0
- package/dist/dev/server/limits.js +85 -0
- package/dist/dev/server/limits.js.map +1 -0
- package/dist/dev/server/rules.d.ts +43 -0
- package/dist/dev/server/rules.js +72 -0
- package/dist/dev/server/rules.js.map +1 -0
- package/dist/dev/testing.d.ts +115 -0
- package/dist/dev/testing.js +427 -0
- package/dist/dev/testing.js.map +1 -0
- package/dist/prod/client/core.js +2 -0
- package/dist/prod/client/core.js.map +1 -0
- package/dist/prod/client/index.js +1 -0
- package/dist/prod/client/presence.js +2 -0
- package/dist/prod/client/presence.js.map +1 -0
- package/dist/prod/client/sse.js +5 -0
- package/dist/prod/client/sse.js.map +1 -0
- package/dist/prod/client/util.js +2 -0
- package/dist/prod/client/util.js.map +1 -0
- package/dist/prod/client/ws.js +2 -0
- package/dist/prod/client/ws.js.map +1 -0
- package/dist/prod/cloudflare/adapter.js +3 -0
- package/dist/prod/cloudflare/adapter.js.map +1 -0
- package/dist/prod/cloudflare/core.js +2 -0
- package/dist/prod/cloudflare/core.js.map +1 -0
- package/dist/prod/cloudflare/link.js +2 -0
- package/dist/prod/cloudflare/link.js.map +1 -0
- package/dist/prod/cloudflare/object.js +2 -0
- package/dist/prod/cloudflare/object.js.map +1 -0
- package/dist/prod/cloudflare/wire.js +2 -0
- package/dist/prod/cloudflare/wire.js.map +1 -0
- package/dist/prod/cloudflare.js +1 -0
- package/dist/prod/errors.js +2 -0
- package/dist/prod/errors.js.map +1 -0
- package/dist/prod/hooks.js +2 -0
- package/dist/prod/hooks.js.map +1 -0
- package/dist/prod/index.js +1 -0
- package/dist/prod/memory.js +2 -0
- package/dist/prod/memory.js.map +1 -0
- package/dist/prod/protocol.js +2 -0
- package/dist/prod/protocol.js.map +1 -0
- package/dist/prod/redis/client.js +2 -0
- package/dist/prod/redis/client.js.map +1 -0
- package/dist/prod/redis/scripts.js +157 -0
- package/dist/prod/redis/scripts.js.map +1 -0
- package/dist/prod/redis/topics.js +2 -0
- package/dist/prod/redis/topics.js.map +1 -0
- package/dist/prod/redis.js +2 -0
- package/dist/prod/redis.js.map +1 -0
- package/dist/prod/server/http.js +2 -0
- package/dist/prod/server/http.js.map +1 -0
- package/dist/prod/server/hub.js +2 -0
- package/dist/prod/server/hub.js.map +1 -0
- package/dist/prod/server/index.js +2 -0
- package/dist/prod/server/index.js.map +1 -0
- package/dist/prod/server/limits.js +2 -0
- package/dist/prod/server/limits.js.map +1 -0
- package/dist/prod/server/rules.js +2 -0
- package/dist/prod/server/rules.js.map +1 -0
- package/dist/prod/testing.js +2 -0
- package/dist/prod/testing.js.map +1 -0
- package/package.json +105 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.js","names":["FATAL","TRANSIENT","RETRYABLE_STATUS","createRealtime","reconnect","options","minDelay","maxDelay","stableMs","random","maxQueued","queueTtl","ackTtl","auto","id","clientId","life","chans","dirty","queue","inflight","statusListeners","status","mode","finished","scheduled","link","gen","ready","connId","maxBytes","streamSig","byeCode","wsFails","attempt","counter","keys","orders","received","silent","connectTimer","reconnectTimer","stableTimer","beatTimer","posting","postTimer","postFails","listening","report","error","guard","run","doFetch","input","init","getHeaders","emit","listener","listeners","value","setStatus","next","setChanStatus","chan","finish","op","settle","fail","succeed","arm","at","RealtimeError","ms","take","n","enqueue","flowing","old","item","flush","schedule","newOp","extra","encode","c","wantsAnswer","text","byteLength","sent","requeue","ops","a","b","again","wants","joins","watches","touch","subscription","join","watch","after","sendPresence","w","goIdle","syncChan","want","current","syncAll","batch","closeChan","resubscribe","now","backoff","declare","entries","signature","sig","connect","g","ev","onFrame","frame","linkClosed","info","onOnline","url","resolveUrl","openStream","openSocket","shutdown","drop","wasReady","code","lost","refused","why","delay","beat","reopen","onReady","notifyPresence","members","gap","meta","handler","viaPost","applyPresence","postNext","texts","bytes","postBatch","headers","sendUrl","response","parsed","decodeServerFrame","refusal","unanswered","needed","closed","pending","makeChannel","channelOptions","name","listen","set","passive","submit","isEventName","event","key","isIdempotencyKey","data","cause","request","kind","ack","reject","resolve","hn","handlers","publishOptions","requestOptions","state","callback","isChannelName"],"sources":["../../../src/client/core.ts"],"sourcesContent":["import { type ErrorCode, RealtimeError } from \"../errors\";\nimport {\n byteLength,\n decodeServerFrame,\n isChannelName,\n isEventName,\n isIdempotencyKey,\n type Json,\n type Member,\n type ServerFrame,\n} from \"../protocol\";\nimport { applyPresence } from \"./presence\";\nimport { type Link, type LinkEvents, openStream } from \"./sse\";\nimport type {\n AnyMessage,\n Channel,\n ChannelDef,\n ChannelOptions,\n ChannelSchema,\n ChannelStatus,\n GapInfo,\n MessageMeta,\n PublishAck,\n Realtime,\n RealtimeOptions,\n Status,\n} from \"./types\";\nimport { backoff, clientId, resolveUrl, sendUrl } from \"./util\";\nimport { openSocket } from \"./ws\";\n\n/** Close codes that mean the client did something the server will not accept again. */\nconst FATAL = new Set([1002, 1008, 1009]);\n/** Refusals that may heal by asking again. */\nconst TRANSIENT = new Set<ErrorCode>([\"unavailable\", \"rate_limited\", \"timeout\"]);\n/** HTTP sl of a stream request that are worth retrying among the 4xx. */\nconst RETRYABLE_STATUS = new Set([408, 425, 429]);\nconst MAX_FRAMES_PER_POST = 64;\nconst MAX_POST_BYTES = 256_000;\nconst MAX_STREAM_CHANNELS = 64;\nconst DEFAULT_REQUEST_MS = 10_000;\n\ntype Handler = (data: Json, meta: MessageMeta) => void;\ntype Timer = ReturnType<typeof setTimeout>;\n\ninterface Wire {\n /** The number the subscription went out with; the server answers with it. */\n readonly n: number;\n readonly join: boolean;\n readonly watch: boolean;\n /** The cursor the subscription asked to resume from. */\n readonly after: number | undefined;\n /** The presence state version the server hd for this connection. */\n ver: number;\n /** False until the server can accept presence changes for this connection (over SSE, until the member list arrived). */\n ready: boolean;\n}\n\ninterface Chan {\n readonly name: string;\n readonly options: ChannelOptions;\n readonly ev: Map<string, Set<Handler>>;\n readonly any: Set<(message: AnyMessage<ChannelDef>) => void>;\n readonly gl: Set<(info: GapInfo) => void>;\n readonly sl: Set<(status: ChannelStatus) => void>;\n readonly presence: Set<(members: readonly Member[]) => void>;\n readonly members: Map<string, Member>;\n list: readonly Member[] | undefined;\n status: ChannelStatus;\n /** Listeners registered; the channel subscribes while this is above zero. */\n refs: number;\n /** Requests in flight; a stateful connection answers a request only on a subscribed channel. */\n hd: number;\n last: number;\n /** True once a `subbed` arrived since the channel last went idle. */\n sy: boolean;\n state: Json | undefined;\n sv: number;\n w: Wire | undefined;\n retry: Timer | undefined;\n fails: number;\n closed: boolean;\n hn: Channel<ChannelDef> | undefined;\n}\n\ninterface Op {\n readonly kind: \"pub\" | \"req\" | \"pres\";\n readonly chan: Chan;\n readonly event: string;\n /** The JSON text of the data (or of the presence state). */\n readonly data: string;\n readonly key: string | undefined;\n readonly ack: boolean;\n readonly ms: number;\n readonly order: number;\n n: number;\n timer: Timer | undefined;\n settle: ((error: RealtimeError | undefined, value?: unknown) => void) | undefined;\n}\n\n/**\n * Creates the realtime client: one transport (WebSocket, falling back to server-sent ev), reconnects with backoff, a\n * bounded offline queue, resume from the last sequence number and typed channels. It connects when the first channel\n * listens or the first message is sent, never earlier.\n *\n * @param options The stream URL and the policy.\n * @returns The client.\n * @example\n * import { createRealtime } from \"@shadow-js/realtime/client\";\n *\n * type Schema = { [name: `chat:${string}`]: { ev: { message: { text: string } } } };\n * const rt = createRealtime<Schema>({ url: \"/rt\" });\n * const lobby = rt.channel(\"chat:lobby\");\n * const stop = lobby.on(\"message\", (data) => console.log(data.text));\n * await lobby.publish(\"message\", { text: \"hello\" });\n * stop();\n * rt.close();\n * @since 1.0.0\n */\nexport function createRealtime<Schema extends ChannelSchema = ChannelSchema>(\n options: RealtimeOptions,\n): Realtime<Schema> {\n const reconnect = options.reconnect ?? {};\n const minDelay = reconnect.minDelayMs ?? 250;\n const maxDelay = reconnect.maxDelayMs ?? 30_000;\n const stableMs = reconnect.stableMs ?? 5000;\n const random = reconnect.random ?? Math.random;\n const maxQueued = options.maxQueued ?? 100;\n const queueTtl = options.queueTtlMs ?? 30_000;\n const ackTtl = options.ackTimeoutMs ?? 10_000;\n const auto = (options.transport ?? \"auto\") === \"auto\";\n const id = clientId();\n const life = new AbortController();\n const chans = new Map<string, Chan>();\n const dirty = new Set<Chan>();\n const queue: Op[] = [];\n const inflight = new Map<number, Op>();\n const statusListeners = new Set<(status: Status) => void>();\n\n let status: Status = \"connecting\";\n let mode: \"ws\" | \"sse\" = options.transport === \"sse\" ? \"sse\" : \"ws\";\n let finished = false;\n let scheduled = false;\n let link: Link | undefined;\n let gen = 0;\n let ready = false;\n let connId: string | undefined;\n let maxBytes = 65_536;\n let streamSig = \"\";\n let byeCode: number | undefined;\n let wsFails = 0;\n let attempt = 0;\n let counter = 0;\n let keys = 0;\n let orders = 0;\n let received = false;\n let silent = 0;\n let connectTimer: Timer | undefined;\n let reconnectTimer: Timer | undefined;\n let stableTimer: Timer | undefined;\n let beatTimer: ReturnType<typeof setInterval> | undefined;\n let posting = false;\n let postTimer: Timer | undefined;\n let postFails = 0;\n let listening = false;\n\n const report = (error: unknown): void => {\n try {\n if (options.onError !== undefined) options.onError(error);\n else console.error(\"[realtime]\", error);\n } catch {\n // A failing reporter must not break the client.\n }\n };\n const guard = (run: () => void): void => {\n try {\n run();\n } catch (error) {\n report(error);\n }\n };\n const doFetch: typeof fetch = (input, init) => (options.fetch ?? globalThis.fetch)(input, init);\n const getHeaders = (): HeadersInit | undefined | Promise<HeadersInit | undefined> =>\n typeof options.headers === \"function\" ? options.headers() : options.headers;\n\n const emit = <T>(listeners: Iterable<(value: T) => void>, value: T): void => {\n for (const listener of [...listeners]) guard(() => listener(value));\n };\n const setStatus = (next: Status): void => {\n if (next === status) return;\n status = next;\n if (options.onStatus !== undefined) guard(() => options.onStatus?.(next));\n emit(statusListeners, next);\n };\n const setChanStatus = (chan: Chan, next: ChannelStatus): void => {\n if (chan.status === next) return;\n chan.status = next;\n emit(chan.sl, next);\n };\n\n // --- operations: publishes and requests, queued while there is no connection --------------------------------------\n\n /** Settles an operation once; an unacknowledged publish that fails is reported instead. */\n const finish = (op: Op, error: RealtimeError | undefined, value?: unknown): void => {\n clearTimeout(op.timer);\n const settle = op.settle;\n op.settle = undefined;\n if (settle !== undefined) settle(error, value);\n else if (error !== undefined && op.kind === \"pub\") report(error);\n };\n const fail = (op: Op, error: RealtimeError): void => finish(op, error);\n const succeed = (op: Op, value: unknown): void => finish(op, undefined, value);\n const arm = (op: Op, ms: number): void => {\n clearTimeout(op.timer);\n op.timer = setTimeout(() => {\n const at = queue.indexOf(op);\n if (at >= 0) queue.splice(at, 1);\n if (inflight.get(op.n) === op) inflight.delete(op.n);\n fail(op, new RealtimeError(\"timeout\"));\n }, ms);\n };\n const take = (n: number): Op | undefined => {\n const op = inflight.get(n);\n if (op !== undefined) inflight.delete(n);\n return op;\n };\n\n const enqueue = (op: Op): RealtimeError | undefined => {\n if (finished) return new RealtimeError(\"closed\");\n const flowing = ready && mode === \"ws\";\n if (op.kind !== \"pres\" && !flowing && queue.length >= maxQueued)\n return new RealtimeError(\"queue_full\");\n if (op.kind === \"pres\") {\n const old = queue.findIndex((item) => item.kind === \"pres\" && item.chan === op.chan);\n if (old >= 0) queue.splice(old, 1);\n } else arm(op, queueTtl);\n queue.push(op);\n if (ready) flush();\n else schedule();\n return undefined;\n };\n\n const newOp = (\n kind: Op[\"kind\"],\n chan: Chan,\n event: string,\n data: string,\n extra: Partial<Pick<Op, \"key\" | \"ack\" | \"ms\" | \"settle\">>,\n ): Op => ({\n kind,\n chan,\n event,\n data,\n key: extra.key,\n ack: extra.ack ?? true,\n ms: extra.ms ?? 0,\n order: ++orders,\n n: 0,\n timer: undefined,\n settle: extra.settle,\n });\n\n /** The frame text of an operation, numbered for an answer. Undefined (and the operation failed) when it is too large. */\n const encode = (op: Op): string | undefined => {\n const c = JSON.stringify(op.chan.name);\n const wantsAnswer = op.kind === \"req\" || (op.kind === \"pub\" && op.ack);\n op.n = wantsAnswer ? ++counter : 0;\n const text =\n op.kind === \"pres\"\n ? `{\"t\":\"pres\",\"c\":${c},\"s\":${op.data}}`\n : op.kind === \"req\"\n ? `{\"t\":\"req\",\"n\":${op.n},\"c\":${c},\"e\":\"${op.event}\",\"d\":${op.data},\"ms\":${op.ms}}`\n : `{\"t\":\"pub\",\"c\":${c},\"e\":\"${op.event}\",\"d\":${op.data}${wantsAnswer ? `,\"n\":${op.n}` : \"\"},\"k\":${JSON.stringify(op.key)}}`;\n if (byteLength(text) > maxBytes) {\n fail(op, new RealtimeError(\"too_large\"));\n return undefined;\n }\n return text;\n };\n\n /** Records that an operation went out: it now waits for an answer, or is done. */\n const sent = (op: Op): void => {\n if (op.kind === \"pres\") return;\n if (op.kind === \"req\" || op.ack) {\n inflight.set(op.n, op);\n arm(op, op.kind === \"req\" ? op.ms + 1000 : ackTtl);\n } else clearTimeout(op.timer);\n };\n\n /** Puts operations that went out on a connection that died back at the head of the queue, in their original order. */\n const requeue = (ops: Op[]): void => {\n ops.sort((a, b) => a.order - b.order);\n const again: Op[] = [];\n for (const op of ops) {\n if (op.kind === \"req\") fail(op, new RealtimeError(\"closed\"));\n else {\n if (op.kind === \"pub\") arm(op, queueTtl);\n again.push(op);\n }\n }\n queue.unshift(...again);\n };\n\n // --- channels -----------------------------------------------------------------------------------------------------\n\n const wants = (chan: Chan): boolean =>\n !chan.closed &&\n (chan.refs > 0 ||\n chan.state !== undefined ||\n chan.options.watch === true ||\n (chan.hd > 0 && mode === \"ws\"));\n const joins = (chan: Chan): boolean => chan.state !== undefined;\n const watches = (chan: Chan): boolean =>\n !joins(chan) && (chan.presence.size > 0 || chan.options.watch === true);\n\n const touch = (chan: Chan): void => {\n if (chan.status === \"idle\" && wants(chan)) setChanStatus(chan, \"subscribing\");\n dirty.add(chan);\n schedule();\n };\n\n /** The fields a subscription goes out with, over a WebSocket as a frame and over SSE as a query entry. */\n const subscription = (chan: Chan, n: number): { fields: Record<string, unknown> } => {\n const join = joins(chan);\n const watch = watches(chan);\n const after = chan.sy ? chan.last : chan.options.replay === true ? 0 : undefined;\n const w: Wire = { n, join, watch, after, ver: chan.sv, ready: !join };\n chan.w = w;\n setChanStatus(chan, chan.sy ? \"resuming\" : \"subscribing\");\n return {\n fields: {\n c: chan.name,\n ...(after === undefined ? {} : { after }),\n ...(chan.options.echo === false ? { echo: false } : {}),\n ...(join ? { presence: chan.state } : {}),\n ...(watch ? { watch: true } : {}),\n },\n };\n };\n\n const sendPresence = (chan: Chan): void => {\n const w = chan.w;\n if (w === undefined || chan.state === undefined) return;\n w.ver = chan.sv;\n const op = newOp(\"pres\", chan, \"\", JSON.stringify(chan.state), {});\n if (mode === \"ws\") {\n const text = encode(op);\n if (text !== undefined) link?.send(text);\n } else enqueue(op);\n };\n\n const goIdle = (chan: Chan): void => {\n chan.sy = false;\n chan.last = 0;\n chan.w = undefined;\n clearTimeout(chan.retry);\n chan.retry = undefined;\n chan.fails = 0;\n if (chan.members.size > 0) {\n chan.members.clear();\n chan.list = undefined;\n }\n setChanStatus(chan, chan.closed ? \"closed\" : \"idle\");\n };\n\n /** Brings the w in line with what a channel wants (WebSocket: subscribe, unsubscribe, presence). */\n const syncChan = (chan: Chan): void => {\n const want = wants(chan);\n const w = chan.w;\n if (mode === \"ws\" && ready) {\n if (w !== undefined && (!want || w.join !== joins(chan) || w.watch !== watches(chan))) {\n link?.send(`{\"t\":\"unsub\",\"c\":${JSON.stringify(chan.name)}}`);\n chan.w = undefined;\n }\n if (want && chan.w === undefined && chan.retry === undefined) {\n const n = ++counter;\n link?.send(JSON.stringify({ t: \"sub\", n, ...subscription(chan, n).fields }));\n }\n }\n if (!want) {\n if (chan.status !== \"idle\" && chan.status !== \"closed\") goIdle(chan);\n return;\n }\n const current = chan.w;\n if (current?.join && current.ready && current.ver !== chan.sv) sendPresence(chan);\n };\n\n const syncAll = (): void => {\n const batch = [...dirty];\n dirty.clear();\n for (const chan of batch) syncChan(chan);\n };\n\n const closeChan = (chan: Chan, error?: RealtimeError): void => {\n if (chan.closed) return;\n chan.closed = true;\n if (chans.get(chan.name) === chan) chans.delete(chan.name);\n if (chan.w !== undefined && mode === \"ws\" && ready)\n link?.send(`{\"t\":\"unsub\",\"c\":${JSON.stringify(chan.name)}}`);\n goIdle(chan);\n chan.ev.clear();\n chan.any.clear();\n chan.gl.clear();\n chan.presence.clear();\n chan.sl.clear();\n chan.refs = 0;\n dirty.delete(chan);\n if (error !== undefined) report(error);\n schedule();\n };\n\n /** Subscribes again after a refusal or a removal the server announced; `now` skips the delay. */\n const resubscribe = (chan: Chan, now: boolean): void => {\n chan.w = undefined;\n setChanStatus(chan, chan.sy ? \"resuming\" : \"subscribing\");\n if (now) {\n touch(chan);\n return;\n }\n chan.retry = setTimeout(\n () => {\n chan.retry = undefined;\n touch(chan);\n },\n backoff(chan.fails, minDelay, maxDelay, random),\n );\n chan.fails += 1;\n };\n\n // --- connection ---------------------------------------------------------------------------------------------------\n\n /** Declares the subscriptions of an SSE stream; returns the query entries. */\n const declare = (): unknown[] => {\n const entries: unknown[] = [];\n for (const chan of [...chans.values()]) {\n if (!wants(chan)) continue;\n if (entries.length >= MAX_STREAM_CHANNELS) {\n closeChan(chan, new RealtimeError(\"limit\"));\n continue;\n }\n entries.push(subscription(chan, entries.length + 1).fields);\n }\n streamSig = signature();\n return entries;\n };\n\n /** What the open stream must be reopened for: the channels it carries and how they join. */\n const signature = (): string => {\n let sig = \"\";\n for (const chan of chans.values())\n if (wants(chan))\n sig += `${chan.name}|${chan.options.echo === false ? 0 : 1}${joins(chan) ? 1 : 0}${watches(chan) ? 1 : 0};`;\n return sig;\n };\n\n const connect = (): void => {\n clearTimeout(reconnectTimer);\n reconnectTimer = undefined;\n if (finished || link !== undefined) return;\n gen += 1;\n const g = gen;\n ready = false;\n received = false;\n silent = 0;\n const ev: LinkEvents = {\n rx: () => {\n if (g === gen) received = true;\n },\n frame: (frame) => {\n if (g === gen) guard(() => onFrame(frame, false));\n },\n closed: (info) => {\n if (g === gen) linkClosed(info);\n },\n };\n try {\n if (!listening && typeof addEventListener === \"function\") {\n listening = true;\n addEventListener(\"online\", onOnline);\n }\n if (mode === \"sse\") {\n const url = resolveUrl(options.url, false);\n url.searchParams.set(\"s\", JSON.stringify(declare()));\n link = openStream(url.toString(), doFetch, getHeaders, ev);\n } else\n link = openSocket(\n resolveUrl(options.url, true).toString(),\n options.WebSocket ?? globalThis.WebSocket,\n ev,\n );\n } catch (error) {\n gen += 1;\n shutdown(new RealtimeError(\"closed\", \"cannot open\", { cause: error }));\n return;\n }\n connectTimer = setTimeout(() => {\n if (g === gen) linkClosed({});\n }, ackTtl);\n };\n\n const onOnline = (): void => {\n if (reconnectTimer !== undefined) connect();\n };\n\n /** Releases the current transport and everything tied to it. */\n const drop = (): void => {\n gen += 1;\n const old = link;\n link = undefined;\n ready = false;\n connId = undefined;\n clearTimeout(connectTimer);\n clearTimeout(stableTimer);\n clearInterval(beatTimer);\n beatTimer = undefined;\n old?.close();\n };\n\n const linkClosed = (info: { readonly code?: number; readonly status?: number }): void => {\n const wasReady = ready;\n const code = byeCode ?? info.code;\n byeCode = undefined;\n drop();\n if (finished) return;\n if (mode === \"ws\" && inflight.size > 0) {\n const lost = [...inflight.values()];\n inflight.clear();\n requeue(lost);\n }\n for (const chan of chans.values()) {\n chan.w = undefined;\n clearTimeout(chan.retry);\n chan.retry = undefined;\n if (wants(chan)) setChanStatus(chan, chan.sy ? \"resuming\" : \"subscribing\");\n }\n const refused =\n info.status !== undefined &&\n info.status >= 400 &&\n info.status < 500 &&\n !RETRYABLE_STATUS.has(info.status);\n if (((code !== undefined && FATAL.has(code)) || refused) && reconnect.retryFatal !== true) {\n const why =\n code !== undefined\n ? `the server closed the connection with ${code}`\n : `the server answered ${info.status}`;\n shutdown(new RealtimeError(\"closed\", why));\n return;\n }\n let delay = backoff(attempt, minDelay, maxDelay, random);\n attempt += 1;\n if (wasReady) wsFails = 0;\n else if (auto && mode === \"ws\") {\n wsFails += 1;\n if (wsFails >= 2) {\n mode = \"sse\";\n delay = 0;\n }\n }\n setStatus(\"reconnecting\");\n reconnectTimer = setTimeout(connect, delay);\n };\n\n /** Heartbeat: every interval sends a ping; two intervals with nothing received declare the connection dead. */\n const beat = (): void => {\n if (received) {\n received = false;\n silent = 0;\n } else {\n silent += 1;\n if (silent >= 2) {\n linkClosed({});\n return;\n }\n }\n if (mode === \"ws\") link?.send('{\"t\":\"ping\"}');\n };\n\n const reopen = (): void => {\n drop();\n for (const chan of chans.values()) chan.w = undefined;\n connect();\n };\n\n const onReady = (frame: Extract<ServerFrame, { t: \"ready\" }>): void => {\n if (ready) return;\n ready = true;\n connId = frame.id;\n maxBytes = frame.max;\n wsFails = 0;\n received = true;\n silent = 0;\n clearTimeout(connectTimer);\n if (frame.hb > 0) beatTimer = setInterval(beat, frame.hb);\n stableTimer = setTimeout(() => {\n attempt = 0;\n }, stableMs);\n setStatus(\"open\");\n if (mode === \"ws\") for (const chan of chans.values()) dirty.add(chan);\n flush();\n };\n\n // --- frames from the server ---------------------------------------------------------------------------------------\n\n const notifyPresence = (chan: Chan): void => {\n const members = [...chan.members.values()];\n chan.list = members;\n emit(chan.presence, members);\n };\n\n const onFrame = (frame: ServerFrame, viaPost: boolean): void => {\n switch (frame.t) {\n case \"ready\":\n onReady(frame);\n return;\n case \"bye\":\n byeCode = frame.code;\n return;\n case \"subbed\": {\n const chan = chans.get(frame.c);\n const w = chan?.w;\n if (chan === undefined || w === undefined || w.n !== frame.n) return;\n if (frame.reset || w.after === undefined) chan.last = frame.head;\n const info = { after: w.after ?? 0, head: frame.head };\n const gap = frame.reset\n ? true\n : frame.gap === true && w.after !== undefined && !(w.after === 0 && !chan.sy);\n chan.sy = true;\n chan.fails = 0;\n setChanStatus(chan, \"live\");\n if (gap) emit(chan.gl, { ...info, reset: frame.reset === true });\n return;\n }\n case \"unsubbed\": {\n const chan = chans.get(frame.c);\n if (chan === undefined) return;\n if (frame.why === \"revoked\") {\n // The hub dropped the subscription: closing must not send an unsub it would answer with not_subscribed.\n chan.w = undefined;\n closeChan(chan, new RealtimeError(\"forbidden\", \"revoked\"));\n } else resubscribe(chan, frame.why === \"gap\" || frame.why === \"reset\");\n return;\n }\n case \"msg\": {\n const chan = chans.get(frame.c);\n if (chan === undefined || frame.s <= chan.last) return;\n chan.last = frame.s;\n const meta: MessageMeta = {\n seq: frame.s,\n replayed: frame.r === 1,\n ...(frame.p === undefined ? {} : { publisher: frame.p }),\n ...(frame.u === undefined ? {} : { user: frame.u }),\n };\n for (const handler of [...(chan.ev.get(frame.e) ?? [])])\n guard(() => handler(frame.d, meta));\n for (const handler of [...chan.any])\n guard(() => handler({ event: frame.e, data: frame.d, meta } as AnyMessage<ChannelDef>));\n return;\n }\n case \"ack\": {\n const op = take(frame.n);\n if (op !== undefined) succeed(op, { seq: frame.s, duplicate: frame.dup === true });\n return;\n }\n case \"rep\": {\n const op = take(frame.n);\n if (op !== undefined) succeed(op, frame.d);\n return;\n }\n case \"err\": {\n const error = new RealtimeError(frame.code, frame.msg);\n const chan = frame.c === undefined ? undefined : chans.get(frame.c);\n if (frame.n !== undefined) {\n if (!viaPost && chan?.w?.n === frame.n) {\n if (TRANSIENT.has(frame.code)) {\n resubscribe(chan, false);\n report(error);\n } else {\n chan.w = undefined;\n closeChan(chan, error);\n }\n return;\n }\n // Over SSE a number on the stream names a subscription; only a POST answer names an operation.\n const op = viaPost || mode === \"ws\" ? take(frame.n) : undefined;\n if (op !== undefined) {\n fail(op, error);\n return;\n }\n }\n report(error);\n return;\n }\n case \"pres\": {\n const chan = chans.get(frame.c);\n if (chan === undefined) return;\n if (applyPresence(chan.members, frame.op, frame.m)) notifyPresence(chan);\n if (frame.op === \"sync\" && chan.w !== undefined) {\n chan.w.ready = true;\n if (chan.w.join && chan.w.ver !== chan.sv) sendPresence(chan);\n }\n return;\n }\n case \"pong\":\n return;\n }\n };\n\n // --- sending ------------------------------------------------------------------------------------------------------\n\n const flush = (): void => {\n if (!ready || finished) return;\n syncAll();\n if (mode === \"sse\") {\n postNext();\n return;\n }\n for (let op = queue[0]; op !== undefined && ready; op = queue[0]) {\n const text = encode(op);\n if (text !== undefined) {\n if (link?.send(text) !== true) return;\n sent(op);\n }\n queue.shift();\n }\n };\n\n const postNext = (): void => {\n if (posting || postTimer !== undefined || connId === undefined) return;\n const ops: Op[] = [];\n const texts: string[] = [];\n let bytes = 0;\n while (queue.length > 0 && ops.length < MAX_FRAMES_PER_POST && bytes < MAX_POST_BYTES) {\n const op = queue.shift();\n if (op === undefined) break;\n const text = encode(op);\n if (text === undefined) continue;\n ops.push(op);\n texts.push(text);\n bytes += text.length;\n }\n if (ops.length === 0) return;\n posting = true;\n postBatch(ops, `{\"cid\":${JSON.stringify(connId)},\"frames\":[${texts.join(\",\")}]}`).then(\n undefined,\n report,\n );\n };\n\n const postBatch = async (ops: Op[], body: string): Promise<void> => {\n for (const op of ops) sent(op);\n let again = false;\n try {\n const headers = new Headers(await getHeaders());\n headers.set(\"content-type\", \"application/json\");\n const url =\n options.postUrl === undefined\n ? sendUrl(resolveUrl(options.url, false))\n : resolveUrl(options.postUrl, false);\n const response = await doFetch(url, { method: \"POST\", headers, body, signal: life.signal });\n if (response.ok) {\n const parsed = (await response.json()) as { frames?: unknown };\n if (Array.isArray(parsed.frames))\n for (const item of parsed.frames as unknown[]) {\n const frame = decodeServerFrame(JSON.stringify(item));\n if (frame !== undefined) guard(() => onFrame(frame, true));\n }\n postFails = 0;\n } else if (response.status >= 500 || RETRYABLE_STATUS.has(response.status)) again = true;\n else {\n const refusal = (await response.json().catch(() => undefined)) as\n | { error?: { code?: ErrorCode; message?: string } }\n | undefined;\n const code: ErrorCode = refusal?.error?.code ?? \"unavailable\";\n for (const op of ops) {\n if (inflight.get(op.n) === op) inflight.delete(op.n);\n fail(op, new RealtimeError(code, refusal?.error?.message));\n }\n }\n } catch {\n if (life.signal.aborted) return;\n again = true;\n }\n posting = false;\n const unanswered = ops.filter(\n (op) => (op.kind === \"req\" || (op.kind === \"pub\" && op.ack)) && inflight.get(op.n) === op,\n );\n for (const op of unanswered) inflight.delete(op.n);\n if (again) {\n for (const op of unanswered)\n if (op.kind === \"req\") fail(op, new RealtimeError(\"unavailable\"));\n requeue(\n ops.filter(\n (op) => op.kind === \"pres\" || (op.kind === \"pub\" && (!op.ack || unanswered.includes(op))),\n ),\n );\n postFails += 1;\n postTimer = setTimeout(\n () => {\n postTimer = undefined;\n flush();\n },\n backoff(postFails - 1, minDelay, maxDelay, random),\n );\n } else for (const op of unanswered) fail(op, new RealtimeError(\"unavailable\"));\n flush();\n };\n\n // --- lifecycle ----------------------------------------------------------------------------------------------------\n\n const needed = (): boolean => queue.length > 0 || [...chans.values()].some(wants);\n\n const run = (): void => {\n scheduled = false;\n if (finished) return;\n syncAll();\n if (link === undefined) {\n if (reconnectTimer === undefined && needed()) connect();\n return;\n }\n if (\n mode === \"sse\" &&\n (signature() !== streamSig ||\n [...chans.values()].some((c) => wants(c) && c.w === undefined && c.retry === undefined))\n )\n reopen();\n else flush();\n };\n\n function schedule(): void {\n if (scheduled || finished) return;\n scheduled = true;\n queueMicrotask(run);\n }\n\n function shutdown(error?: RealtimeError): void {\n if (finished) return;\n finished = true;\n clearTimeout(reconnectTimer);\n clearTimeout(postTimer);\n reconnectTimer = undefined;\n postTimer = undefined;\n drop();\n life.abort();\n if (listening && typeof removeEventListener === \"function\")\n removeEventListener(\"online\", onOnline);\n listening = false;\n const closed = error ?? new RealtimeError(\"closed\");\n const pending = [...queue.splice(0), ...inflight.values()];\n inflight.clear();\n for (const op of pending) {\n if (op.settle === undefined) clearTimeout(op.timer);\n else fail(op, closed);\n }\n for (const chan of [...chans.values()]) closeChan(chan);\n chans.clear();\n dirty.clear();\n setStatus(\"closed\");\n statusListeners.clear();\n if (error !== undefined) report(error);\n }\n\n // --- the public handles -------------------------------------------------------------------------------------------\n\n const makeChannel = (name: string, channelOptions: ChannelOptions): Channel<ChannelDef> => {\n const chan: Chan = {\n name,\n options: channelOptions,\n ev: new Map(),\n any: new Set(),\n gl: new Set(),\n sl: new Set(),\n presence: new Set(),\n members: new Map(),\n list: undefined,\n status: \"idle\",\n refs: 0,\n hd: 0,\n last: 0,\n sy: false,\n state: channelOptions.presence,\n sv: 0,\n w: undefined,\n retry: undefined,\n fails: 0,\n closed: false,\n hn: undefined,\n };\n chans.set(name, chan);\n if (wants(chan)) touch(chan);\n\n /** Registers a listener; unless `passive`, it keeps the channel subscribed until removed. */\n const listen = <T>(\n set: Set<T>,\n item: T,\n passive?: boolean,\n after?: () => void,\n ): (() => void) => {\n if (chan.closed) return () => undefined;\n set.add(item);\n if (!passive) {\n chan.refs += 1;\n touch(chan);\n }\n return () => {\n if (!set.delete(item) || passive) return;\n chan.refs -= 1;\n after?.();\n touch(chan);\n };\n };\n\n /** Validates and queues a publish or a request; the promise settles with its answer. */\n const submit = <T>(\n kind: \"pub\" | \"req\",\n event: string,\n data: unknown,\n key: string | undefined,\n ack: boolean,\n ms: number,\n ): Promise<T> => {\n let text = \"null\";\n let refusal: RealtimeError | undefined;\n if (chan.closed) refusal = new RealtimeError(\"closed\");\n else if (!isEventName(event) || (key !== undefined && !isIdempotencyKey(key)))\n refusal = new RealtimeError(\"bad_frame\");\n else\n try {\n text = JSON.stringify(data ?? null) ?? \"null\";\n } catch (cause) {\n refusal = new RealtimeError(\"bad_frame\", \"not JSON\", { cause });\n }\n if (refusal !== undefined) return Promise.reject(refusal);\n return new Promise<T>((resolve, reject) => {\n const request = kind === \"req\";\n const op = newOp(kind, chan, event, text, {\n key: request ? undefined : (key ?? `${id}:${++keys}`),\n ack,\n ms,\n settle: ack\n ? (error, value) => {\n if (request) {\n chan.hd -= 1;\n touch(chan);\n }\n if (error) reject(error);\n else resolve(value as T);\n }\n : undefined,\n });\n if (request) {\n chan.hd += 1;\n touch(chan);\n }\n const refused = enqueue(op);\n if (refused !== undefined) {\n op.settle = undefined;\n if (request) chan.hd -= 1;\n reject(refused);\n } else if (!ack) resolve({ seq: 0, duplicate: false } as T);\n });\n };\n\n const hn: Channel<ChannelDef> = {\n name,\n get status() {\n return chan.status;\n },\n get lastSeq() {\n return chan.last;\n },\n on(event, handler) {\n let set = chan.ev.get(event);\n if (set === undefined) {\n set = new Set();\n chan.ev.set(event, set);\n }\n const handlers = set;\n return listen(handlers, handler as Handler, false, () => {\n if (handlers.size === 0 && chan.ev.get(event) === handlers) chan.ev.delete(event);\n });\n },\n onAny: (handler) => listen(chan.any, handler as (message: AnyMessage<ChannelDef>) => void),\n onGap: (handler) => listen(chan.gl, handler),\n onStatus: (handler) => listen(chan.sl, handler, true),\n publish: (event, data, publishOptions = {}) =>\n submit<PublishAck>(\"pub\", event, data, publishOptions.key, publishOptions.ack !== false, 0),\n request: (event, data, requestOptions = {}) =>\n submit(\"req\", event, data, undefined, true, requestOptions.timeoutMs ?? DEFAULT_REQUEST_MS),\n presence: {\n members: () => {\n chan.list ??= [...chan.members.values()];\n return chan.list as never;\n },\n set(state) {\n if (chan.closed) return;\n chan.state = state as Json;\n chan.sv += 1;\n touch(chan);\n },\n leave() {\n if (chan.closed || chan.state === undefined) return;\n chan.state = undefined;\n chan.sv += 1;\n touch(chan);\n },\n on: (callback) => listen(chan.presence, callback as (members: readonly Member[]) => void),\n },\n close: () => closeChan(chan),\n };\n chan.hn = hn;\n return hn;\n };\n\n return {\n channel(name, channelOptions = {}) {\n if (finished) throw new RealtimeError(\"closed\");\n if (!isChannelName(name)) throw new RealtimeError(\"bad_channel\", name);\n const live = chans.get(name);\n return (live?.hn ?? makeChannel(name, channelOptions)) as never;\n },\n get status() {\n return status;\n },\n get connectionId() {\n return connId;\n },\n onStatus(handler) {\n if (!finished) statusListeners.add(handler);\n return () => {\n statusListeners.delete(handler);\n };\n },\n close: () => shutdown(),\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{RealtimeError as e}from"../errors.js";import{createRealtime as t}from"./core.js";export{e as RealtimeError,t as createRealtime};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presence.js","names":["applyPresence","op","table","member","payload","known"],"sources":["../../../src/client/presence.ts"],"sourcesContent":["import type { Member } from \"../protocol\";\n\n/**\n * Applies one presence frame to the member table, ordered by member version so a repeated or reordered event changes\n * nothing. Returns whether the table changed.\n * @internal\n */\nexport function applyPresence(\n table: Map<string, Member>,\n op: \"sync\" | \"join\" | \"set\" | \"leave\",\n payload: Member | readonly Member[],\n): boolean {\n if (op === \"sync\") {\n table.clear();\n for (const member of payload as readonly Member[]) table.set(member.id, member);\n return true;\n }\n const member = payload as Member;\n const known = table.get(member.id);\n if (op === \"leave\") return known !== undefined && member.v >= known.v && table.delete(member.id);\n if (known !== undefined && member.v <= known.v) return false;\n table.set(member.id, member);\n return true;\n}\n"],"mappings":"AAOA,SAAgBA,EACd,EACA,EACA,EACS,CACT,GAAIC,IAAO,OAAQ,CACjBC,EAAM,MAAM,EACZ,IAAK,IAAMC,KAAUC,EAA8BF,EAAM,IAAIC,EAAO,GAAIA,CAAM,EAC9E,MAAO,EACT,CACA,IAAMA,EAASC,EACTC,EAAQH,EAAM,IAAIC,EAAO,EAAE,EAIjC,OAHIF,IAAO,QAAgBI,IAAU,IAAA,IAAaF,EAAO,GAAKE,EAAM,GAAKH,EAAM,OAAOC,EAAO,EAAE,EAC3FE,IAAU,IAAA,IAAaF,EAAO,GAAKE,EAAM,EAAU,IACvDH,EAAM,IAAIC,EAAO,GAAIA,CAAM,EACpB,GACT,C"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import{decodeServerFrame as e}from"../protocol.js";function t(e){let t=``,n=``,r=[];return i=>{t+=i;let a=0;for(let i=0;i<t.length;i+=1){let o=t[i];if(o!==`
|
|
2
|
+
`&&o!==`\r`)continue;if(o===`\r`&&i===t.length-1)break;let s=t.slice(a,i);if(o===`\r`&&t[i+1]===`
|
|
3
|
+
`&&(i+=1),a=i+1,s===``){n===`rt`&&r.length>0&&e(r.join(`
|
|
4
|
+
`)),n=``,r=[];continue}if(s[0]===`:`)continue;let c=s.indexOf(`:`),l=c<0?s:s.slice(0,c),u=c<0?``:s.slice(c+1);u[0]===` `&&(u=u.slice(1)),l===`event`?n=u:l===`data`&&r.push(u)}t=t.slice(a)}}function n(n,r,i,a){let o=new AbortController;return(async()=>{let s=new Headers(await i());s.set(`accept`,`text/event-stream`);let c=await r(n,{headers:s,signal:o.signal,cache:`no-store`});if(!c.ok||c.body===null)return c.body?.cancel().catch(()=>void 0),a.closed({status:c.status});let l=c.body.getReader(),u=new TextDecoder,d=t(t=>{let n=e(t);n!==void 0&&a.frame(n)});for(;;){let{done:e,value:t}=await l.read();if(e)break;a.rx(),d(u.decode(t,{stream:!0}))}a.closed({})})().then(void 0,()=>{o.signal.aborted||a.closed({})}),{send:()=>!1,close:()=>o.abort()}}export{n as openStream,t as sseParser};
|
|
5
|
+
//# sourceMappingURL=sse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse.js","names":["sseParser","buffer","name","data","chunk","start","at","char","line","onData","colon","key","value","openStream","control","headers","getHeaders","response","send","url","events","reader","decoder","feed","frame","decodeServerFrame","done"],"sources":["../../../src/client/sse.ts"],"sourcesContent":["import { decodeServerFrame, type ServerFrame } from \"../protocol\";\n\n/** What a transport reports to the client. @internal */\nexport interface LinkEvents {\n /** Any bytes arrived (a comment counts: it proves the stream is alive). */\n rx(): void;\n frame(frame: ServerFrame): void;\n /** The transport ended. `code` is a WebSocket close code, `status` an HTTP status. */\n closed(info: { readonly code?: number; readonly status?: number }): void;\n}\n\n/** One open transport. @internal */\nexport interface Link {\n send(text: string): boolean;\n close(): void;\n}\n\n/** Splits an event stream into `rt` events; keeps a partial line (and a trailing CR) for the next chunk. @internal */\nexport function sseParser(onData: (data: string) => void): (chunk: string) => void {\n let buffer = \"\";\n let name = \"\";\n let data: string[] = [];\n return (chunk) => {\n buffer += chunk;\n let start = 0;\n for (let at = 0; at < buffer.length; at += 1) {\n const char = buffer[at];\n if (char !== \"\\n\" && char !== \"\\r\") continue;\n if (char === \"\\r\" && at === buffer.length - 1) break;\n const line = buffer.slice(start, at);\n if (char === \"\\r\" && buffer[at + 1] === \"\\n\") at += 1;\n start = at + 1;\n if (line === \"\") {\n if (name === \"rt\" && data.length > 0) onData(data.join(\"\\n\"));\n name = \"\";\n data = [];\n continue;\n }\n if (line[0] === \":\") continue;\n const colon = line.indexOf(\":\");\n const key = colon < 0 ? line : line.slice(0, colon);\n let value = colon < 0 ? \"\" : line.slice(colon + 1);\n if (value[0] === \" \") value = value.slice(1);\n if (key === \"event\") name = value;\n else if (key === \"data\") data.push(value);\n }\n buffer = buffer.slice(start);\n };\n}\n\n/** Reads a server-sent events stream with `fetch`, so headers work. Frames are events named `rt`. @internal */\nexport function openStream(\n url: string,\n send: typeof fetch,\n getHeaders: () => HeadersInit | undefined | Promise<HeadersInit | undefined>,\n events: LinkEvents,\n): Link {\n const control = new AbortController();\n const run = async (): Promise<void> => {\n const headers = new Headers(await getHeaders());\n headers.set(\"accept\", \"text/event-stream\");\n const response = await send(url, { headers, signal: control.signal, cache: \"no-store\" });\n if (!response.ok || response.body === null) {\n void response.body?.cancel().catch(() => undefined);\n return events.closed({ status: response.status });\n }\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n const feed = sseParser((data) => {\n const frame = decodeServerFrame(data);\n if (frame !== undefined) events.frame(frame);\n });\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n events.rx();\n feed(decoder.decode(value, { stream: true }));\n }\n events.closed({});\n };\n const end = (): void => {\n if (!control.signal.aborted) events.closed({});\n };\n run().then(undefined, end);\n return { send: () => false, close: () => control.abort() };\n}\n"],"mappings":"A,mDAkBA,SAAgBA,EAAU,EAAyD,CACjF,IAAIC,EAAS,GACTC,EAAO,GACPC,EAAiB,CAAC,EACtB,MAAQ,IAAU,CAChB,GAAUC,EACV,IAAIC,EAAQ,EACZ,IAAK,IAAIC,EAAK,EAAGA,EAAKL,EAAO,OAAQ,GAAM,EAAG,CAC5C,IAAMM,EAAON,EAAOK,GACpB,GAAIC,IAAS;GAAQA,IAAS,KAAM,SACpC,GAAIA,IAAS,MAAQD,IAAOL,EAAO,OAAS,EAAG,MAC/C,IAAMO,EAAOP,EAAO,MAAMI,EAAOC,CAAE,EAGnC,GAFIC,IAAS,MAAQN,EAAOK,EAAK,KAAO;IAAM,GAAM,GACpD,EAAQA,EAAK,EACTE,IAAS,GAAI,CACXN,IAAS,MAAQC,EAAK,OAAS,GAAGM,EAAON,EAAK,KAAK;CAAI,CAAC,EAC5D,EAAO,GACP,EAAO,CAAC,EACR,QACF,CACA,GAAIK,EAAK,KAAO,IAAK,SACrB,IAAME,EAAQF,EAAK,QAAQ,GAAG,EACxBG,EAAMD,EAAQ,EAAIF,EAAOA,EAAK,MAAM,EAAGE,CAAK,EAC9CE,EAAQF,EAAQ,EAAI,GAAKF,EAAK,MAAME,EAAQ,CAAC,EAC7CE,EAAM,KAAO,MAAK,EAAQA,EAAM,MAAM,CAAC,GACvCD,IAAQ,QAAS,EAAOC,EACnBD,IAAQ,QAAQR,EAAK,KAAKS,CAAK,CAC1C,CACA,EAASX,EAAO,MAAMI,CAAK,CAC7B,CACF,CAGA,SAAgBQ,EACd,EACA,EACA,EACA,EACM,CACN,IAAMC,EAAU,IAAI,gBA2BpB,OADA,SAzBuC,CACrC,IAAMC,EAAU,IAAI,QAAQ,MAAMC,EAAW,CAAC,EAC9CD,EAAQ,IAAI,SAAU,mBAAmB,EACzC,IAAME,EAAW,MAAMC,EAAKC,EAAK,CAAE,UAAS,OAAQL,EAAQ,OAAQ,MAAO,UAAW,CAAC,EACvF,GAAI,CAACG,EAAS,IAAMA,EAAS,OAAS,KAEpC,OADAA,EAAc,MAAM,OAAO,CAAC,CAAC,UAAY,IAAA,EAAS,EAC3CG,EAAO,OAAO,CAAE,OAAQH,EAAS,MAAO,CAAC,EAElD,IAAMI,EAASJ,EAAS,KAAK,UAAU,EACjCK,EAAU,IAAI,YACdC,EAAOvB,EAAW,GAAS,CAC/B,IAAMwB,EAAQC,EAAkBtB,CAAI,EAChCqB,IAAU,IAAA,IAAWJ,EAAO,MAAMI,CAAK,CAC7C,CAAC,EACD,OAAS,CACP,GAAM,CAAE,OAAM,SAAU,MAAMH,EAAO,KAAK,EAC1C,GAAIK,EAAM,MACVN,EAAO,GAAG,EACVG,EAAKD,EAAQ,OAAOV,EAAO,CAAE,OAAQ,EAAK,CAAC,CAAC,CAC9C,CACAQ,EAAO,OAAO,CAAC,CAAC,CAClB,EAIA,CAAI,CAAC,CAAC,KAAK,IAAA,OAHa,CACjBN,EAAQ,OAAO,SAASM,EAAO,OAAO,CAAC,CAAC,CAC/C,CACyB,EAClB,CAAE,SAAY,GAAO,UAAaN,EAAQ,MAAM,CAAE,CAC3D,C"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function e(e,t,n,r){return r()*Math.min(n,t*2**Math.min(e,30))}function t(){let e=/* @__PURE__ */ new Uint8Array(8);globalThis.crypto.getRandomValues(e);let t=``;for(let n of e)t+=n.toString(36).padStart(2,`0`);return t.slice(0,12)}function n(e,t){let n=globalThis.location?.href,r=new URL(e,n!==void 0&&URL.canParse(e,n)?n:`http://localhost/`);return t&&(r.protocol=r.protocol===`https:`?`wss:`:`ws:`),r}function r(e){let t=new URL(e);return t.pathname=`${t.pathname.replace(/\/$/,``)}/send`,t}export{e as backoff,t as clientId,n as resolveUrl,r as sendUrl};
|
|
2
|
+
//# sourceMappingURL=util.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.js","names":["backoff","random","max","min","attempt","clientId","bytes","id","byte","resolveUrl","href","resolved","url","ws","sendUrl","next"],"sources":["../../../src/client/util.ts"],"sourcesContent":["/** The delay before reconnect attempt `attempt` (0-based): full jitter below an exponential cap. @internal */\nexport function backoff(attempt: number, min: number, max: number, random: () => number): number {\n return random() * Math.min(max, min * 2 ** Math.min(attempt, 30));\n}\n\n/** A random identifier of 12 base-36 characters, used to build idempotency keys. @internal */\nexport function clientId(): string {\n const bytes = new Uint8Array(8);\n globalThis.crypto.getRandomValues(bytes);\n let id = \"\";\n for (const byte of bytes) id += byte.toString(36).padStart(2, \"0\");\n return id.slice(0, 12);\n}\n\n/** Resolves a stream or POST path against the page; `ws` swaps `http(s):` for `ws(s):`. @internal */\nexport function resolveUrl(url: string, ws: boolean): URL {\n const href = globalThis.location?.href;\n const base = href !== undefined && URL.canParse(url, href) ? href : \"http://localhost/\";\n const resolved = new URL(url, base);\n if (ws) resolved.protocol = resolved.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n return resolved;\n}\n\n/** The default POST URL: the stream path plus `/send`, keeping the query. @internal */\nexport function sendUrl(url: URL): URL {\n const next = new URL(url);\n next.pathname = `${next.pathname.replace(/\\/$/, \"\")}/send`;\n return next;\n}\n"],"mappings":"AACA,SAAgBA,EAAQ,EAAiB,EAAa,EAAa,EAA8B,CAC/F,OAAOC,EAAO,EAAI,KAAK,IAAIC,EAAKC,EAAM,GAAK,KAAK,IAAIC,EAAS,EAAE,CAAC,CAClE,CAGA,SAAgBC,GAAmB,CACjC,IAAMC,kBAAQ,IAAI,WAAW,CAAC,EAC9B,WAAW,OAAO,gBAAgBA,CAAK,EACvC,IAAIC,EAAK,GACT,IAAK,IAAMC,KAAQF,EAAO,GAAME,EAAK,SAAS,EAAE,CAAC,CAAC,SAAS,EAAG,GAAG,EACjE,OAAOD,EAAG,MAAM,EAAG,EAAE,CACvB,CAGA,SAAgBE,EAAW,EAAa,EAAkB,CACxD,IAAMC,EAAO,WAAW,UAAU,KAE5BC,EAAW,IAAI,IAAIC,EADZF,IAAS,IAAA,IAAa,IAAI,SAASE,EAAKF,CAAI,EAAIA,EAAO,mBAClC,EAElC,OADIG,IAAI,EAAS,SAAWF,EAAS,WAAa,SAAW,OAAS,OAC/DA,CACT,CAGA,SAAgBG,EAAQ,EAAe,CACrC,IAAMC,EAAO,IAAI,IAAIH,CAAG,EAExB,MADA,GAAK,SAAW,GAAGG,EAAK,SAAS,QAAQ,MAAO,EAAE,EAAE,OAC7CA,CACT,C"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{decodeServerFrame as e}from"../protocol.js";function t(t,n,r){let i=new n(t);i.onmessage=t=>{if(typeof t.data!=`string`)return;r.rx();let n=e(t.data);n!==void 0&&r.frame(n)};let a=!1,o=e=>{a||(a=!0,r.closed({code:e}))};return i.onclose=e=>o(e.code),i.onerror=()=>{i.readyState!==1&&o(1006)},{send(e){return i.readyState===1&&(i.send(e),!0)},close(){i.onmessage=null,i.onclose=null,i.onerror=null,i.close(1e3)}}}export{t as openSocket};
|
|
2
|
+
//# sourceMappingURL=ws.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws.js","names":["openSocket","socket","Socket","url","event","events","frame","decodeServerFrame","ended","end","text"],"sources":["../../../src/client/ws.ts"],"sourcesContent":["import { decodeServerFrame } from \"../protocol\";\nimport type { Link, LinkEvents } from \"./sse\";\n\n/** Opens a WebSocket whose text messages are protocol frames. Throws for a URL the constructor refuses. @internal */\nexport function openSocket(url: string, Socket: typeof WebSocket, events: LinkEvents): Link {\n const socket = new Socket(url);\n socket.onmessage = (event: MessageEvent<unknown>) => {\n if (typeof event.data !== \"string\") return;\n events.rx();\n const frame = decodeServerFrame(event.data);\n if (frame !== undefined) events.frame(frame);\n };\n // A failed upgrade is `error` then `close` (code 1006) in browsers; Node 22's WebSocket fires only `error` and stays connecting, so\n // an error before the socket opened counts as the close, once.\n let ended = false;\n const end = (code: number): void => {\n if (ended) return;\n ended = true;\n events.closed({ code });\n };\n socket.onclose = (event: CloseEvent) => end(event.code);\n socket.onerror = () => {\n if (socket.readyState !== 1) end(1006);\n };\n return {\n send(text) {\n if (socket.readyState !== 1) return false;\n socket.send(text);\n return true;\n },\n close() {\n socket.onmessage = null;\n socket.onclose = null;\n socket.onerror = null;\n socket.close(1000);\n },\n };\n}\n"],"mappings":"A,mDAIA,SAAgBA,EAAW,EAAa,EAA0B,EAA0B,CAC1F,IAAMC,EAAS,IAAIC,EAAOC,CAAG,EAC7B,EAAO,UAAa,GAAiC,CACnD,GAAI,OAAOC,EAAM,MAAS,SAAU,OACpCC,EAAO,GAAG,EACV,IAAMC,EAAQC,EAAkBH,EAAM,IAAI,EACtCE,IAAU,IAAA,IAAWD,EAAO,MAAMC,CAAK,CAC7C,EAGA,IAAIE,EAAQ,GACNC,EAAO,GAAuB,CAC9BD,IACJ,EAAQ,GACRH,EAAO,OAAO,CAAE,MAAK,CAAC,EACxB,EAKA,MAJA,GAAO,QAAW,GAAsBI,EAAIL,EAAM,IAAI,EACtD,EAAO,YAAgB,CACjBH,EAAO,aAAe,GAAGQ,EAAI,IAAI,CACvC,EACO,CACL,KAAK,EAAM,CAGT,OAFIR,EAAO,aAAe,IAC1BA,EAAO,KAAKS,CAAI,EACT,GACT,EACA,OAAQ,CACN,EAAO,UAAY,KACnB,EAAO,QAAU,KACjB,EAAO,QAAU,KACjBT,EAAO,MAAM,GAAI,CACnB,CACF,CACF,C"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{RealtimeError as e}from"../errors.js";import{isWholeNumber as t,parseMember as n,parseStored as r}from"./wire.js";import{ChannelLink as i}from"./link.js";const a=`https://realtime.internal`,o=/* @__PURE__ */ new Set([`too_large`,`bad_channel`,`bad_frame`,`limit`,`conflict`]);function s(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function c(t,n){return new e(`unavailable`,t,n===void 0?void 0:{cause:n})}function l(e,n,r=!1){if(!t(e)||r&&e<1)throw TypeError(`${n} must be a whole number`);return e}function u(e,t,n){return e===void 0?t:l(e,n)}const d=e=>s(e)&&t(e.seq)&&e.seq>=1&&typeof e.duplicate==`boolean`?{seq:e.seq,duplicate:e.duplicate}:void 0,f=e=>{if(!s(e)||!Array.isArray(e.messages))return;let{head:n,oldest:i,more:a}=e;if(!t(n)||!t(i)||typeof a!=`boolean`)return;let o=[];for(let t of e.messages){let e=r(t);if(e===void 0)return;o.push(e)}return{messages:o,head:n,oldest:i,more:a}},p=e=>{if(!s(e)||!Array.isArray(e.members))return;let t=[];for(let r of e.members){let e=n(r);if(e===void 0)return;t.push(e)}return t},m=e=>s(e)&&typeof e.removed==`boolean`?e.removed:void 0,h=e=>{if(s(e)&&Array.isArray(e.missing))return e.missing.every(e=>typeof e==`string`)?e.missing:void 0},g=e=>s(e)?!0:void 0;function _(t){let{namespace:n}=t;if(typeof n?.idFromName!=`function`||typeof n.get!=`function`)throw TypeError(`cloudflareAdapter: namespace must be a Durable Object namespace binding`);let r=t.prefix;if(r!==void 0&&(r.length===0||r.length>256||r.includes(`
|
|
2
|
+
`)))throw TypeError(`cloudflareAdapter: prefix must be 1..256 characters without a newline`);let _=u(t.requestTimeoutMs,1e4,`requestTimeoutMs`),v=u(t.connectTimeoutMs,1e4,`connectTimeoutMs`),y=u(t.retries,2,`retries`),b=u(t.reconnect?.minDelayMs,250,`reconnect.minDelayMs`),x=u(t.reconnect?.maxDelayMs,1e4,`reconnect.maxDelayMs`),S=u(t.keepAliveMs,3e4,`keepAliveMs`),C=u(t.presenceGraceMs,2e3,`presenceGraceMs`),w=u(t.idleMs,0,`idleMs`),T=l(t.maxQueued??1e3,`maxQueued`,!0);if(_===0||v===0)throw TypeError(`cloudflareAdapter: timeouts must be above 0`);let E=t.onError??((e,t)=>console.error(`[realtime] ${t.where} ${t.channel}`,e)),D=!1,O=/* @__PURE__ */ new Map,k=/* @__PURE__ */ new Set,A=/* @__PURE__ */ new Set,j=/* @__PURE__ */ new Set;function M(e){let i=r===void 0?e:`${r}\n${e}`,a=n.idFromName(i);return t.locationHint===void 0?n.get(a):n.get(a,{locationHint:t.locationHint})}function N(e,t){let n=setTimeout(()=>{k.delete(r),t()},e);n.unref?.();let r=()=>{clearTimeout(n),k.delete(r)};return k.add(r),r}function P(e,t){let n=setInterval(t,e);n.unref?.();let r=()=>{clearInterval(n),k.delete(r)};return k.add(r),r}function F(e){return new Promise((t,n)=>{let r=e=>{i(),j.delete(r),n(e)},i=N(e,()=>{j.delete(r),t()});j.add(r)})}function I(){if(D)throw new e(`closed`,`the adapter is closed`)}async function L(t,n,r,i,l){for(let u=0;;u+=1){I();let d=new AbortController;A.add(d);let f=setTimeout(()=>d.abort(),_);try{let l=await M(t).fetch(`${a}/v1/${n}`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({channel:t,...r}),signal:d.signal}),u=await l.text(),f;try{f=JSON.parse(u)}catch{throw c(`the channel object answered ${l.status} with a body that is not JSON`)}if(l.status===200){let e=i(f);if(e===void 0)throw c(`the channel object answered an unexpected shape`);return e}let p=s(f)&&s(f.error)?f.error:void 0,m=p?.code;throw typeof m==`string`&&o.has(m)?new e(m,String(p?.message??m)):c(`the channel object answered ${l.status}`)}catch(t){if(D)throw new e(`closed`,`the adapter is closed`,{cause:t});if(t instanceof e&&t.code!==`unavailable`)throw t;if(l&&u<y){await F(Math.min(50*3**u,1e3));continue}throw t instanceof e?t:c(`the channel object is unreachable`,t)}finally{clearTimeout(f),A.delete(d)}}}let R={reconnectMinMs:b,reconnectMaxMs:Math.max(b,x),keepAliveMs:S,connectTimeoutMs:v,maxQueued:T,now:Date.now,random:Math.random,async open(e,t){let n=`c=${encodeURIComponent(e)}${t===void 0?``:`&after=${t}`}`,r=await M(e).fetch(`${a}/v1/ws?${n}`,{headers:{upgrade:`websocket`}}),i=r.webSocket;if(r.status!==101||i==null)throw c(`the channel object refused the subscribe socket (${r.status})`);return i},after:N,every:P,report:(e,t,n)=>E(e,{where:t,channel:n}),forget(e){O.get(e.channel)===e&&O.delete(e.channel)}};function z(e){let t=O.get(e);return t===void 0&&(t=new i(e,R),O.set(e,t)),t}async function B(t,n){I();let r=z(t),i=n(r);try{await r.whenReady(v)}catch(t){throw r.remove(i),D?new e(`closed`,`the adapter is closed`,{cause:t}):t instanceof e?t:c(`could not subscribe`,t)}return async()=>{r.remove(i)}}let V=e=>({event:e.event,data:e.data,...e.publisher===void 0?{}:{publisher:e.publisher},...e.user===void 0?{}:{user:e.user}});return{publish(e,t,n){return L(e,`publish`,{entry:V(t),options:{history:n.history,historyTtlMs:n.historyTtlMs,idempotencyTtlMs:n.idempotencyTtlMs,...n.key===void 0?{}:{key:n.key}},...w>0?{idleMs:w}:{}},d,n.key!==void 0)},subscribe(e,t){return B(e,e=>e.addMessages(t))},async history(e,t,n){let r=(t,n)=>L(e,`history`,{after:t,limit:n},f,!0),i=await r(t,n<=0?0:Math.min(n,512)),{oldest:a}=i,o=[...i.messages];for(;i.more&&o.length<n;){let e=o[o.length-1];if(e===void 0)break;i=await r(e.seq,Math.min(n-o.length,512)),o.push(...i.messages)}return{messages:o,head:i.head,oldest:a}},async presenceSet(e,t,n){await L(e,`presence-set`,{member:t,ttlMs:n,graceMs:C},g,!0)},presenceRemove(e,t){return L(e,`presence-remove`,{id:t},m,!1)},presenceList(e){return L(e,`presence-list`,{},p,!0)},async presenceTouch(e,t){let n=/* @__PURE__ */ new Map;for(let t of e){let e=n.get(t.channel);e===void 0?n.set(t.channel,[t.id]):e.push(t.id)}return(await Promise.all([...n].map(async([e,n])=>(await L(e,`presence-touch`,{ids:n,ttlMs:t},h,!0)).map(t=>({channel:e,id:t}))))).flat()},presenceReap(e){return L(e,`presence-reap`,{},p,!1)},presenceSubscribe(e,t){return B(e,e=>e.addPresence(t))},async close(){if(!D){D=!0;for(let e of A)e.abort();for(let t of[...j])t(new e(`closed`,`the adapter is closed`));for(let t of[...O.values()])t.dispose(new e(`closed`,`the adapter is closed`));O.clear();for(let e of[...k])e()}}}}export{_ as cloudflareAdapter};
|
|
3
|
+
//# sourceMappingURL=adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.js","names":["ORIGIN","PASS","isRecord","value","unavailable","RealtimeError","message","cause","count","isWholeNumber","positive","name","option","fallback","readPublish","body","\"seq\"","\"duplicate\"","readHistory","\"messages\"","head","oldest","more","messages","item","parseStored","readMembers","\"members\"","members","member","parseMember","readRemoved","\"removed\"","readMissing","\"missing\"","id","readEmpty","cloudflareAdapter","options","namespace","prefix","requestTimeoutMs","connectTimeoutMs","retries","minDelayMs","maxDelayMs","keepAliveMs","graceMs","idleMs","maxQueued","report","context","error","closed","links","cancels","inflight","sleepers","stubFor","channel","after","handle","cancel","task","ms","every","delay","stop","reject","resolve","assertOpen","call","attempt","controller","timer","response","op","text","parsed","result","read","refusal","\"error\"","code","\"code\"","\"message\"","safe","host","query","from","socket","link","linkFor","ChannelLink","join","sub","add","keepRetained","entry","rules","listener","want","page","limit","tail","byChannel","entries","ids"],"sources":["../../../src/cloudflare/adapter.ts"],"sourcesContent":["import type {\n Adapter,\n HistoryResult,\n PresenceRef,\n PublishOptions,\n PublishResult,\n Unsubscribe,\n} from \"../adapter\";\nimport { type ErrorCode, RealtimeError } from \"../errors\";\nimport type { Member, PresenceEvent, StoredEntry, StoredMessage } from \"../protocol\";\nimport { ChannelLink, type LinkHost } from \"./link\";\nimport type { ChannelNamespace, ChannelStub } from \"./types\";\nimport { isWholeNumber, parseMember, parseStored } from \"./wire\";\n\n/**\n * Options of {@link cloudflareAdapter}.\n * @since 1.0.0\n */\nexport interface CloudflareRealtimeOptions {\n /** The Durable Object namespace binding of {@link RealtimeChannelObject} (`env.CHANNELS`). */\n readonly namespace: ChannelNamespace;\n /**\n * Separates this adapter's channels from other adapters on the same namespace (the object's name is the prefix, a newline and\n * the channel). Default: none, the object is named by the channel alone.\n */\n readonly prefix?: string;\n /** A Cloudflare location hint for objects this adapter creates (for example `\"weur\"`). */\n readonly locationHint?: string;\n /** Milliseconds one request to an object may take. Default: 10,000. */\n readonly requestTimeoutMs?: number;\n /** Milliseconds `subscribe` waits for the subscribe socket before it rejects with `unavailable`. Default: 10,000. */\n readonly connectTimeoutMs?: number;\n /**\n * How many times a request that is safe to repeat (history, presence reads and updates, a publish with an idempotency key) is\n * repeated after a transport failure. Default: 2.\n */\n readonly retries?: number;\n /** Backoff of the subscribe socket: full jitter from `minDelayMs` up to `maxDelayMs`. Defaults: 250 and 10,000. */\n readonly reconnect?: { readonly minDelayMs?: number; readonly maxDelayMs?: number };\n /** Milliseconds between keep-alive pings on a subscribe socket; 0 turns them off. Default: 30,000. */\n readonly keepAliveMs?: number;\n /**\n * Milliseconds after a presence member's expiry that the object's alarm waits before it announces the leave itself. The hub's\n * own `presenceReap` normally runs inside that window. Default: 2,000.\n */\n readonly presenceGraceMs?: number;\n /**\n * Forget a channel (numbering, history, keys) after this many milliseconds with no message, no presence and no subscriber.\n * A client that resumes later sees `reset`. Default: 0, never.\n */\n readonly idleMs?: number;\n /** The most events queued for one listener before newer ones are dropped (the hub then recovers from history). Default: 1,000. */\n readonly maxQueued?: number;\n /** Receives failures nobody else sees: a listener that threw. Default: `console.error`. */\n readonly onError?: (\n error: unknown,\n context: { readonly where: string; readonly channel: string },\n ) => void;\n}\n\nconst ORIGIN = \"https://realtime.internal\";\nconst PAGE = 512;\n/** Codes the object answers that are the caller's to see; everything else is `unavailable`. */\nconst PASS: ReadonlySet<string> = new Set<ErrorCode>([\n \"too_large\",\n \"bad_channel\",\n \"bad_frame\",\n \"limit\",\n \"conflict\",\n]);\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction unavailable(message: string, cause?: unknown): RealtimeError {\n return new RealtimeError(\"unavailable\", message, cause === undefined ? undefined : { cause });\n}\n\nfunction count(value: unknown, name: string, positive = false): number {\n if (!isWholeNumber(value) || (positive && value < 1))\n throw new TypeError(`${name} must be a whole number`);\n return value;\n}\n\nfunction option(value: number | undefined, fallback: number, name: string): number {\n return value === undefined ? fallback : count(value, name);\n}\n\n/** Reads the object's reply. `parse` returns `undefined` for a shape it does not accept. */\ntype Reader<T> = (body: unknown) => T | undefined;\n\nconst readPublish: Reader<PublishResult> = (body) =>\n isRecord(body) &&\n isWholeNumber(body[\"seq\"]) &&\n body[\"seq\"] >= 1 &&\n typeof body[\"duplicate\"] === \"boolean\"\n ? { seq: body[\"seq\"], duplicate: body[\"duplicate\"] }\n : undefined;\n\ninterface Page extends HistoryResult {\n readonly more: boolean;\n}\n\nconst readHistory: Reader<Page> = (body) => {\n if (!isRecord(body) || !Array.isArray(body[\"messages\"])) return undefined;\n const { head, oldest, more } = body;\n if (!isWholeNumber(head) || !isWholeNumber(oldest) || typeof more !== \"boolean\") return undefined;\n const messages: StoredMessage[] = [];\n for (const item of body[\"messages\"]) {\n const message = parseStored(item);\n if (message === undefined) return undefined;\n messages.push(message);\n }\n return { messages, head, oldest, more };\n};\n\nconst readMembers: Reader<Member[]> = (body) => {\n if (!isRecord(body) || !Array.isArray(body[\"members\"])) return undefined;\n const members: Member[] = [];\n for (const item of body[\"members\"]) {\n const member = parseMember(item);\n if (member === undefined) return undefined;\n members.push(member);\n }\n return members;\n};\n\nconst readRemoved: Reader<boolean> = (body) =>\n isRecord(body) && typeof body[\"removed\"] === \"boolean\" ? body[\"removed\"] : undefined;\n\nconst readMissing: Reader<string[]> = (body) => {\n if (!isRecord(body) || !Array.isArray(body[\"missing\"])) return undefined;\n return body[\"missing\"].every((id) => typeof id === \"string\")\n ? (body[\"missing\"] as string[])\n : undefined;\n};\n\nconst readEmpty: Reader<true> = (body) => (isRecord(body) ? true : undefined);\n\n/**\n * The adapter for Cloudflare Workers: each channel is one Durable Object ({@link RealtimeChannelObject}) that numbers,\n * retains and fans out its messages, so a channel is ordered and gapless wherever the Workers that use it run.\n *\n * `publish`, `history` and the presence calls are one request to the channel's object each. `subscribe` and\n * `presenceSubscribe` share one WebSocket per channel from this adapter to the object, which the adapter reopens with backoff\n * and resumes with the last seq it saw. Call `close()` when the adapter is no longer needed; it closes every socket and\n * cancels every timer.\n *\n * Within one Worker request, create the adapter where the hub lives. A Worker may touch a socket only in the request that\n * created it, so one hub that serves many client connections belongs inside a Durable Object, which keeps one context for all\n * of them; see the package's Cloudflare guide.\n *\n * @param options The namespace binding of the channel object and the tuning.\n * @returns The adapter.\n * @throws {TypeError} When an option is not valid.\n * @example\n * import { createHub } from \"@shadow-js/realtime/server\";\n * import { cloudflareAdapter } from \"@shadow-js/realtime/cloudflare\";\n *\n * declare const env: { CHANNELS: Parameters<typeof cloudflareAdapter>[0][\"namespace\"] };\n * const adapter = cloudflareAdapter({ namespace: env.CHANNELS });\n * const hub = createHub({ adapter, channels: { \"chat:{room}\": { subscribe: true, publish: true } } });\n * void hub.close();\n * @since 1.0.0\n */\nexport function cloudflareAdapter(options: CloudflareRealtimeOptions): Adapter {\n const { namespace } = options;\n if (typeof namespace?.idFromName !== \"function\" || typeof namespace.get !== \"function\")\n throw new TypeError(\"cloudflareAdapter: namespace must be a Durable Object namespace binding\");\n const prefix = options.prefix;\n if (prefix !== undefined && (prefix.length === 0 || prefix.length > 256 || prefix.includes(\"\\n\")))\n throw new TypeError(\"cloudflareAdapter: prefix must be 1..256 characters without a newline\");\n const requestTimeoutMs = option(options.requestTimeoutMs, 10_000, \"requestTimeoutMs\");\n const connectTimeoutMs = option(options.connectTimeoutMs, 10_000, \"connectTimeoutMs\");\n const retries = option(options.retries, 2, \"retries\");\n const minDelayMs = option(options.reconnect?.minDelayMs, 250, \"reconnect.minDelayMs\");\n const maxDelayMs = option(options.reconnect?.maxDelayMs, 10_000, \"reconnect.maxDelayMs\");\n const keepAliveMs = option(options.keepAliveMs, 30_000, \"keepAliveMs\");\n const graceMs = option(options.presenceGraceMs, 2000, \"presenceGraceMs\");\n const idleMs = option(options.idleMs, 0, \"idleMs\");\n const maxQueued = count(options.maxQueued ?? 1000, \"maxQueued\", true);\n if (requestTimeoutMs === 0 || connectTimeoutMs === 0)\n throw new TypeError(\"cloudflareAdapter: timeouts must be above 0\");\n const report =\n options.onError ??\n ((error: unknown, context: { readonly where: string; readonly channel: string }): void =>\n console.error(`[realtime] ${context.where} ${context.channel}`, error));\n\n let closed = false;\n const links = new Map<string, ChannelLink>();\n const cancels = new Set<() => void>();\n const inflight = new Set<AbortController>();\n const sleepers = new Set<(error: unknown) => void>();\n\n function stubFor(channel: string): ChannelStub {\n const name = prefix === undefined ? channel : `${prefix}\\n${channel}`;\n const id = namespace.idFromName(name);\n return options.locationHint === undefined\n ? namespace.get(id)\n : namespace.get(id, { locationHint: options.locationHint });\n }\n\n function after(ms: number, task: () => void): () => void {\n const handle = setTimeout(() => {\n cancels.delete(cancel);\n task();\n }, ms);\n (handle as { unref?: () => void }).unref?.();\n const cancel = (): void => {\n clearTimeout(handle);\n cancels.delete(cancel);\n };\n cancels.add(cancel);\n return cancel;\n }\n\n function every(ms: number, task: () => void): () => void {\n const handle = setInterval(task, ms);\n (handle as { unref?: () => void }).unref?.();\n const cancel = (): void => {\n clearInterval(handle);\n cancels.delete(cancel);\n };\n cancels.add(cancel);\n return cancel;\n }\n\n function delay(ms: number): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const stop = (error: unknown): void => {\n cancel();\n sleepers.delete(stop);\n reject(error);\n };\n const cancel = after(ms, () => {\n sleepers.delete(stop);\n resolve();\n });\n sleepers.add(stop);\n });\n }\n\n function assertOpen(): void {\n if (closed) throw new RealtimeError(\"closed\", \"the adapter is closed\");\n }\n\n /**\n * One request to the channel's object. A transport failure, a 5xx or an unreadable reply is `unavailable`; with `safe` it is\n * repeated first, with a short growing pause.\n */\n async function call<T>(\n channel: string,\n op: string,\n body: Readonly<Record<string, unknown>>,\n read: Reader<T>,\n safe: boolean,\n ): Promise<T> {\n for (let attempt = 0; ; attempt += 1) {\n assertOpen();\n const controller = new AbortController();\n inflight.add(controller);\n const timer = setTimeout(() => controller.abort(), requestTimeoutMs);\n try {\n const response = await stubFor(channel).fetch(`${ORIGIN}/v1/${op}`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ channel, ...body }),\n signal: controller.signal,\n });\n const text = await response.text();\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n throw unavailable(\n `the channel object answered ${response.status} with a body that is not JSON`,\n );\n }\n if (response.status === 200) {\n const result = read(parsed);\n if (result === undefined)\n throw unavailable(\"the channel object answered an unexpected shape\");\n return result;\n }\n const refusal = isRecord(parsed) && isRecord(parsed[\"error\"]) ? parsed[\"error\"] : undefined;\n const code = refusal?.[\"code\"];\n if (typeof code === \"string\" && PASS.has(code))\n throw new RealtimeError(code as ErrorCode, String(refusal?.[\"message\"] ?? code));\n throw unavailable(`the channel object answered ${response.status}`);\n } catch (error) {\n if (closed) throw new RealtimeError(\"closed\", \"the adapter is closed\", { cause: error });\n if (error instanceof RealtimeError && error.code !== \"unavailable\") throw error;\n if (safe && attempt < retries) {\n await delay(Math.min(50 * 3 ** attempt, 1000));\n continue;\n }\n throw error instanceof RealtimeError\n ? error\n : unavailable(\"the channel object is unreachable\", error);\n } finally {\n clearTimeout(timer);\n inflight.delete(controller);\n }\n }\n }\n\n const host: LinkHost = {\n reconnectMinMs: minDelayMs,\n reconnectMaxMs: Math.max(minDelayMs, maxDelayMs),\n keepAliveMs,\n connectTimeoutMs,\n maxQueued,\n now: Date.now,\n random: Math.random,\n async open(channel, from) {\n const query = `c=${encodeURIComponent(channel)}${from === undefined ? \"\" : `&after=${from}`}`;\n const response = await stubFor(channel).fetch(`${ORIGIN}/v1/ws?${query}`, {\n headers: { upgrade: \"websocket\" },\n });\n const socket = response.webSocket;\n if (response.status !== 101 || socket === undefined || socket === null)\n throw unavailable(`the channel object refused the subscribe socket (${response.status})`);\n return socket;\n },\n after,\n every,\n report: (error, where, channel) => report(error, { where, channel }),\n forget(link) {\n if (links.get(link.channel) === link) links.delete(link.channel);\n },\n };\n\n function linkFor(channel: string): ChannelLink {\n let link = links.get(channel);\n if (link === undefined) {\n link = new ChannelLink(channel, host);\n links.set(channel, link);\n }\n return link;\n }\n\n async function join(\n channel: string,\n add: (link: ChannelLink) => ReturnType<ChannelLink[\"addMessages\"]>,\n ): Promise<Unsubscribe> {\n assertOpen();\n const link = linkFor(channel);\n const sub = add(link);\n try {\n await link.whenReady(connectTimeoutMs);\n } catch (error) {\n link.remove(sub);\n if (closed) throw new RealtimeError(\"closed\", \"the adapter is closed\", { cause: error });\n throw error instanceof RealtimeError ? error : unavailable(\"could not subscribe\", error);\n }\n return async () => {\n link.remove(sub);\n };\n }\n\n const keepRetained = (entry: StoredEntry): Record<string, unknown> => ({\n event: entry.event,\n data: entry.data,\n ...(entry.publisher === undefined ? {} : { publisher: entry.publisher }),\n ...(entry.user === undefined ? {} : { user: entry.user }),\n });\n\n return {\n publish(channel: string, entry: StoredEntry, rules: PublishOptions): Promise<PublishResult> {\n return call(\n channel,\n \"publish\",\n {\n entry: keepRetained(entry),\n options: {\n history: rules.history,\n historyTtlMs: rules.historyTtlMs,\n idempotencyTtlMs: rules.idempotencyTtlMs,\n ...(rules.key === undefined ? {} : { key: rules.key }),\n },\n ...(idleMs > 0 ? { idleMs } : {}),\n },\n readPublish,\n rules.key !== undefined,\n );\n },\n\n subscribe(channel: string, listener: (message: StoredMessage) => void): Promise<Unsubscribe> {\n return join(channel, (link) => link.addMessages(listener));\n },\n\n async history(channel: string, after: number, limit: number): Promise<HistoryResult> {\n const read = (from: number, want: number): Promise<Page> =>\n call(channel, \"history\", { after: from, limit: want }, readHistory, true);\n let page = await read(after, limit <= 0 ? 0 : Math.min(limit, PAGE));\n const { oldest } = page;\n const messages: StoredMessage[] = [...page.messages];\n // The object answers at most a page at a time; keep reading until `limit` or the end of the retained messages.\n while (page.more && messages.length < limit) {\n const tail = messages[messages.length - 1];\n if (tail === undefined) break;\n page = await read(tail.seq, Math.min(limit - messages.length, PAGE));\n messages.push(...page.messages);\n }\n return { messages, head: page.head, oldest };\n },\n\n async presenceSet(channel: string, member: Member, ttlMs: number): Promise<void> {\n await call(channel, \"presence-set\", { member, ttlMs, graceMs }, readEmpty, true);\n },\n\n presenceRemove(channel: string, id: string): Promise<boolean> {\n return call(channel, \"presence-remove\", { id }, readRemoved, false);\n },\n\n presenceList(channel: string): Promise<readonly Member[]> {\n return call(channel, \"presence-list\", {}, readMembers, true);\n },\n\n async presenceTouch(\n entries: readonly PresenceRef[],\n ttlMs: number,\n ): Promise<readonly PresenceRef[]> {\n const byChannel = new Map<string, string[]>();\n for (const entry of entries) {\n const ids = byChannel.get(entry.channel);\n if (ids === undefined) byChannel.set(entry.channel, [entry.id]);\n else ids.push(entry.id);\n }\n const results = await Promise.all(\n [...byChannel].map(async ([channel, ids]) => {\n const missing = await call(channel, \"presence-touch\", { ids, ttlMs }, readMissing, true);\n return missing.map((id): PresenceRef => ({ channel, id }));\n }),\n );\n return results.flat();\n },\n\n presenceReap(channel: string): Promise<readonly Member[]> {\n return call(channel, \"presence-reap\", {}, readMembers, false);\n },\n\n presenceSubscribe(\n channel: string,\n listener: (event: PresenceEvent) => void,\n ): Promise<Unsubscribe> {\n return join(channel, (link) => link.addPresence(listener));\n },\n\n async close(): Promise<void> {\n if (closed) return;\n closed = true;\n for (const controller of inflight) controller.abort();\n for (const stop of [...sleepers]) stop(new RealtimeError(\"closed\", \"the adapter is closed\"));\n for (const link of [...links.values()])\n link.dispose(new RealtimeError(\"closed\", \"the adapter is closed\"));\n links.clear();\n for (const cancel of [...cancels]) cancel();\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{RealtimeError as e}from"../errors.js";import{historyKey as t,seqOfKey as n}from"./wire.js";const r=`meta`,i=1024,a=5e3;function o(e){return String(e).padStart(16,`0`)}function s(e){let t=[];for(let n=0;n<e.length;n+=128)t.push(e.slice(n,n+128));return t}function c(e){return e.data.length+e.event.length+96}var l=class{#e;#t;#n;#r=/* @__PURE__ */ new Set;#i=Promise.resolve();#a;#o;#s;constructor(e){this.#e=e.storage,this.#t=e.now??Date.now,this.#n=e.maxMembers??1e4}adopt(e){for(let t of e)this.#r.add(t)}detach(e){this.#r.delete(e)}get sockets(){return this.#r.size}#c(t){let n=this.#i.then(t).catch(t=>{throw t instanceof e||(this.#a=void 0,this.#o=void 0,this.#s=void 0),t});return this.#i=n.then(()=>void 0,()=>void 0),n}async#l(t){let n=await this.#u();if(n!==void 0){if(n.channel!==t)throw new e(`conflict`,`the object belongs to another channel`);return n}let r={v:1,channel:t,head:0,oldest:0,oldestAt:0,keep:0,ttlMs:0,keysDue:0,presenceDue:0,graceMs:0,idleMs:0,touched:this.#t()};return this.#a=r,r}async#u(){return this.#a===void 0&&(this.#a=await this.#e.get(r)),this.#a}async#d(){if(this.#o===void 0){let e=await this.#e.list({prefix:`p:`}),t=/* @__PURE__ */ new Map;for(let[n,r]of e)t.set(n.slice(2),{member:r.member,expires:r.expires,stored:r.expires});this.#o=t}return this.#o}async#f(e,t,n){await this.#e.transaction(async i=>{await i.put({...t,[r]:e});for(let e of s(n))await i.delete(e)}),this.#a=e}#p(e,t){try{return e.send(t),!0}catch{this.#r.delete(e);try{e.close(1011,`write failed`)}catch{}return!1}}#m(e){if(this.#r.size===0)return;let t=JSON.stringify(e);for(let e of[...this.#r])this.#p(e,t)}#h(e,t){this.#m({t:`pres`,op:e,member:t})}publish(e,n,r,i=0){return this.#c(async()=>{let s=await this.#l(e),c=this.#t();if(r.key!==void 0){let e=await this.#e.get(`k:`+r.key);if(e!==void 0&&e.expires>c)return{seq:e.seq,duplicate:!0}}let l=s.head+1,u={seq:l,event:n.event,data:n.data,at:c,...n.publisher===void 0?{}:{publisher:n.publisher},...n.user===void 0?{}:{user:n.user}},d=await this.#g(s,c,r.history,r.historyTtlMs,1),f={...s,head:l,keep:r.history,ttlMs:r.historyTtlMs,idleMs:i,touched:c,oldest:d.oldest,oldestAt:d.oldestAt},p={};if(r.history>0&&(p[t(l)]=u,f.oldest===0&&(f.oldest=l,f.oldestAt=c)),r.key!==void 0){let e=c+r.idempotencyTtlMs;p[`k:`+r.key]={seq:l,expires:e},p[`x:${o(e)}:${r.key}`]=1;let t=e+a;f.keysDue=f.keysDue===0?t:Math.min(f.keysDue,t)}return await this.#f(f,p,d.deletes),this.#m({t:`msg`,m:u}),await this.#S(f),{seq:l,duplicate:!1}})}async#g(e,r,a,o,s){let c=[],l=e.oldest,u=e.oldestAt;if(l===0)return{deletes:c,oldest:0,oldestAt:0};let d=e.head+1+s-a;if(l<d){let n=Math.min(d,e.head+1,l+i);for(let e=l;e<n;e+=1)c.push(t(e));if(l=n,l>e.head)return{deletes:c,oldest:0,oldestAt:0};let r=await this.#_(l);if(r===void 0)return{deletes:c,oldest:0,oldestAt:0};l=r.seq,u=r.at}let f=r-o;for(;u<f&&c.length<i;){let e=await this.#e.list({prefix:`h:`,start:t(l),limit:128}),r;for(let n of e.values()){if(n.at>=f||c.length>=i){r=n;break}c.push(t(n.seq))}if(r!==void 0){l=r.seq,u=r.at;break}if(e.size<128)return{deletes:c,oldest:0,oldestAt:0};let a=c.length===0?void 0:c[c.length-1];if(a===void 0)break;l=n(a)+1;let o=await this.#_(l);if(o===void 0)return{deletes:c,oldest:0,oldestAt:0};l=o.seq,u=o.at}return{deletes:c,oldest:l,oldestAt:u}}async#_(e){let n=await this.#e.list({prefix:`h:`,start:t(e),limit:1});for(let e of n.values())return e}#v(e,t){return e.oldest===0?!1:e.head-e.oldest+1>e.keep||e.oldestAt+e.ttlMs<t}async#y(e,t){if(!this.#v(e,t))return!1;let n=await this.#g(e,t,e.keep,e.ttlMs,0);return n.deletes.length===0&&n.oldest===e.oldest?!1:(await this.#f({...e,oldest:n.oldest,oldestAt:n.oldestAt},{},n.deletes),!0)}history(e,n,r){return this.#c(async()=>{let i=await this.#l(e);if(await this.#y(i,this.#t())&&(i=this.#a??i),r<=0||n>=i.head||i.oldest===0)return{messages:[],head:i.head,oldest:i.oldest,more:!1};let a=Math.min(r,512),o=await this.#e.list({prefix:`h:`,start:t(Math.max(n+1,i.oldest)),limit:a}),s=[],l=0;for(let e of o.values()){if(s.length>0&&l+c(e)>4194304)break;s.push(e),l+=c(e)}let u=s[s.length-1];return{messages:s,head:i.head,oldest:i.oldest,more:u!==void 0&&u.seq<i.head}})}attach(e,n,r){return this.#c(async()=>{let i=await this.#l(n),a=this.#t();await this.#y(i,a)&&(i=this.#a??i);let o=await this.#d(),s=[];for(let e of o.values())e.expires>a&&s.push(e.member);let l=[JSON.stringify({t:`ready`,head:i.head,oldest:i.oldest}),JSON.stringify({t:`snap`,members:s})],u=!1;if(r!==void 0&&r<i.head&&i.oldest>0){let e=Math.max(r,i.oldest-1),n=0,a=0;replay:for(;e<i.head;){let r=await this.#e.list({prefix:`h:`,start:t(e+1),limit:128});if(r.size===0)break;for(let t of r.values())if(l.push(JSON.stringify({t:`msg`,m:t})),e=t.seq,n+=1,a+=c(t),n>=1e4||a>=8388608){u=e<i.head;break replay}if(r.size<128)break}}u&&l.push(JSON.stringify({t:`more`}));for(let t of l)try{e.send(t)}catch{return}this.#r.add(e)})}presenceSet(t,n,r,i){return this.#c(async()=>{let a=await this.#l(t),o=await this.#d(),s=o.get(n.id);if(s!==void 0&&s.member.v>n.v)return;if(s===void 0&&o.size>=this.#n)throw new e(`limit`,`the channel has too many presence members`);let c=this.#t(),l=c+r,u=s===void 0||s.member.v<n.v,d=l+i,f={...a,graceMs:i,touched:c,presenceDue:a.presenceDue===0?d:Math.min(a.presenceDue,d)},p=u||s===void 0||l-s.stored>=r/2;(p||f.presenceDue!==a.presenceDue||f.graceMs!==a.graceMs)&&await this.#f(f,p?{[`p:`+n.id]:{member:n,expires:l}}:{},[]),o.set(n.id,{member:n,expires:l,stored:p?l:s?.stored??l}),s===void 0?this.#h(`join`,n):s.member.v<n.v&&this.#h(`set`,n),await this.#S(f)})}presenceRemove(e,t){return this.#c(async()=>{let n=await this.#l(e),r=await this.#d(),i=r.get(t);if(i===void 0)return!1;let a=r.size===1?{...n,presenceDue:0}:n;return await this.#f(a,{},[`p:`+t]),r.delete(t),this.#h(`leave`,i.member),await this.#S(a),!0})}presenceList(e){return this.#c(async()=>{await this.#l(e);let t=await this.#d(),n=this.#t(),r=[];for(let e of t.values())e.expires>n&&r.push(e.member);return r})}presenceTouch(e,t,n){return this.#c(async()=>{let r=await this.#l(e),i=await this.#d(),a=this.#t()+n,o=[],c={},l=[],u=0;for(let e of t){let t=i.get(e);t===void 0?o.push(e):(t.expires=a,u+=1,t.expires-t.stored>=n/2&&(c[`p:`+e]={member:t.member,expires:t.expires},l.push(t)))}let d=Object.keys(c);for(let e of s(d)){let t={};for(let n of e)t[n]=c[n];await this.#e.put(t)}for(let e of l)e.stored=e.expires;let f=a+r.graceMs;if(u>0&&(r.presenceDue===0||f<r.presenceDue)){let e={...r,presenceDue:f};await this.#f(e,{},[]),await this.#S(e)}return o})}presenceReap(e){return this.#c(async()=>{let t=await this.#l(e);return this.#b(t,this.#t())})}async#b(e,t){let n=await this.#d(),r=[];for(let e of n.values())e.expires<=t&&r.push(e);if(r.length===0)return[];let i=0;for(let r of n.values())if(r.expires>t){let t=r.expires+e.graceMs;i=i===0?t:Math.min(i,t)}let a={...e,presenceDue:i};await this.#f(a,{},r.map(e=>`p:`+e.member.id));for(let e of r)n.delete(e.member.id),this.#h(`leave`,e.member);return r.map(e=>e.member)}#x(e,t){let n=0,r=e=>{e>0&&(n===0||e<n)&&(n=e)};if(r(e.presenceDue),r(e.keysDue),e.oldest>0&&r(e.head-e.oldest+1>e.keep?t:e.oldestAt+e.ttlMs+1e3),e.idleMs>0){let n=e.touched+e.idleMs;r(this.#r.size>0&&n<=t?t+e.idleMs:n)}return n===0?void 0:Math.max(n,t)}async#S(e){let t=this.#t(),n=this.#x(e,t);this.#s===void 0&&(this.#s=await this.#e.getAlarm()),n===void 0?this.#s!==null&&(await this.#e.deleteAlarm(),this.#s=null):(this.#s===null||this.#s>n)&&(await this.#e.setAlarm(n),this.#s=n)}async#C(e,t){for(let e=0;e<32;e+=1){let e=await this.#e.list({prefix:`x:`,end:`x:`+o(t+1),limit:128});if(e.size===0)break;let n=[...e.keys()],r=n.map(e=>e.slice(19)),i=await this.#e.get(r.map(e=>`k:`+e)),a=[...n];for(let e of r){let n=i.get(`k:`+e);(n===void 0||n.expires<=t)&&a.push(`k:`+e)}if(await this.#e.transaction(async e=>{for(let t of s(a))await e.delete(t)}),e.size<128)break}let n=await this.#e.list({prefix:`x:`,limit:1}),r=0;for(let e of n.keys())r=Number(e.slice(2,18))+a;return{...e,keysDue:r}}alarm(){return this.#c(async()=>{this.#s=null;let e=await this.#u();if(e===void 0)return;let t=this.#t();if(e.presenceDue>0&&e.presenceDue<=t){await this.#b(e,t),e=this.#a??e;let n=0;for(let t of(await this.#d()).values()){let r=t.expires+e.graceMs;n=n===0?r:Math.min(n,r)}n!==e.presenceDue&&(e={...e,presenceDue:n},await this.#f(e,{},[]))}e.keysDue>0&&e.keysDue<=t&&(e=await this.#C(e,t),await this.#f(e,{},[])),await this.#y(e,t)&&(e=this.#a??e),e.idleMs>0&&e.touched+e.idleMs<=t&&e.oldest===0&&e.presenceDue===0&&e.keysDue===0&&this.#r.size===0?(await this.#e.deleteAll(),this.#a=void 0,this.#o=void 0,this.#s=null):await this.#S(e)})}};export{l as ChannelCore};
|
|
2
|
+
//# sourceMappingURL=core.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.js","names":["META","TRIM_MAX","SWEEP_SLACK_MS","pad","value","chunks","out","at","items","sizeOf","message","ChannelCore","#storage","options","#now","#maxMembers","socket","sockets","#sockets","result","#tail","task","error","RealtimeError","#meta","#members","#alarmAt","#metaFor","meta","#storedMeta","channel","fresh","#loadMembers","rows","members","key","record","#commit","transaction","puts","batch","deletes","text","frame","#send","#broadcast","#run","now","known","seq","entry","plan","#planTrim","next","historyKey","expires","due","#schedule","oldest","oldestAt","target","extra","keep","stop","first","#firstFrom","cutoff","ttlMs","page","survivor","last","seqOfKey","#retain","#retentionDue","limit","after","cap","messages","bytes","live","state","frames","truncated","cursor","count","member","changed","graceMs","persist","#emit","id","missing","written","touched","ids","keys","part","#reap","gone","consider","idleAt","#nextDue","#sweepKeys","pass","indexKeys","names","records","name","keysDue"],"sources":["../../../src/cloudflare/core.ts"],"sourcesContent":["/**\n * The logic of one channel, free of the Workers runtime: it takes the storage, the clock and the sockets as plain objects. The\n * Durable Object class wires it to the platform; the unit tests wire it to an in-memory storage. Everything that touches state\n * runs one at a time (a queue in front of every operation), so numbering is gapless by construction and a presence member\n * leaves exactly once, whether the object is awake, evicted and restarted, or woken by its alarm.\n *\n * Storage layout (keys sort as strings, numbers are zero padded):\n * - `meta`: head seq, oldest retained seq and its time, the retention of the last publish, what is due and when;\n * - `h:<seq>`: one retained message per key (a value stays far below the platform's limit);\n * - `p:<id>`: a presence member and its expiry;\n * - `k:<key>`: an idempotency key and the seq it produced; `x:<expires>:<key>`: the expiry index used to sweep keys.\n * @packageDocumentation\n */\nimport type { PublishOptions, PublishResult } from \"../adapter\";\nimport { RealtimeError } from \"../errors\";\nimport type { Member, PresenceEvent, StoredEntry, StoredMessage } from \"../protocol\";\nimport type { ChannelSocket, ChannelStorage } from \"./types\";\nimport { type HistoryPage, historyKey, type LinkFrame, MAX_MEMBERS, seqOfKey } from \"./wire\";\n\nconst META = \"meta\";\nconst MESSAGES = \"h:\";\nconst MEMBERS = \"p:\";\nconst KEYS = \"k:\";\nconst KEY_INDEX = \"x:\";\n/** `x:` + 16 digits + `:`; what follows is the idempotency key. */\nconst KEY_INDEX_PREFIX = 19;\n/** The platform accepts at most 128 keys in one `delete`, `put` or `get`. */\nconst BATCH = 128;\n/** The most messages one operation deletes; a larger backlog is finished by the next operation or alarm. */\nconst TRIM_MAX = 1024;\n/** Waiting this long after the first idempotency key expires lets one alarm sweep many. */\nconst SWEEP_SLACK_MS = 5000;\n/** The same for retained messages. */\nconst HISTORY_SLACK_MS = 1000;\nconst PAGE_MESSAGES = 512;\nconst PAGE_BYTES = 4 * 1024 * 1024;\nconst REPLAY_MESSAGES = 10_000;\nconst REPLAY_BYTES = 8 * 1024 * 1024;\n\ninterface Meta {\n v: 1;\n channel: string;\n /** The highest seq assigned. */\n head: number;\n /** The oldest retained seq, 0 when nothing is retained. */\n oldest: number;\n /** When the oldest retained message was accepted. */\n oldestAt: number;\n /** The `history` count of the last publish. */\n keep: number;\n /** The `historyTtlMs` of the last publish. */\n ttlMs: number;\n /** When the earliest idempotency key can be swept, 0 for none. */\n keysDue: number;\n /** When the earliest presence member can be reaped by the alarm, 0 for none. */\n presenceDue: number;\n /** How long after its expiry the alarm reaps a member; the hub's own `presenceReap` runs inside that window. */\n graceMs: number;\n /** Forget the channel after this long without activity, 0 to keep it for good. */\n idleMs: number;\n /** The time of the last publish or presence change. */\n touched: number;\n}\n\ninterface KeyRecord {\n readonly seq: number;\n readonly expires: number;\n}\n\ninterface MemberRecord {\n readonly member: Member;\n expires: number;\n}\n\n/** A member in memory: `stored` is the expiry the storage holds, which lags behind `expires` to save writes. */\ninterface MemberState {\n member: Member;\n expires: number;\n stored: number;\n}\n\n/** What {@link ChannelCore} needs from its surroundings. @internal */\nexport interface CoreOptions {\n readonly storage: ChannelStorage;\n /** The clock in epoch milliseconds. Default: `Date.now`. */\n readonly now?: () => number;\n /** The most presence members one channel keeps. Default: {@link MAX_MEMBERS}. */\n readonly maxMembers?: number;\n}\n\nfunction pad(value: number): string {\n return String(value).padStart(16, \"0\");\n}\n\nfunction chunks<T>(items: readonly T[]): T[][] {\n const out: T[][] = [];\n for (let at = 0; at < items.length; at += BATCH) out.push(items.slice(at, at + BATCH));\n return out;\n}\n\nfunction sizeOf(message: StoredMessage): number {\n return message.data.length + message.event.length + 96;\n}\n\n/**\n * The single writer of one channel.\n * @internal\n */\nexport class ChannelCore {\n readonly #storage: ChannelStorage;\n readonly #now: () => number;\n readonly #maxMembers: number;\n readonly #sockets = new Set<ChannelSocket>();\n #tail: Promise<void> = Promise.resolve();\n #meta: Meta | undefined;\n #members: Map<string, MemberState> | undefined;\n /** The alarm time the storage holds: `undefined` unknown, `null` none. */\n #alarmAt: number | null | undefined;\n\n constructor(options: CoreOptions) {\n this.#storage = options.storage;\n this.#now = options.now ?? Date.now;\n this.#maxMembers = options.maxMembers ?? MAX_MEMBERS;\n }\n\n /** Registers sockets that were open before this instance was created (the runtime kept them while the object slept). */\n adopt(sockets: Iterable<ChannelSocket>): void {\n for (const socket of sockets) this.#sockets.add(socket);\n }\n\n /** Forgets a socket that closed. */\n detach(socket: ChannelSocket): void {\n this.#sockets.delete(socket);\n }\n\n /** The number of sockets attached. */\n get sockets(): number {\n return this.#sockets.size;\n }\n\n // --- the queue -----------------------------------------------------------------------------------------------------\n\n /** Runs `task` after every earlier task finished. A failure drops the caches, so the next task reads the storage afresh. */\n #run<T>(task: () => Promise<T>): Promise<T> {\n const result = this.#tail.then(task).catch((error: unknown) => {\n if (!(error instanceof RealtimeError)) {\n this.#meta = undefined;\n this.#members = undefined;\n this.#alarmAt = undefined;\n }\n throw error;\n });\n this.#tail = result.then(\n () => undefined,\n () => undefined,\n );\n return result;\n }\n\n // --- state ---------------------------------------------------------------------------------------------------------\n\n async #metaFor(channel: string): Promise<Meta> {\n const meta = await this.#storedMeta();\n if (meta !== undefined) {\n if (meta.channel !== channel)\n throw new RealtimeError(\"conflict\", \"the object belongs to another channel\");\n return meta;\n }\n const fresh: Meta = {\n v: 1,\n channel,\n head: 0,\n oldest: 0,\n oldestAt: 0,\n keep: 0,\n ttlMs: 0,\n keysDue: 0,\n presenceDue: 0,\n graceMs: 0,\n idleMs: 0,\n touched: this.#now(),\n };\n this.#meta = fresh;\n return fresh;\n }\n\n async #storedMeta(): Promise<Meta | undefined> {\n if (this.#meta === undefined) this.#meta = await this.#storage.get<Meta>(META);\n return this.#meta;\n }\n\n async #loadMembers(): Promise<Map<string, MemberState>> {\n if (this.#members === undefined) {\n const rows = await this.#storage.list<MemberRecord>({ prefix: MEMBERS });\n const members = new Map<string, MemberState>();\n for (const [key, record] of rows)\n members.set(key.slice(MEMBERS.length), {\n member: record.member,\n expires: record.expires,\n stored: record.expires,\n });\n this.#members = members;\n }\n return this.#members;\n }\n\n /** Writes `puts` and `deletes` and the metadata in one transaction. */\n async #commit(\n meta: Meta,\n puts: Readonly<Record<string, unknown>>,\n deletes: readonly string[],\n ): Promise<void> {\n await this.#storage.transaction(async (transaction) => {\n await transaction.put({ ...puts, [META]: meta });\n for (const batch of chunks(deletes)) await transaction.delete(batch);\n });\n this.#meta = meta;\n }\n\n // --- fan-out -------------------------------------------------------------------------------------------------------\n\n #send(socket: ChannelSocket, text: string): boolean {\n try {\n socket.send(text);\n return true;\n } catch {\n this.#sockets.delete(socket);\n try {\n socket.close(1011, \"write failed\");\n } catch {\n // The socket is gone already.\n }\n return false;\n }\n }\n\n #broadcast(frame: LinkFrame): void {\n if (this.#sockets.size === 0) return;\n const text = JSON.stringify(frame);\n for (const socket of [...this.#sockets]) this.#send(socket, text);\n }\n\n #emit(op: PresenceEvent[\"op\"], member: Member): void {\n this.#broadcast({ t: \"pres\", op, member });\n }\n\n // --- messages ------------------------------------------------------------------------------------------------------\n\n /**\n * Assigns the next seq, retains the message, remembers the key and sends the message to every attached socket.\n *\n * @param channel The channel name.\n * @param entry The message.\n * @param options Retention and the idempotency key.\n * @param idleMs Forget the channel after this long without activity (0 keeps it).\n * @returns The seq and whether the key was already accepted.\n */\n publish(\n channel: string,\n entry: StoredEntry,\n options: PublishOptions,\n idleMs = 0,\n ): Promise<PublishResult> {\n return this.#run(async () => {\n const meta = await this.#metaFor(channel);\n const now = this.#now();\n if (options.key !== undefined) {\n const known = await this.#storage.get<KeyRecord>(KEYS + options.key);\n if (known !== undefined && known.expires > now) return { seq: known.seq, duplicate: true };\n }\n const seq = meta.head + 1;\n const message: StoredMessage = {\n seq,\n event: entry.event,\n data: entry.data,\n at: now,\n ...(entry.publisher === undefined ? {} : { publisher: entry.publisher }),\n ...(entry.user === undefined ? {} : { user: entry.user }),\n };\n const plan = await this.#planTrim(meta, now, options.history, options.historyTtlMs, 1);\n const next: Meta = {\n ...meta,\n head: seq,\n keep: options.history,\n ttlMs: options.historyTtlMs,\n idleMs,\n touched: now,\n oldest: plan.oldest,\n oldestAt: plan.oldestAt,\n };\n const puts: Record<string, unknown> = {};\n if (options.history > 0) {\n puts[historyKey(seq)] = message;\n if (next.oldest === 0) {\n next.oldest = seq;\n next.oldestAt = now;\n }\n }\n if (options.key !== undefined) {\n const expires = now + options.idempotencyTtlMs;\n puts[KEYS + options.key] = { seq, expires } satisfies KeyRecord;\n puts[`${KEY_INDEX}${pad(expires)}:${options.key}`] = 1;\n const due = expires + SWEEP_SLACK_MS;\n next.keysDue = next.keysDue === 0 ? due : Math.min(next.keysDue, due);\n }\n await this.#commit(next, puts, plan.deletes);\n this.#broadcast({ t: \"msg\", m: message });\n await this.#schedule(next);\n return { seq, duplicate: false };\n });\n }\n\n /**\n * Works out which retained messages to delete so that, after `extra` more messages are stored, at most `keep` remain and\n * none is older than `ttlMs`. It reads at most a few pages and deletes at most {@link TRIM_MAX} messages.\n */\n async #planTrim(\n meta: Meta,\n now: number,\n keep: number,\n ttlMs: number,\n extra: number,\n ): Promise<{ deletes: string[]; oldest: number; oldestAt: number }> {\n const deletes: string[] = [];\n let oldest = meta.oldest;\n let oldestAt = meta.oldestAt;\n if (oldest === 0) return { deletes, oldest: 0, oldestAt: 0 };\n const target = meta.head + 1 + extra - keep;\n if (oldest < target) {\n const stop = Math.min(target, meta.head + 1, oldest + TRIM_MAX);\n for (let seq = oldest; seq < stop; seq += 1) deletes.push(historyKey(seq));\n oldest = stop;\n if (oldest > meta.head) return { deletes, oldest: 0, oldestAt: 0 };\n const first = await this.#firstFrom(oldest);\n if (first === undefined) return { deletes, oldest: 0, oldestAt: 0 };\n oldest = first.seq;\n oldestAt = first.at;\n }\n const cutoff = now - ttlMs;\n while (oldestAt < cutoff && deletes.length < TRIM_MAX) {\n const page = await this.#storage.list<StoredMessage>({\n prefix: MESSAGES,\n start: historyKey(oldest),\n limit: BATCH,\n });\n let survivor: StoredMessage | undefined;\n for (const message of page.values()) {\n if (message.at >= cutoff || deletes.length >= TRIM_MAX) {\n survivor = message;\n break;\n }\n deletes.push(historyKey(message.seq));\n }\n if (survivor !== undefined) {\n oldest = survivor.seq;\n oldestAt = survivor.at;\n break;\n }\n if (page.size < BATCH) return { deletes, oldest: 0, oldestAt: 0 };\n const last = deletes.length === 0 ? undefined : deletes[deletes.length - 1];\n if (last === undefined) break;\n oldest = seqOfKey(last) + 1;\n const next = await this.#firstFrom(oldest);\n if (next === undefined) return { deletes, oldest: 0, oldestAt: 0 };\n oldest = next.seq;\n oldestAt = next.at;\n }\n return { deletes, oldest, oldestAt };\n }\n\n async #firstFrom(seq: number): Promise<StoredMessage | undefined> {\n const page = await this.#storage.list<StoredMessage>({\n prefix: MESSAGES,\n start: historyKey(seq),\n limit: 1,\n });\n for (const message of page.values()) return message;\n return undefined;\n }\n\n /** Whether retained messages are past their time or above the count of the last publish. */\n #retentionDue(meta: Meta, now: number): boolean {\n if (meta.oldest === 0) return false;\n return meta.head - meta.oldest + 1 > meta.keep || meta.oldestAt + meta.ttlMs < now;\n }\n\n async #retain(meta: Meta, now: number): Promise<boolean> {\n if (!this.#retentionDue(meta, now)) return false;\n const plan = await this.#planTrim(meta, now, meta.keep, meta.ttlMs, 0);\n if (plan.deletes.length === 0 && plan.oldest === meta.oldest) return false;\n await this.#commit({ ...meta, oldest: plan.oldest, oldestAt: plan.oldestAt }, {}, plan.deletes);\n return true;\n }\n\n /**\n * Reads retained messages after a seq.\n *\n * @param channel The channel name.\n * @param after The last seq the caller holds.\n * @param limit The most messages to return (0 asks for the head only).\n * @returns A page; `more` says retained messages remain after it.\n */\n history(channel: string, after: number, limit: number): Promise<HistoryPage> {\n return this.#run(async () => {\n let meta = await this.#metaFor(channel);\n if (await this.#retain(meta, this.#now())) meta = this.#meta ?? meta;\n if (limit <= 0 || after >= meta.head || meta.oldest === 0)\n return { messages: [], head: meta.head, oldest: meta.oldest, more: false };\n const cap = Math.min(limit, PAGE_MESSAGES);\n const rows = await this.#storage.list<StoredMessage>({\n prefix: MESSAGES,\n start: historyKey(Math.max(after + 1, meta.oldest)),\n limit: cap,\n });\n const messages: StoredMessage[] = [];\n let bytes = 0;\n for (const message of rows.values()) {\n if (messages.length > 0 && bytes + sizeOf(message) > PAGE_BYTES) break;\n messages.push(message);\n bytes += sizeOf(message);\n }\n const last = messages[messages.length - 1];\n // Retained messages are contiguous, so any seq above the last one returned (up to the head) is retained too.\n return {\n messages,\n head: meta.head,\n oldest: meta.oldest,\n more: last !== undefined && last.seq < meta.head,\n };\n });\n }\n\n // --- subscribers ---------------------------------------------------------------------------------------------------\n\n /**\n * Starts sending a socket the channel's messages and presence events: the head, the live members, the messages after\n * `after` that are retained, then every later event. Nothing can interleave, because it runs in the queue.\n *\n * @param socket The server end of the subscribe socket.\n * @param channel The channel name.\n * @param after The last seq the subscriber holds, when it is resuming.\n */\n attach(socket: ChannelSocket, channel: string, after: number | undefined): Promise<void> {\n return this.#run(async () => {\n let meta = await this.#metaFor(channel);\n const now = this.#now();\n if (await this.#retain(meta, now)) meta = this.#meta ?? meta;\n const members = await this.#loadMembers();\n const live: Member[] = [];\n for (const state of members.values()) if (state.expires > now) live.push(state.member);\n const frames: string[] = [\n JSON.stringify({ t: \"ready\", head: meta.head, oldest: meta.oldest } satisfies LinkFrame),\n JSON.stringify({ t: \"snap\", members: live } satisfies LinkFrame),\n ];\n let truncated = false;\n if (after !== undefined && after < meta.head && meta.oldest > 0) {\n let cursor = Math.max(after, meta.oldest - 1);\n let count = 0;\n let bytes = 0;\n replay: while (cursor < meta.head) {\n const rows = await this.#storage.list<StoredMessage>({\n prefix: MESSAGES,\n start: historyKey(cursor + 1),\n limit: BATCH,\n });\n if (rows.size === 0) break;\n for (const message of rows.values()) {\n frames.push(JSON.stringify({ t: \"msg\", m: message } satisfies LinkFrame));\n cursor = message.seq;\n count += 1;\n bytes += sizeOf(message);\n if (count >= REPLAY_MESSAGES || bytes >= REPLAY_BYTES) {\n truncated = cursor < meta.head;\n break replay;\n }\n }\n if (rows.size < BATCH) break;\n }\n }\n if (truncated) frames.push(JSON.stringify({ t: \"more\" } satisfies LinkFrame));\n for (const frame of frames) {\n try {\n socket.send(frame);\n } catch {\n return;\n }\n }\n this.#sockets.add(socket);\n });\n }\n\n // --- presence ------------------------------------------------------------------------------------------------------\n\n /**\n * Adds or updates a member. An older version is ignored; the first sighting is a `join`, a newer version a `set`.\n *\n * @param channel The channel name.\n * @param member The member.\n * @param ttlMs Milliseconds until it expires without a touch.\n * @param graceMs Milliseconds after expiry that the alarm waits before it reaps (so the hub's own reap comes first).\n */\n presenceSet(channel: string, member: Member, ttlMs: number, graceMs: number): Promise<void> {\n return this.#run(async () => {\n const meta = await this.#metaFor(channel);\n const members = await this.#loadMembers();\n const known = members.get(member.id);\n if (known !== undefined && known.member.v > member.v) return;\n if (known === undefined && members.size >= this.#maxMembers)\n throw new RealtimeError(\"limit\", \"the channel has too many presence members\");\n const now = this.#now();\n const expires = now + ttlMs;\n const changed = known === undefined || known.member.v < member.v;\n const due = expires + graceMs;\n const next: Meta = {\n ...meta,\n graceMs,\n touched: now,\n presenceDue: meta.presenceDue === 0 ? due : Math.min(meta.presenceDue, due),\n };\n const persist = changed || known === undefined || expires - known.stored >= ttlMs / 2;\n if (persist || next.presenceDue !== meta.presenceDue || next.graceMs !== meta.graceMs) {\n await this.#commit(\n next,\n persist ? { [MEMBERS + member.id]: { member, expires } satisfies MemberRecord } : {},\n [],\n );\n }\n members.set(member.id, {\n member,\n expires,\n stored: persist ? expires : (known?.stored ?? expires),\n });\n if (known === undefined) this.#emit(\"join\", member);\n else if (known.member.v < member.v) this.#emit(\"set\", member);\n await this.#schedule(next);\n });\n }\n\n /**\n * Removes a member.\n *\n * @param channel The channel name.\n * @param id The member id.\n * @returns True when the member existed (and a `leave` was sent); false otherwise.\n */\n presenceRemove(channel: string, id: string): Promise<boolean> {\n return this.#run(async () => {\n const meta = await this.#metaFor(channel);\n const members = await this.#loadMembers();\n const known = members.get(id);\n if (known === undefined) return false;\n const next: Meta = members.size === 1 ? { ...meta, presenceDue: 0 } : meta;\n await this.#commit(next, {}, [MEMBERS + id]);\n members.delete(id);\n this.#emit(\"leave\", known.member);\n await this.#schedule(next);\n return true;\n });\n }\n\n /**\n * The members that have not expired.\n *\n * @param channel The channel name.\n * @returns The members.\n */\n presenceList(channel: string): Promise<Member[]> {\n return this.#run(async () => {\n await this.#metaFor(channel);\n const members = await this.#loadMembers();\n const now = this.#now();\n const out: Member[] = [];\n for (const state of members.values()) if (state.expires > now) out.push(state.member);\n return out;\n });\n }\n\n /**\n * Extends the expiry of members. The storage is written only when the new expiry is at least half a time to live beyond\n * the stored one, so a heartbeat every few seconds costs a write every few beats; a restart in between loses at most\n * that half, which a heartbeat repairs long before the member would expire.\n *\n * @param channel The channel name.\n * @param ids The member ids.\n * @param ttlMs Milliseconds until they expire without another touch.\n * @returns The ids that no longer exist.\n */\n presenceTouch(channel: string, ids: readonly string[], ttlMs: number): Promise<string[]> {\n return this.#run(async () => {\n const meta = await this.#metaFor(channel);\n const members = await this.#loadMembers();\n const now = this.#now();\n const expires = now + ttlMs;\n const missing: string[] = [];\n const puts: Record<string, unknown> = {};\n const written: MemberState[] = [];\n let touched = 0;\n for (const id of ids) {\n const state = members.get(id);\n if (state === undefined) {\n missing.push(id);\n continue;\n }\n // Like the reference adapter, the expiry becomes exactly now + ttl: a touch with a shorter life shortens it.\n state.expires = expires;\n touched += 1;\n if (state.expires - state.stored >= ttlMs / 2) {\n puts[MEMBERS + id] = {\n member: state.member,\n expires: state.expires,\n } satisfies MemberRecord;\n written.push(state);\n }\n }\n const keys = Object.keys(puts);\n // `put` takes at most 128 entries.\n for (const batch of chunks(keys)) {\n const part: Record<string, unknown> = {};\n for (const key of batch) part[key] = puts[key];\n await this.#storage.put(part);\n }\n for (const state of written) state.stored = state.expires;\n // Touches usually push the expiry later, which the alarm finds out for itself; a shorter life needs an earlier alarm.\n const due = expires + meta.graceMs;\n if (touched > 0 && (meta.presenceDue === 0 || due < meta.presenceDue)) {\n const next: Meta = { ...meta, presenceDue: due };\n await this.#commit(next, {}, []);\n await this.#schedule(next);\n }\n return missing;\n });\n }\n\n /**\n * Removes every expired member and sends one `leave` for each.\n *\n * @param channel The channel name.\n * @returns The members removed.\n */\n presenceReap(channel: string): Promise<Member[]> {\n return this.#run(async () => {\n const meta = await this.#metaFor(channel);\n return this.#reap(meta, this.#now());\n });\n }\n\n async #reap(meta: Meta, now: number): Promise<Member[]> {\n const members = await this.#loadMembers();\n const gone: MemberState[] = [];\n for (const state of members.values()) if (state.expires <= now) gone.push(state);\n if (gone.length === 0) return [];\n let due = 0;\n for (const state of members.values())\n if (state.expires > now) {\n const at = state.expires + meta.graceMs;\n due = due === 0 ? at : Math.min(due, at);\n }\n const next: Meta = { ...meta, presenceDue: due };\n await this.#commit(\n next,\n {},\n gone.map((state) => MEMBERS + state.member.id),\n );\n for (const state of gone) {\n members.delete(state.member.id);\n this.#emit(\"leave\", state.member);\n }\n return gone.map((state) => state.member);\n }\n\n // --- the alarm -----------------------------------------------------------------------------------------------------\n\n /** The time the next alarm is due, `undefined` when nothing is pending. */\n #nextDue(meta: Meta, now: number): number | undefined {\n let due = 0;\n const consider = (at: number): void => {\n if (at > 0 && (due === 0 || at < due)) due = at;\n };\n consider(meta.presenceDue);\n consider(meta.keysDue);\n if (meta.oldest > 0)\n consider(\n meta.head - meta.oldest + 1 > meta.keep\n ? now\n : meta.oldestAt + meta.ttlMs + HISTORY_SLACK_MS,\n );\n if (meta.idleMs > 0) {\n // A channel with a subscriber is not idle: look again after another idle period instead of waking at once, every time.\n const idleAt = meta.touched + meta.idleMs;\n consider(this.#sockets.size > 0 && idleAt <= now ? now + meta.idleMs : idleAt);\n }\n return due === 0 ? undefined : Math.max(due, now);\n }\n\n /** Makes the storage's alarm fire no later than the next thing that is due. An earlier alarm is left alone: it wakes, finds nothing, and sets the right time. */\n async #schedule(meta: Meta): Promise<void> {\n const now = this.#now();\n const due = this.#nextDue(meta, now);\n if (this.#alarmAt === undefined) this.#alarmAt = await this.#storage.getAlarm();\n if (due === undefined) {\n if (this.#alarmAt !== null) {\n await this.#storage.deleteAlarm();\n this.#alarmAt = null;\n }\n return;\n }\n if (this.#alarmAt === null || this.#alarmAt > due) {\n await this.#storage.setAlarm(due);\n this.#alarmAt = due;\n }\n }\n\n /** Deletes idempotency keys that expired, in batches. */\n async #sweepKeys(meta: Meta, now: number): Promise<Meta> {\n for (let pass = 0; pass < 32; pass += 1) {\n const rows = await this.#storage.list<number>({\n prefix: KEY_INDEX,\n end: KEY_INDEX + pad(now + 1),\n limit: BATCH,\n });\n if (rows.size === 0) break;\n const indexKeys = [...rows.keys()];\n const names = indexKeys.map((key) => key.slice(KEY_INDEX_PREFIX));\n const records = await this.#storage.get<KeyRecord>(names.map((name) => KEYS + name));\n const deletes = [...indexKeys];\n for (const name of names) {\n const record = records.get(KEYS + name);\n if (record === undefined || record.expires <= now) deletes.push(KEYS + name);\n }\n await this.#storage.transaction(async (transaction) => {\n for (const batch of chunks(deletes)) await transaction.delete(batch);\n });\n if (rows.size < BATCH) break;\n }\n const first = await this.#storage.list<number>({ prefix: KEY_INDEX, limit: 1 });\n let keysDue = 0;\n for (const key of first.keys())\n keysDue = Number(key.slice(KEY_INDEX.length, KEY_INDEX.length + 16)) + SWEEP_SLACK_MS;\n return { ...meta, keysDue };\n }\n\n /**\n * The alarm handler: reaps expired presence members (one `leave` each), sweeps expired idempotency keys, trims retained\n * messages that expired, forgets a channel that has been idle for its `idleMs`, and sets the next alarm. It is safe to run\n * more often than needed, and to run again after a failure.\n */\n alarm(): Promise<void> {\n return this.#run(async () => {\n this.#alarmAt = null;\n let meta = await this.#storedMeta();\n if (meta === undefined) return;\n const now = this.#now();\n if (meta.presenceDue > 0 && meta.presenceDue <= now) {\n await this.#reap(meta, now);\n meta = this.#meta ?? meta;\n // An alarm earlier than the earliest expiry (members were touched) reaps nothing: move the due time instead.\n let due = 0;\n for (const state of (await this.#loadMembers()).values()) {\n const at = state.expires + meta.graceMs;\n due = due === 0 ? at : Math.min(due, at);\n }\n if (due !== meta.presenceDue) {\n meta = { ...meta, presenceDue: due };\n await this.#commit(meta, {}, []);\n }\n }\n if (meta.keysDue > 0 && meta.keysDue <= now) {\n meta = await this.#sweepKeys(meta, now);\n await this.#commit(meta, {}, []);\n }\n if (await this.#retain(meta, now)) meta = this.#meta ?? meta;\n if (\n meta.idleMs > 0 &&\n meta.touched + meta.idleMs <= now &&\n meta.oldest === 0 &&\n meta.presenceDue === 0 &&\n meta.keysDue === 0 &&\n this.#sockets.size === 0\n ) {\n await this.#storage.deleteAll();\n this.#meta = undefined;\n this.#members = undefined;\n this.#alarmAt = null;\n return;\n }\n await this.#schedule(meta);\n });\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{RealtimeError as e}from"../errors.js";import{decodeLinkFrame as t}from"./wire.js";function n(){let e=()=>void 0,t=()=>void 0,n=new Promise((n,r)=>{e=n,t=r});return n.catch(()=>void 0),{promise:n,resolve:e,reject:t}}var r=class{#e=[];#t;#n;#r=!1;constructor(e){this.#t=e}push(e){if(this.#r||this.#e.length>=this.#t)return;this.#e.push(e);let t=this.#n;this.#n=void 0,t?.()}stop(){this.#r=!0,this.#e.length=0;let e=this.#n;this.#n=void 0,e?.()}async run(e,t,n){let r=n?.();try{for(;!this.#r;){let n=this.#e.shift();if(n===void 0)await new Promise(e=>{this.#n=e});else try{e(n)}catch(e){t(e)}}}finally{r?.()}}},i=class{channel;#e;#t=/* @__PURE__ */ new Set;#n=/* @__PURE__ */ new Set;#r=`idle`;#i;#a;#o=0;#s=0;#c=!1;#l;#u=0;#d=0;#f=0;#p=0;#m=n();#h;#g;#_;constructor(e,t){this.channel=e,this.#e=t}get state(){return this.#r}get size(){return this.#t.size+this.#n.size}addMessages(e){let t={listener:e,pump:new r(this.#e.maxQueued),last:this.#r===`open`?this.#s:-1};return this.#t.add(t),t.pump.run(e,e=>this.#e.report(e,`listener`,this.channel),this.#v()),this.#y(),t}addPresence(e){let t={listener:e,pump:new r(this.#e.maxQueued)};return this.#n.add(t),t.pump.run(e,e=>this.#e.report(e,`listener`,this.channel),this.#v()),this.#y(),t}remove(t){t.pump.stop(),this.#t.delete(t),this.#n.delete(t),this.size===0&&this.dispose(new e(`closed`,`the subscription ended`))}whenReady(t){return this.#r===`open`?Promise.resolve():new Promise((n,r)=>{let i=this.#e.after(t,()=>r(new e(`unavailable`,`timed out connecting to the channel object`)));this.#m.promise.then(()=>{i(),n()},e=>{i(),r(e)})})}#v(){let e=this.#e.keepAliveMs;if(!(e<=0))return()=>this.#e.every(e,()=>this.#E())}#y(){this.#r===`idle`&&this.#b()}#b(){if(this.#r===`disposed`)return;this.#h?.(),this.#h=void 0,this.#g?.(),this.#r=`connecting`;let t=++this.#o;this.#p=this.#e.now(),this.#g=this.#e.after(this.#e.connectTimeoutMs,()=>{t===this.#o&&this.#r===`connecting`&&this.#w(t,new e(`unavailable`,`the channel object did not answer`))}),this.#e.open(this.channel,this.#c?this.#s:void 0).then(e=>this.#x(t,e),e=>{t===this.#o&&this.#w(t,e)})}#x(t,r){if(t!==this.#o||this.#r!==`connecting`){this.#C(r,void 0);return}let i=n();this.#a=i,this.#i=r,i.promise.then(()=>this.#S(r),()=>void 0),r.addEventListener(`message`,e=>{t===this.#o&&typeof e.data==`string`&&this.#D(t,e.data)}),r.addEventListener(`close`,()=>{t===this.#o&&this.#w(t,new e(`unavailable`,`closed`))}),r.addEventListener(`error`,()=>{t===this.#o&&this.#w(t,new e(`unavailable`,`error`))});try{r.accept()}catch(e){this.#w(t,e)}}#S(e){try{return e.close(1e3,`done`),!0}catch{return!1}}#C(e,t){e!==void 0&&(this.#S(e)||t?.resolve())}#w(e,t){if(e!==this.#o||this.#r===`disposed`)return;this.#o+=1,this.#g?.(),this.#g=void 0,this.#_?.(),this.#_=void 0;let r=this.#i,i=this.#a;if(this.#i=void 0,this.#a=void 0,this.#C(r,i),this.#r===`open`&&(this.#m=n()),this.size===0){this.dispose(t);return}this.#r=`backoff`,this.#d+=1;let a=Math.min(this.#e.reconnectMaxMs,this.#e.reconnectMinMs*2**Math.min(this.#d-1,20)),o=Math.floor(this.#e.random()*a);this.#f=this.#e.now()+o,this.#h?.(),this.#h=this.#e.after(o,()=>this.#b())}#T(e){if(e!==this.#o)return;this.#o+=1;let t=this.#i,n=this.#a;this.#i=void 0,this.#a=void 0,this.#_?.(),this.#_=void 0,this.#C(t,n),this.#r=`idle`,this.#b()}#E(){let t=this.#e.now(),n=Math.max(1e3,this.#e.keepAliveMs);switch(this.#r){case`backoff`:t>this.#f+n&&this.#b();return;case`connecting`:t>this.#p+this.#e.connectTimeoutMs*2&&this.#b();return;case`open`:{let n=this.#e.keepAliveMs;if(n>0&&t-this.#u>n*3){this.#w(this.#o,new e(`unavailable`,`the channel object went silent`));return}try{this.#i?.send(`ping`)}catch{}return}default:return}}#D(n,r){if(this.#u=this.#e.now(),r===`pong`)return;let i=t(r);if(i!==void 0)switch(i.t){case`ready`:if(this.#c&&i.head<this.#s){this.#s=i.head;for(let e of this.#t)e.last=Math.min(e.last,i.head)}this.#c||=(this.#s=i.head,!0);for(let e of this.#t)e.last<0&&(e.last=i.head);this.#r=`open`,this.#d=0,this.#g?.(),this.#g=void 0,this.#u=this.#e.now(),this.#e.keepAliveMs>0&&(this.#_?.(),this.#_=this.#e.every(this.#e.keepAliveMs,()=>this.#E())),this.#m.resolve();return;case`snap`:{let e=/* @__PURE__ */ new Map;for(let t of i.members)e.set(t.id,t);let t=this.#l;if(this.#l=e,t===void 0)return;for(let[n,r]of t)e.has(n)||this.#O({channel:this.channel,op:`leave`,member:r});for(let[n,r]of e){let e=t.get(n);e===void 0?this.#O({channel:this.channel,op:`join`,member:r}):r.v>e.v&&this.#O({channel:this.channel,op:`set`,member:r})}return}case`msg`:{let e=i.m;e.seq>this.#s&&(this.#s=e.seq);for(let t of this.#t)e.seq>t.last&&(t.last=e.seq,t.pump.push(e));return}case`pres`:{let e=this.#l??/* @__PURE__ */ new Map;this.#l=e;let t=e.get(i.member.id);if(i.op===`leave`){if(t===void 0)return;e.delete(i.member.id)}else{if(t!==void 0&&t.v>=i.member.v&&i.op===`join`||t!==void 0&&t.v>i.member.v)return;e.set(i.member.id,i.member)}this.#O({channel:this.channel,op:i.op,member:i.member});return}case`more`:this.#T(n);return;case`bye`:this.#w(n,new e(`unavailable`,i.why));return}}#O(e){for(let t of this.#n)t.pump.push(e)}dispose(e){if(this.#r===`disposed`)return;this.#r=`disposed`,this.#o+=1,this.#h?.(),this.#g?.(),this.#_?.(),this.#h=void 0,this.#g=void 0,this.#_=void 0;let t=this.#i,n=this.#a;this.#i=void 0,this.#a=void 0,this.#C(t,n),this.#m.reject(e);for(let e of this.#t)e.pump.stop();for(let e of this.#n)e.pump.stop();this.#t.clear(),this.#n.clear(),this.#e.forget(this)}};export{i as ChannelLink};
|
|
2
|
+
//# sourceMappingURL=link.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link.js","names":["deferred","resolve","reject","promise","onResolve","onReject","Pump","#max","max","#stopped","#queue","item","wake","#wake","cancel","every","deliver","error","report","ChannelLink","channel","#host","host","#state","#messages","#presence","sub","#last","listener","#nudger","#start","RealtimeError","ms","#ready","#check","#connect","#cancelRetry","#cancelConnect","attempt","#attempt","#connectedAt","#down","#known","#opened","socket","#closeSocket","owner","#closeOwner","#socket","#tryClose","event","#received","#cancelKeep","#failures","ceiling","delay","#retryAt","now","grace","keep","#lastReceived","text","frame","decodeLinkFrame","next","member","before","#members","id","#fanPresence","old","message","members","known","#resume","reason"],"sources":["../../../src/cloudflare/link.ts"],"sourcesContent":["/**\n * The Worker side of a channel's subscribe socket: one socket per channel and adapter, shared by every local listener, kept\n * open with backoff, resumed with `after`, and feeding each listener through its own queue.\n *\n * A Worker may use an I/O object (a socket, a stream, a timer) only inside the request that created it. A listener runs in the\n * request that subscribed, so it is called by a loop that was started there and woken by a plain promise: the socket's\n * handler only moves data into queues and never calls a listener itself.\n * @packageDocumentation\n */\nimport { RealtimeError } from \"../errors\";\nimport type { Member, PresenceEvent, StoredMessage } from \"../protocol\";\nimport type { ClientSocket } from \"./types\";\nimport { decodeLinkFrame } from \"./wire\";\n\n/** What a link needs from the adapter that owns it. @internal */\nexport interface LinkHost {\n readonly reconnectMinMs: number;\n readonly reconnectMaxMs: number;\n /** Ping period; 0 turns pings off. */\n readonly keepAliveMs: number;\n readonly connectTimeoutMs: number;\n /** The most events queued for one listener. */\n readonly maxQueued: number;\n now(): number;\n random(): number;\n /** Opens the subscribe socket (not yet accepted). */\n open(channel: string, after: number | undefined): Promise<ClientSocket>;\n /** Runs `task` once after `ms`; returns the way to cancel. Everything scheduled is canceled when the adapter closes. */\n after(ms: number, task: () => void): () => void;\n /** Runs `task` every `ms`; returns the way to cancel. */\n every(ms: number, task: () => void): () => void;\n report(error: unknown, where: string, channel: string): void;\n /** Called once when the link has nothing left to do. */\n forget(link: ChannelLink): void;\n}\n\ninterface Deferred {\n readonly promise: Promise<void>;\n resolve(): void;\n reject(error: unknown): void;\n}\n\nfunction deferred(): Deferred {\n let resolve: () => void = () => undefined;\n let reject: (error: unknown) => void = () => undefined;\n const promise = new Promise<void>((onResolve, onReject) => {\n resolve = onResolve;\n reject = onReject;\n });\n // A waiter may give up first; the rejection must not become an unhandled one.\n promise.catch(() => undefined);\n return { promise, resolve, reject };\n}\n\n/** A queue and the loop that empties it, in the request that created it. */\nclass Pump<T> {\n readonly #queue: T[] = [];\n readonly #max: number;\n #wake: (() => void) | undefined;\n #stopped = false;\n\n constructor(max: number) {\n this.#max = max;\n }\n\n push(item: T): void {\n if (this.#stopped || this.#queue.length >= this.#max) return;\n this.#queue.push(item);\n const wake = this.#wake;\n this.#wake = undefined;\n wake?.();\n }\n\n stop(): void {\n this.#stopped = true;\n this.#queue.length = 0;\n const wake = this.#wake;\n this.#wake = undefined;\n wake?.();\n }\n\n async run(\n deliver: (item: T) => void,\n report: (error: unknown) => void,\n every: (() => () => void) | undefined,\n ): Promise<void> {\n const cancel = every?.();\n try {\n while (!this.#stopped) {\n const item = this.#queue.shift();\n if (item === undefined) {\n await new Promise<void>((resolve) => {\n this.#wake = resolve;\n });\n continue;\n }\n try {\n deliver(item);\n } catch (error) {\n report(error);\n }\n }\n } finally {\n cancel?.();\n }\n }\n}\n\ninterface MessageSub {\n readonly listener: (message: StoredMessage) => void;\n readonly pump: Pump<StoredMessage>;\n /** The highest seq queued for this listener; -1 until the link tells the head. */\n last: number;\n}\n\ninterface PresenceSub {\n readonly listener: (event: PresenceEvent) => void;\n readonly pump: Pump<PresenceEvent>;\n}\n\n/** A way to stop one listener. @internal */\nexport type LinkSubscription = MessageSub | PresenceSub;\n\ntype LinkState = \"idle\" | \"connecting\" | \"open\" | \"backoff\" | \"disposed\";\n\n/** @internal */\nexport class ChannelLink {\n readonly channel: string;\n readonly #host: LinkHost;\n readonly #messages = new Set<MessageSub>();\n readonly #presence = new Set<PresenceSub>();\n #state: LinkState = \"idle\";\n #socket: ClientSocket | undefined;\n #closeOwner: Deferred | undefined;\n #attempt = 0;\n /** The highest seq received on this link: where a reconnect resumes. */\n #last = 0;\n #known = false;\n #members: Map<string, Member> | undefined;\n #lastReceived = 0;\n #failures = 0;\n #retryAt = 0;\n #connectedAt = 0;\n #ready: Deferred = deferred();\n #cancelRetry: (() => void) | undefined;\n #cancelConnect: (() => void) | undefined;\n #cancelKeep: (() => void) | undefined;\n\n constructor(channel: string, host: LinkHost) {\n this.channel = channel;\n this.#host = host;\n }\n\n get state(): LinkState {\n return this.#state;\n }\n\n get size(): number {\n return this.#messages.size + this.#presence.size;\n }\n\n // --- listeners -----------------------------------------------------------------------------------------------------\n\n /** Adds a message listener (call it in the request that will receive messages). */\n addMessages(listener: (message: StoredMessage) => void): LinkSubscription {\n const sub: MessageSub = {\n listener,\n pump: new Pump(this.#host.maxQueued),\n last: this.#state === \"open\" ? this.#last : -1,\n };\n this.#messages.add(sub);\n void sub.pump.run(\n listener,\n (error) => this.#host.report(error, \"listener\", this.channel),\n this.#nudger(),\n );\n this.#start();\n return sub;\n }\n\n /** Adds a presence listener (call it in the request that will receive events). */\n addPresence(listener: (event: PresenceEvent) => void): LinkSubscription {\n const sub: PresenceSub = { listener, pump: new Pump(this.#host.maxQueued) };\n this.#presence.add(sub);\n void sub.pump.run(\n listener,\n (error) => this.#host.report(error, \"listener\", this.channel),\n this.#nudger(),\n );\n this.#start();\n return sub;\n }\n\n /** Removes a listener; the last one closes the link. */\n remove(sub: LinkSubscription): void {\n sub.pump.stop();\n this.#messages.delete(sub as MessageSub);\n this.#presence.delete(sub as PresenceSub);\n if (this.size === 0) this.dispose(new RealtimeError(\"closed\", \"the subscription ended\"));\n }\n\n /** Resolves when the socket is open and has told the channel's head; rejects after `ms`. */\n whenReady(ms: number): Promise<void> {\n if (this.#state === \"open\") return Promise.resolve();\n return new Promise<void>((resolve, reject) => {\n const cancel = this.#host.after(ms, () =>\n reject(new RealtimeError(\"unavailable\", \"timed out connecting to the channel object\")),\n );\n this.#ready.promise.then(\n () => {\n cancel();\n resolve();\n },\n (error: unknown) => {\n cancel();\n reject(error);\n },\n );\n });\n }\n\n // --- connecting ----------------------------------------------------------------------------------------------------\n\n /** The pump's periodic check, if pings are on. */\n #nudger(): (() => () => void) | undefined {\n const every = this.#host.keepAliveMs;\n if (every <= 0) return undefined;\n return () => this.#host.every(every, () => this.#check());\n }\n\n #start(): void {\n if (this.#state === \"idle\") this.#connect();\n }\n\n #connect(): void {\n if (this.#state === \"disposed\") return;\n this.#cancelRetry?.();\n this.#cancelRetry = undefined;\n this.#cancelConnect?.();\n this.#state = \"connecting\";\n const attempt = ++this.#attempt;\n this.#connectedAt = this.#host.now();\n this.#cancelConnect = this.#host.after(this.#host.connectTimeoutMs, () => {\n if (attempt === this.#attempt && this.#state === \"connecting\")\n this.#down(attempt, new RealtimeError(\"unavailable\", \"the channel object did not answer\"));\n });\n this.#host.open(this.channel, this.#known ? this.#last : undefined).then(\n (socket) => this.#opened(attempt, socket),\n (error: unknown) => {\n if (attempt === this.#attempt) this.#down(attempt, error);\n },\n );\n }\n\n #opened(attempt: number, socket: ClientSocket): void {\n if (attempt !== this.#attempt || this.#state !== \"connecting\") {\n this.#closeSocket(socket, undefined);\n return;\n }\n const owner = deferred();\n this.#closeOwner = owner;\n this.#socket = socket;\n // Waits in the request that opened the socket, which is the one place guaranteed to be allowed to close it.\n void owner.promise.then(\n () => this.#tryClose(socket),\n () => undefined,\n );\n socket.addEventListener(\"message\", (event) => {\n if (attempt !== this.#attempt || typeof event.data !== \"string\") return;\n this.#received(attempt, event.data);\n });\n socket.addEventListener(\"close\", () => {\n if (attempt === this.#attempt)\n this.#down(attempt, new RealtimeError(\"unavailable\", \"closed\"));\n });\n socket.addEventListener(\"error\", () => {\n if (attempt === this.#attempt) this.#down(attempt, new RealtimeError(\"unavailable\", \"error\"));\n });\n try {\n socket.accept();\n } catch (error) {\n this.#down(attempt, error);\n }\n }\n\n #tryClose(socket: ClientSocket): boolean {\n try {\n socket.close(1000, \"done\");\n return true;\n } catch {\n return false;\n }\n }\n\n /** Closes a socket from wherever this runs; when the runtime refuses, the request that opened it does it. */\n #closeSocket(socket: ClientSocket | undefined, owner: Deferred | undefined): void {\n if (socket === undefined) return;\n if (!this.#tryClose(socket)) owner?.resolve();\n }\n\n /** The socket failed or closed: schedule a new attempt, or end the link when nobody listens. */\n #down(attempt: number, error: unknown): void {\n if (attempt !== this.#attempt || this.#state === \"disposed\") return;\n this.#attempt += 1;\n this.#cancelConnect?.();\n this.#cancelConnect = undefined;\n this.#cancelKeep?.();\n this.#cancelKeep = undefined;\n const socket = this.#socket;\n const owner = this.#closeOwner;\n this.#socket = undefined;\n this.#closeOwner = undefined;\n this.#closeSocket(socket, owner);\n // Waiters keep the promise they hold until the link is open; only an opened link needs a new one.\n if (this.#state === \"open\") this.#ready = deferred();\n if (this.size === 0) {\n this.dispose(error);\n return;\n }\n this.#state = \"backoff\";\n this.#failures += 1;\n const ceiling = Math.min(\n this.#host.reconnectMaxMs,\n this.#host.reconnectMinMs * 2 ** Math.min(this.#failures - 1, 20),\n );\n const delay = Math.floor(this.#host.random() * ceiling);\n this.#retryAt = this.#host.now() + delay;\n this.#cancelRetry?.();\n this.#cancelRetry = this.#host.after(delay, () => this.#connect());\n }\n\n /** Reconnects now, without waiting: the object asked for it (a long replay is continued with a new socket). */\n #resume(attempt: number): void {\n if (attempt !== this.#attempt) return;\n this.#attempt += 1;\n const socket = this.#socket;\n const owner = this.#closeOwner;\n this.#socket = undefined;\n this.#closeOwner = undefined;\n this.#cancelKeep?.();\n this.#cancelKeep = undefined;\n this.#closeSocket(socket, owner);\n this.#state = \"idle\";\n this.#connect();\n }\n\n /**\n * A health check run by every listener's loop (each in its own request) and by the ping timer: whichever request is alive\n * can repair a link whose own request is gone, because a timer or a socket handler of a finished request never runs again.\n */\n #check(): void {\n const now = this.#host.now();\n const grace = Math.max(1000, this.#host.keepAliveMs);\n switch (this.#state) {\n case \"backoff\":\n if (now > this.#retryAt + grace) this.#connect();\n return;\n case \"connecting\":\n if (now > this.#connectedAt + this.#host.connectTimeoutMs * 2) this.#connect();\n return;\n case \"open\": {\n const keep = this.#host.keepAliveMs;\n if (keep > 0 && now - this.#lastReceived > keep * 3) {\n this.#down(\n this.#attempt,\n new RealtimeError(\"unavailable\", \"the channel object went silent\"),\n );\n return;\n }\n try {\n this.#socket?.send(\"ping\");\n } catch {\n // Sending from another request is refused; the owner's own ping covers it.\n }\n return;\n }\n default:\n return;\n }\n }\n\n // --- frames --------------------------------------------------------------------------------------------------------\n\n #received(attempt: number, text: string): void {\n this.#lastReceived = this.#host.now();\n if (text === \"pong\") return;\n const frame = decodeLinkFrame(text);\n if (frame === undefined) return;\n switch (frame.t) {\n case \"ready\": {\n if (this.#known && frame.head < this.#last) {\n // The channel's numbering restarted (it was forgotten): follow it.\n this.#last = frame.head;\n for (const sub of this.#messages) sub.last = Math.min(sub.last, frame.head);\n }\n if (!this.#known) {\n this.#last = frame.head;\n this.#known = true;\n }\n for (const sub of this.#messages) if (sub.last < 0) sub.last = frame.head;\n this.#state = \"open\";\n this.#failures = 0;\n this.#cancelConnect?.();\n this.#cancelConnect = undefined;\n this.#lastReceived = this.#host.now();\n if (this.#host.keepAliveMs > 0) {\n this.#cancelKeep?.();\n this.#cancelKeep = this.#host.every(this.#host.keepAliveMs, () => this.#check());\n }\n this.#ready.resolve();\n return;\n }\n case \"snap\": {\n const next = new Map<string, Member>();\n for (const member of frame.members) next.set(member.id, member);\n const before = this.#members;\n this.#members = next;\n if (before === undefined) return;\n for (const [id, member] of before)\n if (!next.has(id)) this.#fanPresence({ channel: this.channel, op: \"leave\", member });\n for (const [id, member] of next) {\n const old = before.get(id);\n if (old === undefined) this.#fanPresence({ channel: this.channel, op: \"join\", member });\n else if (member.v > old.v)\n this.#fanPresence({ channel: this.channel, op: \"set\", member });\n }\n return;\n }\n case \"msg\": {\n const message = frame.m;\n if (message.seq > this.#last) this.#last = message.seq;\n for (const sub of this.#messages)\n if (message.seq > sub.last) {\n sub.last = message.seq;\n sub.pump.push(message);\n }\n return;\n }\n case \"pres\": {\n const members = this.#members ?? new Map<string, Member>();\n this.#members = members;\n const known = members.get(frame.member.id);\n if (frame.op === \"leave\") {\n if (known === undefined) return;\n members.delete(frame.member.id);\n } else {\n if (known !== undefined && known.v >= frame.member.v && frame.op === \"join\") return;\n if (known !== undefined && known.v > frame.member.v) return;\n members.set(frame.member.id, frame.member);\n }\n this.#fanPresence({ channel: this.channel, op: frame.op, member: frame.member });\n return;\n }\n case \"more\":\n this.#resume(attempt);\n return;\n case \"bye\":\n this.#down(attempt, new RealtimeError(\"unavailable\", frame.why));\n return;\n }\n }\n\n #fanPresence(event: PresenceEvent): void {\n for (const sub of this.#presence) sub.pump.push(event);\n }\n\n // --- ending --------------------------------------------------------------------------------------------------------\n\n /** Ends the link: stops every listener, cancels timers and closes the socket. */\n dispose(reason: unknown): void {\n if (this.#state === \"disposed\") return;\n this.#state = \"disposed\";\n this.#attempt += 1;\n this.#cancelRetry?.();\n this.#cancelConnect?.();\n this.#cancelKeep?.();\n this.#cancelRetry = undefined;\n this.#cancelConnect = undefined;\n this.#cancelKeep = undefined;\n const socket = this.#socket;\n const owner = this.#closeOwner;\n this.#socket = undefined;\n this.#closeOwner = undefined;\n this.#closeSocket(socket, owner);\n this.#ready.reject(reason);\n for (const sub of this.#messages) sub.pump.stop();\n for (const sub of this.#presence) sub.pump.stop();\n this.#messages.clear();\n this.#presence.clear();\n this.#host.forget(this);\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{RealtimeError as e}from"../errors.js";import{isChannelName as t}from"../protocol.js";import{isOperation as n,parseRequest as r}from"./wire.js";import{ChannelCore as i}from"./core.js";function a(){return{createPair(){let t=Reflect.get(globalThis,`WebSocketPair`);if(typeof t!=`function`)throw new e(`unavailable`,`WebSocketPair is missing: this is not a Worker`);let n=Reflect.construct(t,[]),r=Reflect.get(Object(n),`0`),i=Reflect.get(Object(n),`1`);if(typeof i!=`object`||!i||typeof Reflect.get(i,`send`)!=`function`||typeof Reflect.get(i,`close`)!=`function`)throw TypeError(`WebSocketPair returned an unexpected socket`);return{client:r,server:i}},upgrade(e){return new Response(null,{status:101,webSocket:e})},autoResponse(){let e=Reflect.get(globalThis,`WebSocketRequestResponsePair`);return typeof e==`function`?Reflect.construct(e,[`ping`,`pong`]):void 0}}}const o={"content-type":`application/json`};function s(e,t=200){return new Response(JSON.stringify(e),{status:t,headers:o})}function c(e,t,n){return s({error:{code:e,message:t}},n)}const l={bad_frame:400,bad_channel:400,too_large:413,limit:429,conflict:409};function u(e){return e>=1e3&&e<5e3&&e!==1005&&e!==1006&&e!==1015?e:1e3}function d(e){let t=e.searchParams.get(`after`);if(t!==null)return/^\d{1,16}$/.test(t)?Number(t):`bad`}var f=class{#e;#t;#n;constructor(e,t,n){this.#e=e,this.#n=n??a(),this.#t=new i({storage:e.storage}),this.#t.adopt(e.getWebSockets());let r=this.#n.autoResponse();r!==void 0&&e.setWebSocketAutoResponse?.(r)}async fetch(t){let i=new URL(t.url);if(i.pathname===`/v1/ws`)return this.#r(t,i);let a=/^\/v(\d+)\/([a-z-]+)$/.exec(i.pathname);if(a?.[1]!==`1`||!n(a[2]??``))return c(`bad_frame`,`unknown operation`,404);let o=a[2]??``;if(!n(o))return c(`bad_frame`,`unknown operation`,404);if(t.method!==`POST`)return c(`bad_frame`,`use POST`,405);if(Number(t.headers.get(`content-length`)??0)>262144)return c(`too_large`,`the body is too large`,413);let u=await t.text();if(u.length>262144)return c(`too_large`,`the body is too large`,413);let d;try{d=JSON.parse(u)}catch{return c(`bad_frame`,`the body is not JSON`,400)}let f=r(o,d);if(!f.ok)return c(f.code,f.message,l[f.code]??400);let p=f.request;try{switch(p.op){case`publish`:return s(await this.#t.publish(p.channel,p.entry,p.options,p.idleMs));case`history`:return s(await this.#t.history(p.channel,p.after,p.limit));case`presence-set`:return await this.#t.presenceSet(p.channel,p.member,p.ttlMs,p.graceMs),s({});case`presence-remove`:return s({removed:await this.#t.presenceRemove(p.channel,p.id)});case`presence-list`:return s({members:await this.#t.presenceList(p.channel)});case`presence-touch`:return s({missing:await this.#t.presenceTouch(p.channel,p.ids,p.ttlMs)});case`presence-reap`:return s({members:await this.#t.presenceReap(p.channel)})}}catch(t){return t instanceof e?c(t.code,t.message,l[t.code]??500):(console.error(`[realtime] channel object`,t),c(`unavailable`,`the channel object failed`,500))}}async#r(n,r){if(n.headers.get(`upgrade`)?.toLowerCase()!==`websocket`)return c(`bad_frame`,`expected a WebSocket upgrade`,426);let i=r.searchParams.get(`c`);if(!t(i))return c(`bad_channel`,`bad channel name`,400);let a=d(r);if(a===`bad`)return c(`bad_frame`,`bad after`,400);let{client:o,server:s}=this.#n.createPair();this.#e.acceptWebSocket(s);try{await this.#t.attach(s,i,a)}catch(t){try{s.close(1011,`attach failed`)}catch{}return t instanceof e?c(t.code,t.message,l[t.code]??500):(console.error(`[realtime] channel object`,t),c(`unavailable`,`the channel object failed`,500))}return this.#n.upgrade(o)}webSocketMessage(e,t){if(t===`ping`)try{e.send(`pong`)}catch{this.#t.detach(e)}}webSocketClose(e,t,n){this.#t.detach(e);try{e.close(u(t),n.slice(0,100))}catch{}}webSocketError(e){this.#t.detach(e);try{e.close(1011,`error`)}catch{}}async alarm(){await this.#t.alarm()}};export{f as RealtimeChannelObject};
|
|
2
|
+
//# sourceMappingURL=object.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"object.js","names":["workersPlatform","Pair","RealtimeError","pair","client","server","JSON_HEADERS","reply","body","refusal","status","STATUS","replyCode","code","readAfter","value","url","RealtimeChannelObject","#state","state","#platform","platform","#core","ChannelCore","request","#subscribe","operation","isOperation","op","text","parsed","parseRequest","call","error","channel","isChannelName","after","message","socket","reason"],"sources":["../../../src/cloudflare/object.ts"],"sourcesContent":["import { RealtimeError } from \"../errors\";\nimport { isChannelName } from \"../protocol\";\nimport { ChannelCore } from \"./core\";\nimport type { ChannelObjectState, ChannelSocket } from \"./types\";\nimport { isOperation, MAX_BODY_BYTES, parseRequest, WIRE_VERSION } from \"./wire\";\n\n/**\n * The pieces of the platform the object creates: a WebSocket pair, the upgrade response and the auto-response pair. The default\n * reads the Workers globals; the unit tests pass their own.\n * @internal\n */\nexport interface ObjectPlatform {\n createPair(): { readonly client: unknown; readonly server: ChannelSocket };\n upgrade(client: unknown): Response;\n autoResponse(): object | undefined;\n}\n\nfunction workersPlatform(): ObjectPlatform {\n return {\n createPair() {\n const Pair: unknown = Reflect.get(globalThis, \"WebSocketPair\");\n if (typeof Pair !== \"function\")\n throw new RealtimeError(\"unavailable\", \"WebSocketPair is missing: this is not a Worker\");\n const pair: unknown = Reflect.construct(Pair, []);\n const client: unknown = Reflect.get(Object(pair), \"0\");\n const server: unknown = Reflect.get(Object(pair), \"1\");\n if (\n typeof server !== \"object\" ||\n server === null ||\n typeof Reflect.get(server, \"send\") !== \"function\" ||\n typeof Reflect.get(server, \"close\") !== \"function\"\n )\n throw new TypeError(\"WebSocketPair returned an unexpected socket\");\n return { client, server: server as ChannelSocket };\n },\n upgrade(client) {\n // `webSocket` is a Workers extension of `ResponseInit`; the standard type does not have it.\n return new Response(null, { status: 101, webSocket: client } as unknown as ResponseInit);\n },\n autoResponse() {\n const Pair: unknown = Reflect.get(globalThis, \"WebSocketRequestResponsePair\");\n return typeof Pair === \"function\"\n ? (Reflect.construct(Pair, [\"ping\", \"pong\"]) as object)\n : undefined;\n },\n };\n}\n\nconst JSON_HEADERS = { \"content-type\": \"application/json\" } as const;\n\nfunction reply(body: unknown, status = 200): Response {\n return new Response(JSON.stringify(body), { status, headers: JSON_HEADERS });\n}\n\nfunction refusal(code: string, message: string, status: number): Response {\n return reply({ error: { code, message } }, status);\n}\n\nconst STATUS: Readonly<Record<string, number>> = {\n bad_frame: 400,\n bad_channel: 400,\n too_large: 413,\n limit: 429,\n conflict: 409,\n};\n\n/** A close code `close()` may send: the reserved 1005, 1006 and 1015 never go on the wire. */\nfunction replyCode(code: number): number {\n return code >= 1000 && code < 5000 && code !== 1005 && code !== 1006 && code !== 1015\n ? code\n : 1000;\n}\n\nfunction readAfter(url: URL): number | undefined | \"bad\" {\n const value = url.searchParams.get(\"after\");\n if (value === null) return undefined;\n if (!/^\\d{1,16}$/.test(value)) return \"bad\";\n return Number(value);\n}\n\n/**\n * The Durable Object that owns one channel: the single writer of its sequence numbers, its retained history, its idempotency\n * keys and its presence set, and the fan-out to the Workers that subscribed. Re-export it from your Worker and bind it as a\n * SQLite-backed Durable Object; `cloudflareAdapter` talks to it.\n *\n * It keeps everything in Durable Object storage, so it can be evicted and restart at any time, and it uses the WebSocket\n * Hibernation API, so a channel nobody publishes to costs nothing while its subscribers stay connected. An alarm reaps expired\n * presence members and expired retention.\n *\n * @example\n * // worker.ts\n * export { RealtimeChannelObject } from \"@shadow-js/realtime/cloudflare\";\n *\n * // wrangler.toml\n * // [[durable_objects.bindings]]\n * // name = \"CHANNELS\"\n * // class_name = \"RealtimeChannelObject\"\n * // [[migrations]]\n * // tag = \"v1\"\n * // new_sqlite_classes = [\"RealtimeChannelObject\"]\n * @since 1.0.0\n */\nexport class RealtimeChannelObject {\n readonly #state: ChannelObjectState;\n readonly #core: ChannelCore;\n readonly #platform: ObjectPlatform;\n\n /**\n * @param state The Durable Object state the runtime passes.\n * @param env The Worker's bindings (unused).\n * @param platform The platform seams (the runtime passes none).\n */\n constructor(state: ChannelObjectState, env?: unknown, platform?: ObjectPlatform) {\n void env;\n this.#state = state;\n this.#platform = platform ?? workersPlatform();\n this.#core = new ChannelCore({ storage: state.storage });\n this.#core.adopt(state.getWebSockets());\n const pair = this.#platform.autoResponse();\n if (pair !== undefined) state.setWebSocketAutoResponse?.(pair);\n }\n\n /**\n * Handles a request from `cloudflareAdapter`: `POST /v1/<operation>` with a JSON body, or `GET /v1/ws` to subscribe.\n *\n * @param request The request.\n * @returns The response.\n */\n async fetch(request: Request): Promise<Response> {\n const url = new URL(request.url);\n if (url.pathname === `/v${WIRE_VERSION}/ws`) return this.#subscribe(request, url);\n const operation = /^\\/v(\\d+)\\/([a-z-]+)$/.exec(url.pathname);\n if (operation?.[1] !== String(WIRE_VERSION) || !isOperation(operation[2] ?? \"\"))\n return refusal(\"bad_frame\", \"unknown operation\", 404);\n const op = operation[2] ?? \"\";\n if (!isOperation(op)) return refusal(\"bad_frame\", \"unknown operation\", 404);\n if (request.method !== \"POST\") return refusal(\"bad_frame\", \"use POST\", 405);\n const declared = Number(request.headers.get(\"content-length\") ?? 0);\n if (declared > MAX_BODY_BYTES) return refusal(\"too_large\", \"the body is too large\", 413);\n const text = await request.text();\n if (text.length > MAX_BODY_BYTES) return refusal(\"too_large\", \"the body is too large\", 413);\n let body: unknown;\n try {\n body = JSON.parse(text);\n } catch {\n return refusal(\"bad_frame\", \"the body is not JSON\", 400);\n }\n const parsed = parseRequest(op, body);\n if (!parsed.ok) return refusal(parsed.code, parsed.message, STATUS[parsed.code] ?? 400);\n const call = parsed.request;\n try {\n switch (call.op) {\n case \"publish\":\n return reply(\n await this.#core.publish(call.channel, call.entry, call.options, call.idleMs),\n );\n case \"history\":\n return reply(await this.#core.history(call.channel, call.after, call.limit));\n case \"presence-set\":\n await this.#core.presenceSet(call.channel, call.member, call.ttlMs, call.graceMs);\n return reply({});\n case \"presence-remove\":\n return reply({ removed: await this.#core.presenceRemove(call.channel, call.id) });\n case \"presence-list\":\n return reply({ members: await this.#core.presenceList(call.channel) });\n case \"presence-touch\":\n return reply({\n missing: await this.#core.presenceTouch(call.channel, call.ids, call.ttlMs),\n });\n case \"presence-reap\":\n return reply({ members: await this.#core.presenceReap(call.channel) });\n }\n } catch (error) {\n if (error instanceof RealtimeError)\n return refusal(error.code, error.message, STATUS[error.code] ?? 500);\n console.error(\"[realtime] channel object\", error);\n return refusal(\"unavailable\", \"the channel object failed\", 500);\n }\n }\n\n async #subscribe(request: Request, url: URL): Promise<Response> {\n if (request.headers.get(\"upgrade\")?.toLowerCase() !== \"websocket\")\n return refusal(\"bad_frame\", \"expected a WebSocket upgrade\", 426);\n const channel = url.searchParams.get(\"c\");\n if (!isChannelName(channel)) return refusal(\"bad_channel\", \"bad channel name\", 400);\n const after = readAfter(url);\n if (after === \"bad\") return refusal(\"bad_frame\", \"bad after\", 400);\n const { client, server } = this.#platform.createPair();\n this.#state.acceptWebSocket(server);\n try {\n await this.#core.attach(server, channel, after);\n } catch (error) {\n try {\n server.close(1011, \"attach failed\");\n } catch {\n // Already closed.\n }\n if (error instanceof RealtimeError)\n return refusal(error.code, error.message, STATUS[error.code] ?? 500);\n console.error(\"[realtime] channel object\", error);\n return refusal(\"unavailable\", \"the channel object failed\", 500);\n }\n return this.#platform.upgrade(client);\n }\n\n /**\n * Runtime hook: a message on a subscribe socket. The only message is the keep-alive `ping`, which the runtime normally answers\n * itself without waking the object.\n *\n * @param socket The socket.\n * @param message The message.\n */\n webSocketMessage(socket: ChannelSocket, message: string | ArrayBuffer): void {\n if (message === \"ping\") {\n try {\n socket.send(\"pong\");\n } catch {\n this.#core.detach(socket);\n }\n }\n }\n\n /**\n * Runtime hook: a subscribe socket closed.\n *\n * @param socket The socket.\n * @param code The close code the peer sent.\n * @param reason The reason the peer sent.\n */\n webSocketClose(socket: ChannelSocket, code: number, reason: string): void {\n this.#core.detach(socket);\n try {\n socket.close(replyCode(code), reason.slice(0, 100));\n } catch {\n // Already closed.\n }\n }\n\n /**\n * Runtime hook: a subscribe socket failed.\n *\n * @param socket The socket.\n */\n webSocketError(socket: ChannelSocket): void {\n this.#core.detach(socket);\n try {\n socket.close(1011, \"error\");\n } catch {\n // Already closed.\n }\n }\n\n /**\n * Runtime hook: the alarm. Reaps expired presence members (one `leave` each), expired idempotency keys and expired retained\n * messages, and sets the next alarm.\n */\n async alarm(): Promise<void> {\n await this.#core.alarm();\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{byteLength as e,isChannelName as t,isEventName as n,isIdempotencyKey as r}from"../protocol.js";const i=122880,a=16384,o=1e4,s=1e4,c=864e5,l=262144,u=/* @__PURE__ */ new Set([`publish`,`history`,`presence-set`,`presence-remove`,`presence-list`,`presence-touch`,`presence-reap`]);function d(e){return u.has(e)}function f(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function p(e,t=2**53-1){return typeof e==`number`&&Number.isInteger(e)&&e>=0&&e<=t}function m(e,t=`bad_frame`){return{ok:!1,code:t,message:e}}function h(t){if(!f(t))return;let{id:n,u:r,s:i,v:a}=t;if(typeof n!=`string`||n.length===0||n.length>256||r!==void 0&&(typeof r!=`string`||r.length>256)||typeof a!=`number`||!Number.isInteger(a)||a<1)return;let o=i===void 0?null:i,s=JSON.stringify(o);if(!(s===void 0||e(s)>16384))return r===void 0?{id:n,s:o,v:a}:{id:n,u:r,s:o,v:a}}function g(e){if(!f(e))return;let{event:t,data:r,publisher:i,user:a}=e;if(n(t)&&typeof r==`string`&&!(i!==void 0&&(typeof i!=`string`||i.length>256))&&!(a!==void 0&&(typeof a!=`string`||a.length>256)))return{event:t,data:r,...i===void 0?{}:{publisher:i},...a===void 0?{}:{user:a}}}function _(e){if(!f(e))return;let{history:t,historyTtlMs:n,key:i,idempotencyTtlMs:a}=e;if(p(t,1e4)&&p(n,316224e5)&&p(a,864e5)&&(i===void 0||r(i)))return i===void 0?{history:t,historyTtlMs:n,idempotencyTtlMs:a}:{history:t,historyTtlMs:n,key:i,idempotencyTtlMs:a}}function v(n,r){if(!f(r))return m(`the body must be an object`);let{channel:i}=r;if(!t(i))return m(`bad channel name`,`bad_channel`);switch(n){case`publish`:{let t=g(r.entry),a=_(r.options),o=r.idleMs??0;return t===void 0?m(`bad entry`):a===void 0?m(`bad options`):p(o,31536e7)?e(t.data)+e(t.event)>122880?m(`the message is too large for one stored value`,`too_large`):{ok:!0,request:{op:n,channel:i,entry:t,options:a,idleMs:o}}:m(`bad idleMs`)}case`history`:{let{after:e,limit:t}=r;return!p(e)||!p(t,1e5)?m(`bad after or limit`):{ok:!0,request:{op:n,channel:i,after:e,limit:t}}}case`presence-set`:{let e=h(r.member),{ttlMs:t}=r,a=r.graceMs??0;return e===void 0?m(`bad member`):!p(t,864e5)||t===0?m(`bad ttlMs`):p(a,36e5)?{ok:!0,request:{op:n,channel:i,member:e,ttlMs:t,graceMs:a}}:m(`bad graceMs`)}case`presence-remove`:{let{id:e}=r;return typeof e!=`string`||e.length===0||e.length>256?m(`bad id`):{ok:!0,request:{op:n,channel:i,id:e}}}case`presence-touch`:{let{ids:e,ttlMs:t}=r;return!p(t,864e5)||t===0?m(`bad ttlMs`):!Array.isArray(e)||e.length>1e4||!e.every(e=>typeof e==`string`&&e.length>0&&e.length<=256)?m(`bad ids`):{ok:!0,request:{op:n,channel:i,ids:e,ttlMs:t}}}case`presence-list`:case`presence-reap`:return{ok:!0,request:{op:n,channel:i}}}}function y(e){let t;try{t=JSON.parse(e)}catch{return}if(f(t))switch(t.t){case`ready`:{let{head:e,oldest:n}=t;return p(e)&&p(n)?{t:`ready`,head:e,oldest:n}:void 0}case`snap`:{let e=t.members;if(!Array.isArray(e))return;let n=[];for(let t of e){let e=h(t);if(e===void 0)return;n.push(e)}return{t:`snap`,members:n}}case`msg`:{let e=b(t.m);return e===void 0?void 0:{t:`msg`,m:e}}case`pres`:{let e=h(t.member),n=t.op;return e===void 0||n!==`join`&&n!==`set`&&n!==`leave`?void 0:{t:`pres`,op:n,member:e}}case`more`:return{t:`more`};case`bye`:return{t:`bye`,why:typeof t.why==`string`?t.why:``};default:return}}function b(e){if(!f(e))return;let{seq:t,event:n,data:r,at:i,publisher:a,user:o}=e;if(!(!p(t)||t<1||typeof n!=`string`||typeof r!=`string`)&&p(i)&&(a===void 0||typeof a==`string`)&&(o===void 0||typeof o==`string`))return{seq:t,event:n,data:r,at:i,...a===void 0?{}:{publisher:a},...o===void 0?{}:{user:o}}}function x(e){return p(e)}function S(e){return`h:${String(e).padStart(16,`0`)}`}function C(e){return Number(e.slice(2))}export{l as MAX_BODY_BYTES,i as MAX_ENTRY_BYTES,s as MAX_HISTORY,o as MAX_MEMBERS,a as MAX_STATE_BYTES,c as MAX_TTL_MS,y as decodeLinkFrame,S as historyKey,d as isOperation,x as isWholeNumber,h as parseMember,v as parseRequest,b as parseStored,C as seqOfKey};
|
|
2
|
+
//# sourceMappingURL=wire.js.map
|