@rsc-kit/core 0.27.0 → 0.28.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/dist/broadcast.d.ts +22 -0
- package/dist/broadcast.js +109 -0
- package/dist/broadcast.js.map +1 -0
- package/dist/changed.d.ts +82 -26
- package/dist/changed.js +252 -26
- package/dist/changed.js.map +1 -1
- package/dist/compress.js +8 -1
- package/dist/compress.js.map +1 -1
- package/dist/events.d.ts +21 -3
- package/dist/events.js +31 -6
- package/dist/events.js.map +1 -1
- package/dist/js/refreshOn.d.ts +9 -1
- package/dist/js/refreshOn.js +12 -1
- package/dist/js/refreshOn.js.map +1 -1
- package/dist/testHost.d.ts +15 -4
- package/dist/testHost.js +2 -11
- package/dist/testHost.js.map +1 -1
- package/package.json +2 -2
package/dist/changed.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"changed.js","sourceRoot":"","sources":["../src/changed.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Names: a name for data a page depends on, so whatever changes it can say so\n * from anywhere - a webhook, a job, another visitor's action - and every open\n * tab showing it refreshes.\n *\n * // a section says what it depends on\n * export default section('repos', Repos, { refreshOn: ({ params }) => [`team:${params.team}:repos`] })\n *\n * // and whatever changes it says so\n * changed(`team:${team}:repos`) // here, from a route.ts or an action\n * rsckit.Changed(ctx, \"team:1:repos\") // or the backend, from its webhook\n * Rsc::changed(\"team:$team:repos\"); // Laravel\n *\n * A name has a version: a number that moves whenever something says the name\n * changed. Nothing else - not what changed, not the data. A page learns the\n * versions at render; one stream per open tab watches them; when one moves,\n * the sections carrying that name ask for themselves again, the way refresh()\n * does. Data is never pushed, which is what keeps this working on a host that\n * cannot hold state: a version is a read.\n *\n * Where versions live is the source's business. Without a backend they are in\n * this process, which is one instance. With one, the backend keeps them - in\n * its cache or a table, shared by every instance it runs - and answers\n * `__rsc.changed`: given the versions a watcher holds, the ones that differ now.\n * A backend that can wait holds the call until one does; one that cannot\n * answers at once, and the watcher asks again on its interval. The protocol\n * is the same either way, and so is the page.\n */\n\nimport { KEEPALIVE_MS, frame } from \"./events.js\";\nimport { HEADER } from \"./headers.js\";\n\n/** The reserved name a backend answers name versions on. */\nexport const CHANGED_FUNCTION = \"__rsc.changed\";\n\n/** What `__rsc.changed` is asked. */\nexport interface ChangedQuery {\n /** The versions the asker holds. A name never changed is 0; -1 asks for every current version. */\n since: Record<string, number>;\n /** How long the backend may hold the call waiting for one to differ, in ms. It may answer at once. */\n wait?: number;\n}\n\n/** What `__rsc.changed` answers: the versions among `since` that differ now. */\nexport interface ChangedAnswer {\n versions: Record<string, number>;\n}\n\n/**\n * Where name versions are read from, and - when they live here - bumped.\n *\n * `changed` answers the versions among `since` that differ now, and may wait\n * up to `wait` ms for one to. Nothing else is required of it, which is why a\n * backend's cache, a table, this process's memory, or a pub/sub that answers\n * the moment a name moves all fit behind it.\n */\nexport interface VersionSource {\n changed(\n since: Record<string, number>,\n wait: number,\n ): Promise<Record<string, number>>;\n /** Say these names changed. Only a source that holds its own versions can. */\n bump(names: string[]): void | Promise<void>;\n}\n\n/** Versions kept in this process: the source without a backend, and the one tests use. */\nexport function memoryVersions(): VersionSource {\n const versions = new Map<string, number>();\n const waiters = new Set<() => void>();\n\n const differing = (since: Record<string, number>): Record<string, number> => {\n const out: Record<string, number> = {};\n\n for (const [name, held] of Object.entries(since)) {\n const current = versions.get(name) ?? 0;\n\n if (current !== held) out[name] = current;\n }\n\n return out;\n };\n\n return {\n async changed(since, wait) {\n const now = differing(since);\n\n if (Object.keys(now).length > 0 || wait <= 0) return now;\n\n await new Promise<void>((resolve) => {\n const done = () => {\n clearTimeout(timer);\n waiters.delete(done);\n resolve();\n };\n const timer = setTimeout(done, wait);\n\n // A test or a build must not be held open by a watcher's wait.\n (timer as { unref?: () => void }).unref?.();\n waiters.add(done);\n });\n\n return differing(since);\n },\n bump(names) {\n for (const name of names)\n versions.set(name, (versions.get(name) ?? 0) + 1);\n for (const wake of [...waiters]) wake();\n },\n };\n}\n\ntype HostFn = (name: string, ...args: unknown[]) => Promise<unknown>;\n\n/**\n * Versions kept by the backend, read through `__rsc.changed`.\n *\n * A backend that has not got the function - an adapter from before names, or\n * a host of plain rpc functions - is answered from this process instead,\n * found out on the first ask and remembered, so a page with names still works\n * on it: one instance, versions bumped by `changed()` here.\n */\nexport function backendVersions(call: HostFn): VersionSource {\n let local: VersionSource | null = null;\n\n // A backend's \"no such function\", or the test host's \"no handler for\".\n const unknown = (error: unknown): boolean =>\n /no host function named|has no handler for/i.test(\n error instanceof Error ? error.message : String(error),\n );\n\n return {\n async changed(since, wait) {\n if (local) return local.changed(since, wait);\n\n try {\n const answer = (await call(CHANGED_FUNCTION, {\n since,\n wait,\n } satisfies ChangedQuery)) as Partial<ChangedAnswer> | null;\n\n return answer?.versions ?? {};\n } catch (error) {\n if (!unknown(error)) throw error;\n\n local = memoryVersions();\n\n return local.changed(since, wait);\n }\n },\n bump(names) {\n if (local) return local.bump(names);\n\n throw new Error(\n \"changed() bumps versions this process keeps, and a backend keeps these: say the name changed there - \" +\n \"rsckit.Changed(ctx, ...) in Go, Rsc::changed(...) in Laravel.\",\n );\n },\n };\n}\n\n/** Runs one statement with positional parameters and resolves to its rows. */\nexport type SqlQuery = (\n text: string,\n params: unknown[],\n) => Promise<readonly Record<string, unknown>[]>;\n\nexport interface SqlVersionsOptions {\n /**\n * Your driver, as a function of SQL text and parameters:\n *\n * query: (text, params) => sql.unsafe(text, params) // postgres.js, Bun.sql\n * query: async (text, params) => (await pool.query(text, params)).rows // node-postgres\n */\n query: SqlQuery;\n /** The table: `CREATE TABLE rsc_versions (name TEXT PRIMARY KEY, version BIGINT NOT NULL)`. */\n table?: string;\n /** How the n-th parameter is written (1-based). Default `?`; Postgres is `(n) => '$' + n`. */\n placeholder?: (n: number) => string;\n /**\n * Called once with a function that wakes every waiting ask, for a database\n * that can say the moment a version moved - Postgres's LISTEN:\n *\n * listen: (wake) => sql.listen('rsc_versions', wake)\n *\n * Without it, a waiting ask reads the table every `poll` ms.\n */\n listen?: (wake: () => void) => unknown;\n /**\n * Run after every bump, to tell the other instances - Postgres's NOTIFY:\n *\n * notify: () => sql.notify('rsc_versions', '')\n */\n notify?: () => unknown;\n /** How often a waiting ask reads the table. Default 1000ms. */\n poll?: number;\n}\n\n/**\n * Versions in a table every instance shares: the web servers, and the worker\n * that finishes a job and says so. The same table and columns Go's\n * `SQLVersions` uses, so a Go service and a JavaScript worker can share one.\n *\n * installVersionSource(sqlVersions({\n * query: (text, params) => sql.unsafe(text, params),\n * placeholder: (n) => '$' + n,\n * listen: (wake) => sql.listen('rsc_versions', wake),\n * notify: () => sql.notify('rsc_versions', ''),\n * }))\n *\n * Written with an UPDATE and, the first time, an INSERT - two statements any\n * SQL dialect has - rather than an upsert each spells differently. Takes the\n * app's own driver, so the engine has no database dependency of its own.\n */\nexport function sqlVersions(options: SqlVersionsOptions): VersionSource {\n const { query } = options;\n const table = options.table ?? \"rsc_versions\";\n const mark = options.placeholder ?? (() => \"?\");\n\n return createVersions(\n {\n async read(names) {\n const rows = await query(\n `SELECT name, version FROM ${table} WHERE name IN (${names.map((_, i) => mark(i + 1)).join(\", \")})`,\n names,\n );\n\n // A BIGINT comes back as a string from most drivers.\n return Object.fromEntries(\n rows.map((row) => [String(row.name), Number(row.version)]),\n );\n },\n async bump(names) {\n const update = `UPDATE ${table} SET version = version + 1 WHERE name = ${mark(1)}`;\n const insert = `INSERT INTO ${table} (name, version) VALUES (${mark(1)}, 1)`;\n\n for (const name of names) {\n // The row count is not something every driver reports the same way,\n // so ask: a name with no row yet gets one.\n const [exists] = await query(\n `SELECT 1 AS found FROM ${table} WHERE name = ${mark(1)}`,\n [name],\n );\n\n if (exists) {\n await query(update, [name]);\n continue;\n }\n\n try {\n await query(insert, [name]);\n } catch {\n // Another instance inserted it first: bump that one.\n await query(update, [name]);\n }\n }\n },\n listen: options.listen,\n notify: options.notify,\n },\n { poll: options.poll },\n );\n}\n\n/**\n * Where versions are kept, as the app writes it for its own database: the\n * three things rsc-kit cannot know about your storage, and nothing else.\n * `createVersions` does the rest - comparing, waiting, waking.\n *\n * // Drizzle\n * read: async (names) => Object.fromEntries(\n * (await db.select().from(rscVersions).where(inArray(rscVersions.name, names)))\n * .map((r) => [r.name, Number(r.version)])),\n * bump: async (names) => { for (const name of names) await db.insert(rscVersions)\n * .values({ name, version: 1 })\n * .onConflictDoUpdate({ target: rscVersions.name, set: { version: sql`${rscVersions.version} + 1` } }) },\n *\n * // Redis\n * read: async (names) => Object.fromEntries((await redis.mget(names.map(k)))\n * .map((v, i) => [names[i], Number(v ?? 0)])),\n * bump: async (names) => { await Promise.all(names.map((n) => redis.incr(k(n)))) },\n */\nexport interface VersionStore {\n /** The current version of each name asked for. A name it has never seen may be left out: it is 0. */\n read(names: string[]): Promise<Record<string, number>>;\n /** Move each name's version - by one, or to anything new: watchers compare, they do not count. */\n bump(names: string[]): Promise<void>;\n /**\n * Called once with a function that wakes every waiting ask, when the store\n * can say the moment a version moved - Postgres LISTEN, Redis SUBSCRIBE.\n * Without it a waiting ask reads again every `poll` ms.\n */\n listen?(wake: () => void): unknown;\n /** Run after a bump, to tell the other instances listening: Postgres NOTIFY, Redis PUBLISH. */\n notify?(): unknown;\n}\n\n/**\n * A version source over any store: SQL through any driver or ORM - Drizzle,\n * Prisma, Kysely, Knex - or Redis, or anything with a read and an increment.\n *\n * installVersionSource(createVersions({ read, bump, listen, notify }))\n *\n * rsc-kit owns the rest: an ask waits up to the time it was given for a\n * version to differ, reading every `poll` ms (1000 by default) or the moment\n * `listen` wakes it, and a bump in this process wakes this process's asks at\n * once whatever the store.\n */\nexport function createVersions(\n store: VersionStore,\n options: { poll?: number } = {},\n): VersionSource {\n const poll = options.poll ?? 1_000;\n const waiters = new Set<() => void>();\n const wakeAll = () => {\n for (const wake of [...waiters]) wake();\n };\n\n if (store.listen) void Promise.resolve(store.listen(wakeAll)).catch(() => {});\n\n const read = async (names: string[]): Promise<Record<string, number>> => {\n if (names.length === 0) return {};\n\n const found = await store.read(names);\n\n return Object.fromEntries(\n names.map((name) => [name, Number(found[name] ?? 0)]),\n );\n };\n\n return {\n async changed(since, wait) {\n const names = Object.keys(since);\n const deadline = Date.now() + Math.max(0, wait);\n\n for (;;) {\n const versions = await read(names);\n const differ: Record<string, number> = {};\n\n for (const name of names)\n if (versions[name] !== since[name])\n differ[name] = versions[name] ?? 0;\n\n const remaining = deadline - Date.now();\n\n if (Object.keys(differ).length > 0 || remaining <= 0) return differ;\n\n await new Promise<void>((resolve) => {\n const done = () => {\n clearTimeout(timer);\n waiters.delete(done);\n resolve();\n };\n const timer = setTimeout(done, Math.min(poll, remaining));\n\n (timer as { unref?: () => void }).unref?.();\n waiters.add(done);\n });\n }\n },\n async bump(names) {\n const unique = [...new Set(names)];\n\n if (unique.length === 0) return;\n\n await store.bump(unique);\n wakeAll();\n if (store.notify) await store.notify();\n },\n };\n}\n\n/** The parts of a Postgres client `postgresVersions` uses: postgres.js and `Bun.sql` both have them. */\nexport interface PostgresClient {\n unsafe(\n text: string,\n params?: unknown[],\n ): PromiseLike<readonly Record<string, unknown>[]>;\n /** postgres.js has it; with it, a waiting ask hears another instance at once. */\n listen?(channel: string, onNotify: () => void): unknown;\n notify?(channel: string, payload: string): unknown;\n}\n\n/**\n * Versions in Postgres, from the client the app already has:\n *\n * installVersionSource(postgresVersions(sql))\n *\n * `sqlVersions` with Postgres's placeholders, and - when the client can\n * LISTEN, as postgres.js can - a change on one instance wakes the waiting asks\n * on every other at once, rather than on their next read of the table.\n * Without it (`Bun.sql`), the table is read once a second while an ask waits.\n *\n * CREATE TABLE rsc_versions (name TEXT PRIMARY KEY, version BIGINT NOT NULL)\n */\nexport function postgresVersions(\n sql: PostgresClient,\n options: { table?: string; channel?: string; poll?: number } = {},\n): VersionSource {\n const channel = options.channel ?? \"rsc_versions\";\n const listen = sql.listen?.bind(sql);\n const notify = sql.notify?.bind(sql);\n\n return sqlVersions({\n query: async (text, params) => await sql.unsafe(text, params),\n placeholder: (n) => \"$\" + n,\n table: options.table,\n poll: options.poll,\n listen: listen ? (wake) => listen(channel, wake) : undefined,\n notify: listen && notify ? () => notify(channel, \"\") : undefined,\n });\n}\n\nconst SOURCE = Symbol.for(\"rsc-kit.version-source\");\nconst BACKEND_SOURCE = Symbol.for(\"rsc-kit.backend-version-source\");\nconst LOCAL_SOURCE = Symbol.for(\"rsc-kit.local-version-source\");\nconst globals = globalThis as Record<symbol, unknown>;\n\n/**\n * Install where versions are read from - a table, Redis, any store - for this\n * process; `null` takes it out again.\n *\n * The app's choice wins over the backend's: with a Go or Laravel backend that\n * writes its versions to a store this process can read too, reading it here\n * means watching costs the backend nothing.\n */\nexport function installVersionSource(source: VersionSource | null): void {\n globals[SOURCE] = source;\n}\n\n/** The engine's, when a backend is installed: its versions, read through it. Not for apps. */\nexport function installBackendVersionSource(\n source: VersionSource | null,\n): void {\n globals[BACKEND_SOURCE] = source;\n}\n\n/** The source in use: the app's, else the backend's, else this process's own. */\nexport function versionSource(): VersionSource {\n return (\n (globals[SOURCE] as VersionSource | null | undefined) ??\n (globals[BACKEND_SOURCE] as VersionSource | null | undefined) ??\n ((globals[LOCAL_SOURCE] as VersionSource | undefined) ??= memoryVersions())\n );\n}\n\n/**\n * Say these names changed, so every open tab showing them refreshes.\n *\n * export async function POST(request: Request) { // a webhook, in a route.ts\n * const { team } = await request.json()\n * changed(`team:${team}:repos`)\n * return new Response(null, { status: 204 })\n * }\n *\n * For an app that keeps its versions here. With a backend the versions are\n * its, and so is the call: `rsckit.Changed`, `Rsc::changed`.\n */\nexport async function changed(...names: string[]): Promise<void> {\n await versionSource().bump(names);\n}\n\n// ── Tokens ───────────────────────────────────────────────────────────────────\n//\n// A tab may watch exactly the names its page was rendered with. Rather than\n// remembering what each tab was given - state, which a Worker cannot keep\n// between requests - every name goes to the client signed, and comes back\n// with its signature. Verified, not looked up, so a watch request is\n// stateless, and a browser cannot make up a name to watch.\n\n/** Thrown when a production server has no key to sign names with. */\nexport class MissingSigningSecret extends Error {\n constructor() {\n super(\n \"refreshOn needs a signing secret in production, the same on every instance: set RSC_SIGNING_SECRET \" +\n \"to a long random string. Without one, each instance would sign with its own random key, and a tab \" +\n \"served by one would be refused by the next.\",\n );\n this.name = \"MissingSigningSecret\";\n }\n}\n\n// On the global rather than in this module: an app imports this file through\n// its own copy of the package, and a key configured there must be the key the\n// engine's copy signs and verifies with.\nconst KEY = Symbol.for(\"rsc-kit.signing-key\");\n\ninterface Keying {\n secret: string | null;\n keyed: Promise<CryptoKey> | null;\n signatures: Map<string, Promise<string>>;\n}\n\nfunction keying(): Keying {\n return ((globals[KEY] as Keying | undefined) ??= {\n secret: null,\n keyed: null,\n signatures: new Map(),\n });\n}\n\nconst env = (name: string): string | undefined =>\n (globalThis as { process?: { env?: Record<string, string | undefined> } })\n .process?.env?.[name] || undefined;\n\nconst production = (): boolean => env(\"NODE_ENV\") === \"production\";\n\n/**\n * The secret names are signed with, when it does not come from the\n * environment. Normally it does: `RSC_SIGNING_SECRET`, the same on every\n * instance, or a tab served by one is refused by another.\n *\n * Its own secret, not the backend's host-call secret: that one authorises\n * calls between the renderer and a backend, and an app with no backend has\n * no reason to have it. Two jobs, two keys - one leaking does not hand out\n * the other.\n */\nexport function configureChanged(options: { secret?: string | null }): void {\n const state = keying();\n\n state.secret = options.secret ?? null;\n state.keyed = null;\n state.signatures.clear();\n}\n\n/** The secret in use: configured, or from the environment; null when there is none. */\nfunction secretInUse(): string | null {\n const state = keying();\n\n return state.secret ?? env(\"RSC_SIGNING_SECRET\") ?? null;\n}\n\n/**\n * Refuse, in production, to sign with a key nobody else has.\n *\n * A random key is right for one process - a dev server - and silently wrong\n * for two: every tab served by one instance is refused by the next, and never\n * refreshes. The server calls this on its first request when the app uses\n * refreshOn, so the misconfiguration is an error at once rather than a page\n * that quietly stops updating.\n */\nexport function assertSigningSecret(): void {\n if (!secretInUse() && production()) throw new MissingSigningSecret();\n}\n\nlet warnedRandom = false;\n\nasync function key(): Promise<CryptoKey> {\n const state = keying();\n\n return (state.keyed ??= (async () => {\n const secret = secretInUse();\n let raw: Uint8Array<ArrayBuffer>;\n\n if (secret) raw = new TextEncoder().encode(secret);\n else if (production()) throw new MissingSigningSecret();\n else {\n if (!warnedRandom) {\n warnedRandom = true;\n console.warn(\n \"[rsc-kit] refreshOn is signing with a random key, which is fine for one dev server. \" +\n \"Set RSC_SIGNING_SECRET before running more than one instance.\",\n );\n }\n\n raw = crypto.getRandomValues(new Uint8Array(32));\n }\n\n return await crypto.subtle.importKey(\n \"raw\",\n raw,\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"],\n );\n })());\n}\n\n/** A name's signature: the first 16 bytes of an HMAC, base64url, which is what rides in a url. */\nexport function sign(name: string): Promise<string> {\n const { signatures } = keying();\n let pending = signatures.get(name);\n\n if (!pending) {\n pending = (async () => {\n const mac = await crypto.subtle.sign(\n \"HMAC\",\n await key(),\n new TextEncoder().encode(name),\n );\n\n return base64url(new Uint8Array(mac).slice(0, 16));\n })();\n\n // Bounded: a name with a visitor's id in it is a new entry per visitor.\n if (signatures.size > 10_000) signatures.clear();\n signatures.set(name, pending);\n }\n\n return pending;\n}\n\nasync function verify(name: string, signature: string): Promise<boolean> {\n const expected = await sign(name);\n\n if (expected.length !== signature.length) return false;\n\n let differ = 0;\n\n for (let i = 0; i < expected.length; i++)\n differ |= expected.charCodeAt(i) ^ signature.charCodeAt(i);\n\n return differ === 0;\n}\n\nfunction base64url(bytes: Uint8Array): string {\n let text = \"\";\n\n for (const byte of bytes) text += String.fromCharCode(byte);\n\n return btoa(text).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n/** What a page hands the client for one name: its version at render, and its signature. */\nexport type SignedName = [version: number, signature: string];\n\n// ── Watching ─────────────────────────────────────────────────────────────────\n//\n// One stream per open tab, and one ask of the source per process: every\n// stream's names are asked about together, on an interval, and a version that\n// moved is written to every stream holding its name. A thousand tabs on a\n// long-lived server cost the backend one call per interval, not a thousand.\n// On a host that answers each request in its own isolate the sharing is\n// whatever the isolate serves at once, and a lone stream asks for itself -\n// the same code, degrading to one small call per tab per interval.\n\n/** How often the source is asked, when it answered at once. */\nexport const CHANGES_INTERVAL_MS = 2_000;\n\n/** How long the source may hold one ask. Bounds how long a new tab waits to join the shared ask. */\nexport const CHANGES_WAIT_MS = 5_000;\n\ninterface Stream {\n names: Set<string>;\n send(name: string, version: number): void;\n}\n\nconst streams = new Set<Stream>();\n\n/** The latest version seen for each name any stream holds. */\nconst known = new Map<string, number>();\n\nlet running = false;\nlet wake: (() => void) | null = null;\nlet warned = false;\n\nfunction observe(name: string, version: number): void {\n if (known.get(name) === version) return;\n\n known.set(name, version);\n\n for (const stream of streams)\n if (stream.names.has(name)) stream.send(name, version);\n}\n\n/** Mapped to this process's set of streams: what to ask about, relative to what is known. */\nfunction sinceAll(): Record<string, number> {\n const since: Record<string, number> = {};\n\n for (const stream of streams)\n for (const name of stream.names) since[name] = known.get(name) ?? 0;\n\n return since;\n}\n\nasync function loop(): Promise<void> {\n running = true;\n\n try {\n while (streams.size > 0) {\n const started = Date.now();\n\n try {\n const moved = await versionSource().changed(\n sinceAll(),\n CHANGES_WAIT_MS,\n );\n\n for (const [name, version] of Object.entries(moved))\n observe(name, version);\n } catch (error) {\n // A backend blip is not the end of watching. Said once, not per interval.\n if (!warned) {\n warned = true;\n console.warn(\n \"[rsc-kit] asking the backend for name versions failed: \" +\n (error instanceof Error ? error.message : String(error)),\n );\n }\n }\n\n // A source that waited used the interval; one that answered at once is\n // asked again after it. A stream opening meanwhile wakes this early.\n const remaining = CHANGES_INTERVAL_MS - (Date.now() - started);\n\n if (remaining > 0 && streams.size > 0) {\n await new Promise<void>((resolve) => {\n const timer = setTimeout(resolve, remaining);\n\n (timer as { unref?: () => void }).unref?.();\n wake = () => {\n clearTimeout(timer);\n resolve();\n };\n });\n wake = null;\n }\n }\n } finally {\n running = false;\n // A stream that opened as the loop was ending starts it again.\n if (streams.size > 0) void loop();\n }\n}\n\nfunction parseWatch(\n url: URL,\n): { name: string; version: number; signature: string }[] | null {\n const raw = url.searchParams.get(\"w\");\n\n if (!raw) return null;\n\n let entries: unknown;\n\n try {\n entries = JSON.parse(raw);\n } catch {\n return null;\n }\n\n if (!Array.isArray(entries) || entries.length === 0 || entries.length > 200)\n return null;\n\n const parsed: { name: string; version: number; signature: string }[] = [];\n\n for (const entry of entries) {\n if (!Array.isArray(entry) || entry.length !== 3) return null;\n\n const [name, version, signature] = entry as unknown[];\n\n if (\n typeof name !== \"string\" ||\n typeof version !== \"number\" ||\n typeof signature !== \"string\"\n )\n return null;\n\n parsed.push({ name, version, signature });\n }\n\n return parsed;\n}\n\n/**\n * The stream a tab watches its names on: `GET /_rsc/changes?w=[[name, version, signature], …]`.\n *\n * One `data:` line per version that moved, `{ name, version }`, from the moment\n * the stream opens - a name that moved between the render and now is reported\n * at once, so nothing that happened in the gap is missed. Keepalives as an\n * events() route sends them; ends when the browser goes away.\n */\nexport async function changes(request: Request): Promise<Response> {\n const entries = parseWatch(new URL(request.url));\n\n if (!entries) return new Response(\"Bad watch request.\", { status: 400 });\n\n for (const { name, signature } of entries) {\n if (!(await verify(name, signature))) {\n return new Response(\"Not a name this page was rendered with.\", {\n status: 403,\n });\n }\n }\n\n const since: Record<string, number> = {};\n\n for (const { name, version } of entries) since[name] = version;\n\n const encoder = new TextEncoder();\n let stream: Stream | null = null;\n\n const body = new ReadableStream<Uint8Array>({\n start(sink) {\n const write = (text: string) => {\n try {\n sink.enqueue(encoder.encode(text));\n } catch {\n // Closed under us; the cancel below ends the rest.\n }\n };\n\n write(\"retry: 2000\\n\\n\");\n\n const keepalive = setInterval(\n () => write(\": keepalive\\n\\n\"),\n KEEPALIVE_MS,\n );\n\n (keepalive as { unref?: () => void }).unref?.();\n\n const me: Stream = {\n names: new Set(Object.keys(since)),\n send: (name, version) => write(frame({ name, version })),\n };\n\n stream = me;\n streams.add(me);\n\n const end = () => {\n clearInterval(keepalive);\n streams.delete(me);\n\n try {\n sink.close();\n } catch {\n // Already closed.\n }\n };\n\n request.signal.addEventListener(\"abort\", end, { once: true });\n\n // What is known already answers at once; what is not is seeded with\n // what this tab holds, so the shared ask compares against it.\n for (const [name, version] of Object.entries(since)) {\n const seen = known.get(name);\n\n if (seen === undefined) known.set(name, version);\n else if (seen !== version) me.send(name, seen);\n }\n\n // This tab's own first ask, holding as long as the source will: the\n // shared ask may be mid-wait on an older set of names, and a name moving\n // in the seconds before this one joins it must not go unnoticed.\n void versionSource()\n .changed(since, CHANGES_WAIT_MS)\n .then((moved) => {\n for (const [name, version] of Object.entries(moved))\n observe(name, version);\n })\n .catch(() => {});\n\n if (!running) void loop();\n else wake?.();\n },\n cancel() {\n if (stream) streams.delete(stream);\n },\n });\n\n return new Response(body, {\n headers: {\n \"Content-Type\": \"text/event-stream; charset=utf-8\",\n \"Cache-Control\": \"no-store\",\n \"X-Accel-Buffering\": \"no\",\n },\n });\n}\n\n/** For tests: forget every stream and version this process has seen. */\nexport function resetChanges(): void {\n streams.clear();\n known.clear();\n warned = false;\n delete globals[LOCAL_SOURCE];\n}\n\n/** The url a tab opens to watch, for the client. */\nexport const CHANGES_PATH = HEADER.changesPath;\n"]}
|
|
1
|
+
{"version":3,"file":"changed.js","sourceRoot":"","sources":["../src/changed.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Names: a name for data a page depends on, so whatever changes it can say so\n * from anywhere - a webhook, a job, another visitor's action - and every open\n * tab showing it refreshes.\n *\n * // a section says what it depends on\n * export default section('repos', Repos, { refreshOn: ({ params }) => [`team:${params.team}:repos`] })\n *\n * // and whatever changes it says so\n * changed(`team:${team}:repos`) // here, from a route.ts or an action\n * rsckit.Changed(ctx, \"team:1:repos\") // or the backend, from its webhook\n * Rsc::changed(\"team:$team:repos\"); // Laravel\n *\n * A name has a version: a number that moves whenever something says the name\n * changed. Nothing else - not what changed, not the data. A page learns the\n * versions at render; one stream per open tab watches them; when one moves,\n * the sections carrying that name ask for themselves again, the way refresh()\n * does. Data is never pushed, which is what keeps this working on a host that\n * cannot hold state: a version is a read.\n *\n * Where versions live is the source's business. Without a backend they are in\n * this process, which is one instance. With one, the backend keeps them - in\n * its cache or a table, shared by every instance it runs - and answers\n * `__rsc.changed`: given the versions a watcher holds, the ones that differ now.\n * A backend that can wait holds the call until one does; one that cannot\n * answers at once, and the watcher asks again on its interval. The protocol\n * is the same either way, and so is the page.\n */\n\nimport { frame, keepaliveMs } from \"./events.js\";\nimport { HEADER } from \"./headers.js\";\nimport { listenToBroadcast } from \"./broadcast.js\";\n\nexport { listenToBroadcast, type BroadcastOptions } from \"./broadcast.js\";\n\n/** The reserved name a backend answers name versions on. */\nexport const CHANGED_FUNCTION = \"__rsc.changed\";\n\n/** What `__rsc.changed` is asked. */\nexport interface ChangedQuery {\n /** The versions the asker holds. A name never changed is 0; -1 asks for every current version. */\n since: Record<string, number>;\n /** How long the backend may hold the call waiting for one to differ, in ms. It may answer at once. */\n wait?: number;\n}\n\n/** What `__rsc.changed` answers: the versions among `since` that differ now. */\nexport interface ChangedAnswer {\n versions: Record<string, number>;\n}\n\n/**\n * Where name versions are read from, and - when they live here - bumped.\n *\n * `changed` answers the versions among `since` that differ now, and may wait\n * up to `wait` ms for one to. Nothing else is required of it, which is why a\n * backend's cache, a table, this process's memory, or a pub/sub that answers\n * the moment a name moves all fit behind it.\n */\nexport interface VersionSource {\n changed(\n since: Record<string, number>,\n wait: number,\n ): Promise<Record<string, number>>;\n /** Say these names changed. Only a source that holds its own versions can. */\n bump(names: string[]): void | Promise<void>;\n}\n\n/**\n * The version a name moves to: the larger of one past where it was and the\n * current time in milliseconds.\n *\n * A counter would do for \"it moved\", but a counter repeats once its row is\n * gone - deleted to keep the store small, a name starts again from 0 and\n * climbs back to a value some tab is still holding, and that tab misses the\n * change. A time never comes round again, so a name may be forgotten at any\n * moment: a tab holding the old value sees a different one, and at worst\n * refreshes once for nothing. It is also when the name last moved, which is\n * all cleanup needs. Every store bumps with it; one that writes `+ 1` still\n * works, it is just not safe to prune.\n */\nexport function nextVersion(current = 0): number {\n return Math.max(current + 1, Date.now());\n}\n\n/** The safety-net read of a store that listens: longer than any ask waits, so once per ask. */\nexport const LISTENING_POLL_MS = 30_000;\n\n/** How long a name nobody changes is kept, by the stores that forget on their own. */\nexport const VERSIONS_KEPT_MS = 30 * 24 * 60 * 60 * 1000;\n\n/**\n * Versions kept in this process: the source without a backend, and the one\n * tests use. A name not changed in `forgetAfter` ms (30 days) is forgotten,\n * swept at most once an hour, so a process up for months does not hold every\n * name it ever saw.\n */\nexport function memoryVersions(\n options: { forgetAfter?: number } = {},\n): VersionSource {\n const forgetAfter = options.forgetAfter ?? VERSIONS_KEPT_MS;\n const versions = new Map<string, number>();\n const waiters = new Set<() => void>();\n let swept = Date.now();\n\n const sweep = () => {\n const now = Date.now();\n\n if (now - swept < Math.min(forgetAfter, 60 * 60 * 1000)) return;\n swept = now;\n\n for (const [name, version] of versions)\n if (version < now - forgetAfter) versions.delete(name);\n };\n\n const differing = (since: Record<string, number>): Record<string, number> => {\n const out: Record<string, number> = {};\n\n for (const [name, held] of Object.entries(since)) {\n const current = versions.get(name) ?? 0;\n\n if (current !== held) out[name] = current;\n }\n\n return out;\n };\n\n return {\n async changed(since, wait) {\n const now = differing(since);\n\n if (Object.keys(now).length > 0 || wait <= 0) return now;\n\n await new Promise<void>((resolve) => {\n const done = () => {\n clearTimeout(timer);\n waiters.delete(done);\n resolve();\n };\n const timer = setTimeout(done, wait);\n\n // A test or a build must not be held open by a watcher's wait.\n (timer as { unref?: () => void }).unref?.();\n waiters.add(done);\n });\n\n return differing(since);\n },\n bump(names) {\n sweep();\n for (const name of names)\n versions.set(name, nextVersion(versions.get(name)));\n for (const wake of [...waiters]) wake();\n },\n };\n}\n\ntype HostFn = (name: string, ...args: unknown[]) => Promise<unknown>;\n\n/**\n * Versions kept by the backend, read through `__rsc.changed`.\n *\n * A backend that has not got the function - an adapter from before names, or\n * a host of plain rpc functions - is answered from this process instead,\n * found out on the first ask and remembered, so a page with names still works\n * on it: one instance, versions bumped by `changed()` here.\n */\nexport function backendVersions(call: HostFn): VersionSource {\n let local: VersionSource | null = null;\n\n // A backend's \"no such function\", or the test host's \"no handler for\".\n const unknown = (error: unknown): boolean =>\n /no host function named|has no handler for/i.test(\n error instanceof Error ? error.message : String(error),\n );\n\n return {\n async changed(since, wait) {\n if (local) return local.changed(since, wait);\n\n try {\n const answer = (await call(CHANGED_FUNCTION, {\n since,\n wait,\n } satisfies ChangedQuery)) as Partial<ChangedAnswer> | null;\n\n return answer?.versions ?? {};\n } catch (error) {\n if (!unknown(error)) throw error;\n\n local = memoryVersions();\n\n return local.changed(since, wait);\n }\n },\n bump(names) {\n if (local) return local.bump(names);\n\n throw new Error(\n \"changed() bumps versions this process keeps, and a backend keeps these: say the name changed there - \" +\n \"rsckit.Changed(ctx, ...) in Go, Rsc::changed(...) in Laravel.\",\n );\n },\n };\n}\n\n/** Runs one statement with positional parameters and resolves to its rows. */\nexport type SqlQuery = (\n text: string,\n params: unknown[],\n) => Promise<readonly Record<string, unknown>[]>;\n\nexport interface SqlVersionsOptions {\n /**\n * Which database, so a change is one statement - an upsert of every name\n * it moves - rather than a read and a write per name. Left out, the store\n * uses only SQL every database has, at the cost of those extra queries.\n */\n dialect?: \"postgres\" | \"mysql\" | \"sqlite\";\n /**\n * Postgres only: NOTIFY this channel in the same statement as the upsert,\n * so a listening server wakes - and only once the write commits.\n */\n channel?: string;\n /**\n * Your driver, as a function of SQL text and parameters:\n *\n * query: (text, params) => sql.unsafe(text, params) // postgres.js, Bun.sql\n * query: async (text, params) => (await pool.query(text, params)).rows // node-postgres\n */\n query: SqlQuery;\n /** The table: `CREATE TABLE rsc_versions (name TEXT PRIMARY KEY, version BIGINT NOT NULL)`. */\n table?: string;\n /** How the n-th parameter is written (1-based). Default `?`; Postgres is `(n) => '$' + n`. */\n placeholder?: (n: number) => string;\n /**\n * Called once with a function that wakes every waiting ask, for a database\n * that can say the moment a version moved - Postgres's LISTEN:\n *\n * listen: (wake) => sql.listen('rsc_versions', wake)\n *\n * Without it, a waiting ask reads the table every `poll` ms.\n */\n listen?: (wake: () => void) => unknown;\n /**\n * Run after every bump, to tell the other instances - Postgres's NOTIFY:\n *\n * notify: () => sql.notify('rsc_versions', '')\n */\n notify?: () => unknown;\n /**\n * How often a waiting ask reads the table: every second by default, or,\n * with `listen`, only as a safety net - about once per ask.\n */\n poll?: number;\n}\n\n/**\n * Versions in a table every instance shares: the web servers, and the worker\n * that finishes a job and says so. The same table and columns Go's\n * `SQLVersions` uses, so a Go service and a JavaScript worker can share one.\n *\n * installVersionSource(sqlVersions({\n * query: (text, params) => sql.unsafe(text, params),\n * placeholder: (n) => '$' + n,\n * listen: (wake) => sql.listen('rsc_versions', wake),\n * notify: () => sql.notify('rsc_versions', ''),\n * }))\n *\n * Written with an UPDATE and, the first time, an INSERT - two statements any\n * SQL dialect has - rather than an upsert each spells differently. Takes the\n * app's own driver, so the engine has no database dependency of its own.\n */\n/**\n * One statement that moves every name to nextVersion: an upsert, in the\n * dialect's own words, and on Postgres the NOTIFY with it. A name is either\n * inserted at `now` or moved to the larger of one more and `now`.\n */\nfunction upsert(\n dialect: \"postgres\" | \"mysql\" | \"sqlite\",\n table: string,\n mark: (n: number) => string,\n names: string[],\n now: number,\n channel?: string,\n): [string, unknown[]] {\n if (dialect === \"postgres\") {\n // $1 is now, the names follow; the channel, if any, comes last.\n const rows = names.map((_, i) => `(${mark(i + 2)}, ${mark(1)})`).join(\", \");\n const insert =\n `INSERT INTO ${table} (name, version) VALUES ${rows} ` +\n `ON CONFLICT (name) DO UPDATE SET version = GREATEST(${table}.version + 1, EXCLUDED.version)`;\n\n if (!channel) return [insert, [now, ...names]];\n\n // A NOTIFY is delivered when the transaction commits, so a listener is\n // never woken before the change it is told about can be read.\n return [\n `WITH moved AS (${insert} RETURNING 1) SELECT pg_notify(${mark(names.length + 2)}, '') FROM (SELECT count(*) FROM moved) AS done`,\n [now, ...names, channel],\n ];\n }\n\n const rows = names.map(() => `(${mark(1)}, ${mark(1)})`).join(\", \");\n const params = names.flatMap((name) => [name, now]);\n\n return dialect === \"mysql\"\n ? [\n `INSERT INTO ${table} (name, version) VALUES ${rows} ON DUPLICATE KEY UPDATE version = GREATEST(version + 1, VALUES(version))`,\n params,\n ]\n : [\n `INSERT INTO ${table} (name, version) VALUES ${rows} ON CONFLICT (name) DO UPDATE SET version = MAX(version + 1, excluded.version)`,\n params,\n ];\n}\n\n/** A store whose old names can be cleared out: the SQL ones. */\nexport interface PrunableVersions extends VersionSource {\n /**\n * Delete every name not changed in `olderThan` ms (30 days by default).\n * Always safe: a version is a time and never comes round again, so a tab\n * still holding a pruned name sees it differ and refreshes once. Run it\n * from a scheduled job - a daily one is plenty.\n */\n prune(olderThan?: number): Promise<void>;\n}\n\nexport function sqlVersions(options: SqlVersionsOptions): PrunableVersions {\n const { query } = options;\n const table = options.table ?? \"rsc_versions\";\n const mark = options.placeholder ?? (() => \"?\");\n\n const source = createVersions(\n {\n async read(names) {\n const rows = await query(\n `SELECT name, version FROM ${table} WHERE name IN (${names.map((_, i) => mark(i + 1)).join(\", \")})`,\n names,\n );\n\n // A BIGINT comes back as a string from most drivers.\n return Object.fromEntries(\n rows.map((row) => [String(row.name), Number(row.version)]),\n );\n },\n async bump(names) {\n if (options.dialect) {\n await query(\n ...upsert(\n options.dialect,\n table,\n mark,\n names,\n Date.now(),\n options.channel,\n ),\n );\n\n return;\n }\n\n // nextVersion, in SQL every dialect has: CASE rather than GREATEST,\n // which SQLite spells MAX. The time is this process's, passed in, so\n // the database's clock never has to agree with it.\n const update =\n `UPDATE ${table} SET version = CASE WHEN version + 1 > ${mark(1)} THEN version + 1 ELSE ${mark(2)} END ` +\n `WHERE name = ${mark(3)}`;\n const insert = `INSERT INTO ${table} (name, version) VALUES (${mark(1)}, ${mark(2)})`;\n\n for (const name of names) {\n const now = Date.now();\n\n // The row count is not something every driver reports the same way,\n // so ask: a name with no row yet gets one.\n const [exists] = await query(\n `SELECT 1 AS found FROM ${table} WHERE name = ${mark(1)}`,\n [name],\n );\n\n if (exists) {\n await query(update, [now, now, name]);\n continue;\n }\n\n try {\n await query(insert, [name, now]);\n } catch {\n // Another instance inserted it first: bump that one.\n await query(update, [now, now, name]);\n }\n }\n },\n listen: options.listen,\n notify: options.notify,\n },\n { poll: options.poll },\n );\n\n return {\n ...source,\n async prune(olderThan = VERSIONS_KEPT_MS) {\n await query(`DELETE FROM ${table} WHERE version < ${mark(1)}`, [\n Date.now() - olderThan,\n ]);\n },\n };\n}\n\n/**\n * Where versions are kept, as the app writes it for its own database: the\n * three things rsc-kit cannot know about your storage, and nothing else.\n * `createVersions` does the rest - comparing, waiting, waking.\n *\n * // Drizzle\n * read: async (names) => Object.fromEntries(\n * (await db.select().from(rscVersions).where(inArray(rscVersions.name, names)))\n * .map((r) => [r.name, Number(r.version)])),\n * bump: async (names) => { for (const name of names) await db.insert(rscVersions)\n * .values({ name, version: 1 })\n * .onConflictDoUpdate({ target: rscVersions.name, set: { version: sql`${rscVersions.version} + 1` } }) },\n *\n * // Redis\n * read: async (names) => Object.fromEntries((await redis.mget(names.map(k)))\n * .map((v, i) => [names[i], Number(v ?? 0)])),\n * bump: async (names) => { await Promise.all(names.map((n) => redis.incr(k(n)))) },\n */\nexport interface VersionStore {\n /** The current version of each name asked for. A name it has never seen may be left out: it is 0. */\n read(names: string[]): Promise<Record<string, number>>;\n /**\n * Move each name's version to `nextVersion(current)` - one past where it\n * was, or the current time in ms if that is larger. Watchers only compare,\n * so any new value works; this one never repeats, which is what makes it\n * safe to delete a name later. A plain `+ 1` works too, until a row is\n * deleted.\n */\n bump(names: string[]): Promise<void>;\n /**\n * Called once with a function that wakes every waiting ask, when the store\n * can say the moment a version moved - Postgres LISTEN, Redis SUBSCRIBE.\n * Without it a waiting ask reads again every `poll` ms.\n */\n listen?(wake: () => void): unknown;\n /** Run after a bump, to tell the other instances listening: Postgres NOTIFY, Redis PUBLISH. */\n notify?(): unknown;\n}\n\n/**\n * A version source over any store: SQL through any driver or ORM - Drizzle,\n * Prisma, Kysely, Knex - or Redis, or anything with a read and an increment.\n *\n * installVersionSource(createVersions({ read, bump, listen, notify }))\n *\n * rsc-kit owns the rest: an ask waits up to the time it was given for a\n * version to differ, reading every `poll` ms (1000 by default) or the moment\n * `listen` wakes it, and a bump in this process wakes this process's asks at\n * once whatever the store.\n */\nexport function createVersions(\n store: VersionStore,\n options: { poll?: number } = {},\n): VersionSource {\n // A store that listens is woken the moment a version moves, so reading the\n // table on a timer is only a safety net - for a listening connection that\n // dropped without a word. Once an ask is enough: about every five seconds\n // per server, where one that cannot listen reads every second.\n const poll = options.poll ?? (store.listen ? LISTENING_POLL_MS : 1_000);\n const waiters = new Set<() => void>();\n const wakeAll = () => {\n for (const wake of [...waiters]) wake();\n };\n\n // A store that can listen wakes the renderer the way anything else does -\n // through wakeOn, which starts it with the first watching tab and never in\n // a build. A wake also ends an ask this store is holding.\n if (store.listen) wakeOn(store.listen);\n wakers().sleepers.add(wakeAll);\n\n const read = async (names: string[]): Promise<Record<string, number>> => {\n if (names.length === 0) return {};\n\n const found = await store.read(names);\n\n return Object.fromEntries(\n names.map((name) => [name, Number(found[name] ?? 0)]),\n );\n };\n\n return {\n async changed(since, wait) {\n // Only a server with a tab watching asks with a wait - never a build -\n // so this is a safe moment to start listening, as is the first tab.\n if (wait > 0) startWakers();\n\n const names = Object.keys(since);\n const deadline = Date.now() + Math.max(0, wait);\n\n for (;;) {\n const versions = await read(names);\n const differ: Record<string, number> = {};\n\n for (const name of names)\n if (versions[name] !== since[name])\n differ[name] = versions[name] ?? 0;\n\n const remaining = deadline - Date.now();\n\n if (Object.keys(differ).length > 0 || remaining <= 0) return differ;\n\n await new Promise<void>((resolve) => {\n const done = () => {\n clearTimeout(timer);\n waiters.delete(done);\n resolve();\n };\n const timer = setTimeout(done, Math.min(poll, remaining));\n\n (timer as { unref?: () => void }).unref?.();\n waiters.add(done);\n });\n }\n },\n async bump(names) {\n const unique = [...new Set(names)];\n\n if (unique.length === 0) return;\n\n await store.bump(unique);\n wakeAll();\n if (store.notify) await store.notify();\n },\n };\n}\n\n/** The parts of a Postgres client `postgresVersions` uses: postgres.js and `Bun.sql` both have them. */\nexport interface PostgresClient {\n unsafe(\n text: string,\n params?: unknown[],\n ): PromiseLike<readonly Record<string, unknown>[]>;\n /** postgres.js has it; with it, a waiting ask hears another instance at once. */\n listen?(channel: string, onNotify: () => void): unknown;\n notify?(channel: string, payload: string): unknown;\n}\n\n/**\n * Versions in Postgres, from the client the app already has:\n *\n * installVersionSource(postgresVersions(sql))\n *\n * `sqlVersions` with Postgres's placeholders, and - when the client can\n * LISTEN, as postgres.js and `Bun.sql` (Bun 1.4+) can - a change on one\n * instance wakes the waiting asks on every other at once, rather than on\n * their next read of the table. Without it, the table is read once a second\n * while an ask waits.\n *\n * CREATE TABLE rsc_versions (name TEXT PRIMARY KEY, version BIGINT NOT NULL)\n */\nexport function postgresVersions(\n sql: PostgresClient,\n options: { table?: string; channel?: string; poll?: number } = {},\n): PrunableVersions {\n const channel = options.channel ?? \"rsc_versions\";\n const listen = sql.listen?.bind(sql);\n\n // One statement per change: the upsert of every name, and the NOTIFY that\n // wakes every listening server - this one included - when it commits.\n return sqlVersions({\n query: async (text, params) => await sql.unsafe(text, params),\n placeholder: (n) => \"$\" + n,\n dialect: \"postgres\",\n channel,\n table: options.table,\n poll: options.poll,\n listen: listen ? (wake) => listen(channel, wake) : undefined,\n });\n}\n\nconst SOURCE = Symbol.for(\"rsc-kit.version-source\");\nconst BACKEND_SOURCE = Symbol.for(\"rsc-kit.backend-version-source\");\nconst LOCAL_SOURCE = Symbol.for(\"rsc-kit.local-version-source\");\nconst globals = globalThis as Record<symbol, unknown>;\n\n/**\n * Install where versions are read from - a table, Redis, any store - for this\n * process; `null` takes it out again.\n *\n * The app's choice wins over the backend's: with a Go or Laravel backend that\n * writes its versions to a store this process can read too, reading it here\n * means watching costs the backend nothing.\n */\nexport function installVersionSource(source: VersionSource | null): void {\n globals[SOURCE] = source;\n}\n\n/** The engine's, when a backend is installed: its versions, read through it. Not for apps. */\nexport function installBackendVersionSource(\n source: VersionSource | null,\n): void {\n globals[BACKEND_SOURCE] = source;\n}\n\n/** The source in use: the app's, else the backend's, else this process's own. */\nexport function versionSource(): VersionSource {\n const chosen =\n (globals[SOURCE] as VersionSource | null | undefined) ??\n (globals[BACKEND_SOURCE] as VersionSource | null | undefined);\n\n if (chosen) return chosen;\n\n let local = globals[LOCAL_SOURCE] as VersionSource | undefined;\n\n if (!local) {\n local = globals[LOCAL_SOURCE] = memoryVersions();\n\n // The fallback works in development and in tests, which are one process,\n // and fails silently in production with two: a change on one instance\n // never reaches the tabs on another. Said once, at the first use. An app\n // that really is one instance installs memoryVersions() itself, which is\n // a choice rather than a fallback, and is not warned.\n if (production()) {\n console.warn(\n \"[rsc-kit] refreshOn versions are kept in this process only: a change made on another \" +\n \"instance, or in a worker, will not reach the tabs this one serves. Install a store every \" +\n \"process shares with installVersionSource() in instrumentation.ts - sqlVersions for MySQL \" +\n \"or SQLite, postgresVersions for Postgres, createVersions for anything else: \" +\n \"https://docs.rsc-kit.dev/guides/sections#where-the-versions-live. For a deploy that is one \" +\n \"instance, installVersionSource(memoryVersions()) says so.\",\n );\n }\n }\n\n return local;\n}\n\n/**\n * Say these names changed, so every open tab showing them refreshes.\n *\n * export async function POST(request: Request) { // a webhook, in a route.ts\n * const { team } = await request.json()\n * changed(`team:${team}:repos`)\n * return new Response(null, { status: 204 })\n * }\n *\n * For an app that keeps its versions here. With a backend the versions are\n * its, and so is the call: `rsckit.Changed`, `Rsc::changed`.\n */\nexport async function changed(...names: string[]): Promise<void> {\n await versionSource().bump(names);\n}\n\n// ── Tokens ───────────────────────────────────────────────────────────────────\n//\n// A tab may watch exactly the names its page was rendered with. Rather than\n// remembering what each tab was given - state, which a Worker cannot keep\n// between requests - every name goes to the client signed, and comes back\n// with its signature. Verified, not looked up, so a watch request is\n// stateless, and a browser cannot make up a name to watch.\n\n/** Thrown when a production server has no key to sign names with. */\nexport class MissingSigningSecret extends Error {\n constructor() {\n super(\n \"refreshOn needs a signing secret in production, the same on every instance: set RSC_SIGNING_SECRET \" +\n \"to a long random string. Without one, each instance would sign with its own random key, and a tab \" +\n \"served by one would be refused by the next.\",\n );\n this.name = \"MissingSigningSecret\";\n }\n}\n\n// On the global rather than in this module: an app imports this file through\n// its own copy of the package, and a key configured there must be the key the\n// engine's copy signs and verifies with.\nconst KEY = Symbol.for(\"rsc-kit.signing-key\");\n\ninterface Keying {\n secret: string | null;\n keyed: Promise<CryptoKey> | null;\n signatures: Map<string, Promise<string>>;\n}\n\nfunction keying(): Keying {\n return ((globals[KEY] as Keying | undefined) ??= {\n secret: null,\n keyed: null,\n signatures: new Map(),\n });\n}\n\nconst env = (name: string): string | undefined =>\n (globalThis as { process?: { env?: Record<string, string | undefined> } })\n .process?.env?.[name] || undefined;\n\nconst production = (): boolean => env(\"NODE_ENV\") === \"production\";\n\n/**\n * The secret names are signed with, when it does not come from the\n * environment. Normally it does: `RSC_SIGNING_SECRET`, the same on every\n * instance, or a tab served by one is refused by another.\n *\n * Its own secret, not the backend's host-call secret: that one authorises\n * calls between the renderer and a backend, and an app with no backend has\n * no reason to have it. Two jobs, two keys - one leaking does not hand out\n * the other.\n */\nexport function configureChanged(options: { secret?: string | null }): void {\n const state = keying();\n\n state.secret = options.secret ?? null;\n state.keyed = null;\n state.signatures.clear();\n}\n\n/** The secret in use: configured, or from the environment; null when there is none. */\nfunction secretInUse(): string | null {\n const state = keying();\n\n return state.secret ?? env(\"RSC_SIGNING_SECRET\") ?? null;\n}\n\n/**\n * Refuse, in production, to sign with a key nobody else has.\n *\n * A random key is right for one process - a dev server - and silently wrong\n * for two: every tab served by one instance is refused by the next, and never\n * refreshes. The server calls this on its first request when the app uses\n * refreshOn, so the misconfiguration is an error at once rather than a page\n * that quietly stops updating.\n */\nexport function assertSigningSecret(): void {\n if (!secretInUse() && production()) throw new MissingSigningSecret();\n}\n\nlet warnedRandom = false;\n\nasync function key(): Promise<CryptoKey> {\n const state = keying();\n\n return (state.keyed ??= (async () => {\n const secret = secretInUse();\n let raw: Uint8Array<ArrayBuffer>;\n\n if (secret) raw = new TextEncoder().encode(secret);\n else if (production()) throw new MissingSigningSecret();\n else {\n if (!warnedRandom) {\n warnedRandom = true;\n console.warn(\n \"[rsc-kit] refreshOn is signing with a random key, which is fine for one dev server. \" +\n \"Set RSC_SIGNING_SECRET before running more than one instance.\",\n );\n }\n\n raw = crypto.getRandomValues(new Uint8Array(32));\n }\n\n return await crypto.subtle.importKey(\n \"raw\",\n raw,\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"],\n );\n })());\n}\n\n/** A name's signature: the first 16 bytes of an HMAC, base64url, which is what rides in a url. */\nexport function sign(name: string): Promise<string> {\n const { signatures } = keying();\n let pending = signatures.get(name);\n\n if (!pending) {\n pending = (async () => {\n const mac = await crypto.subtle.sign(\n \"HMAC\",\n await key(),\n new TextEncoder().encode(name),\n );\n\n return base64url(new Uint8Array(mac).slice(0, 16));\n })();\n\n // Bounded: a name with a visitor's id in it is a new entry per visitor.\n if (signatures.size > 10_000) signatures.clear();\n signatures.set(name, pending);\n }\n\n return pending;\n}\n\nasync function verify(name: string, signature: string): Promise<boolean> {\n const expected = await sign(name);\n\n if (expected.length !== signature.length) return false;\n\n let differ = 0;\n\n for (let i = 0; i < expected.length; i++)\n differ |= expected.charCodeAt(i) ^ signature.charCodeAt(i);\n\n return differ === 0;\n}\n\nfunction base64url(bytes: Uint8Array): string {\n let text = \"\";\n\n for (const byte of bytes) text += String.fromCharCode(byte);\n\n return btoa(text).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n/** What a page hands the client for one name: its version at render, and its signature. */\nexport type SignedName = [version: number, signature: string];\n\n// ── Watching ─────────────────────────────────────────────────────────────────\n//\n// One stream per open tab, and one ask of the source per process: every\n// stream's names are asked about together, on an interval, and a version that\n// moved is written to every stream holding its name. A thousand tabs on a\n// long-lived server cost the backend one call per interval, not a thousand.\n// On a host that answers each request in its own isolate the sharing is\n// whatever the isolate serves at once, and a lone stream asks for itself -\n// the same code, degrading to one small call per tab per interval.\n\n/** How often the source is asked, when it answered at once. */\nexport const CHANGES_INTERVAL_MS = 2_000;\n\n/** How long the source may hold one ask. Bounds how long a new tab waits to join the shared ask. */\nexport const CHANGES_WAIT_MS = 5_000;\n\ninterface Stream {\n names: Set<string>;\n send(name: string, version: number): void;\n}\n\nconst streams = new Set<Stream>();\n\n/** The latest version seen for each name any stream holds. */\nconst known = new Map<string, number>();\n\nlet running = false;\nlet wake: (() => void) | null = null;\nlet warned = false;\n\nfunction observe(name: string, version: number): void {\n if (known.get(name) === version) return;\n\n known.set(name, version);\n\n for (const stream of streams)\n if (stream.names.has(name)) stream.send(name, version);\n}\n\n/**\n * A stream gone: and with it every name no other stream holds, which this\n * process has no reason to remember. Kept, the latest version of every name\n * any tab ever watched piled up for as long as the server ran.\n */\nfunction close(stream: Stream): void {\n if (!streams.delete(stream)) return;\n\n for (const name of stream.names) {\n let held = false;\n\n for (const other of streams) {\n if (other.names.has(name)) {\n held = true;\n break;\n }\n }\n\n if (!held) known.delete(name);\n }\n}\n\n/** Mapped to this process's set of streams: what to ask about, relative to what is known. */\nfunction sinceAll(): Record<string, number> {\n const since: Record<string, number> = {};\n\n for (const stream of streams)\n for (const name of stream.names) since[name] = known.get(name) ?? 0;\n\n return since;\n}\n\n// ── Waking ───────────────────────────────────────────────────────────────────\n//\n// A backend that cannot hold a question open - PHP - is asked again on an\n// interval. Something that can say \"a version moved\" the moment it does -\n// a broadcast, a Redis channel - makes that interval a safety net: the\n// renderer asks the moment it is woken, and otherwise rarely.\n\nconst WAKERS = Symbol.for(\"rsc-kit.wakers\");\n\ninterface Wakers {\n listens: ((wake: () => void) => unknown)[];\n started: boolean;\n /** A wake that came while an ask was in flight: ask again straight after. */\n pending: boolean;\n /** Asks a store is holding open, ended by a wake. */\n sleepers: Set<() => void>;\n}\n\nfunction wakers(): Wakers {\n return ((globals[WAKERS] as Wakers | undefined) ??= {\n listens: [],\n started: false,\n pending: false,\n sleepers: new Set(),\n });\n}\n\n/**\n * Wake the renderer when a version may have moved, so it asks then rather\n * than on its next interval.\n *\n * wakeOn(listenToBroadcast({ url, key })) // a broadcast server\n * wakeOn((wake) => subscriber.subscribe('rsc', wake)) // a Redis channel\n *\n * `listen` is given a function to call - it carries no names and no data;\n * the renderer asks wherever the versions live, as always. It is started\n * when the first tab starts watching, never in a build. With one installed,\n * a backend that answers at once is asked again every 30 seconds as a safety\n * net instead of every two.\n */\nexport function wakeOn(listen: (wake: () => void) => unknown): void {\n const state = wakers();\n\n state.listens.push(listen);\n if (state.started) start(listen);\n}\n\n/**\n * Start one listener; one that fails - a database not up yet - is tried\n * again, backing off to thirty seconds. Until it is up the timer stands in.\n */\nfunction start(listen: (wake: () => void) => unknown, failures = 0): void {\n void Promise.resolve()\n .then(() => listen(signal))\n .catch((error: unknown) => {\n if (failures === 0) {\n console.warn(\n \"[rsc-kit] a wakeOn listener failed to start, trying again: \" +\n (error instanceof Error ? error.message : String(error)),\n );\n }\n\n const retry = setTimeout(\n () => start(listen, failures + 1),\n Math.min(30_000, 1000 * 2 ** failures),\n );\n\n (retry as { unref?: () => void }).unref?.();\n });\n}\n\n/** Something said a version may have moved: ask now, and end any ask a store is holding. */\nfunction signal(): void {\n const state = wakers();\n\n state.pending = true;\n wake?.();\n for (const sleeper of [...state.sleepers]) sleeper();\n}\n\nfunction startWakers(): void {\n const state = wakers();\n\n if (state.started) return;\n state.started = true;\n\n // Set in the environment, not guessed: a broadcast server to listen to.\n const url = env(\"RSC_BROADCAST_URL\");\n const key = env(\"RSC_BROADCAST_KEY\");\n\n if (url && key) state.listens.push(listenToBroadcast({ url, key }));\n\n for (const listen of state.listens) start(listen);\n}\n\nasync function loop(): Promise<void> {\n running = true;\n startWakers();\n\n try {\n while (streams.size > 0) {\n const started = Date.now();\n const state = wakers();\n\n state.pending = false;\n\n try {\n const moved = await versionSource().changed(\n sinceAll(),\n CHANGES_WAIT_MS,\n );\n\n for (const [name, version] of Object.entries(moved))\n observe(name, version);\n } catch (error) {\n // A backend blip is not the end of watching. Said once, not per interval.\n if (!warned) {\n warned = true;\n console.warn(\n \"[rsc-kit] asking the backend for name versions failed: \" +\n (error instanceof Error ? error.message : String(error)),\n );\n }\n }\n\n // A source that waited used the interval; one that answered at once is\n // asked again after it - or, with something to wake it, only as a\n // safety net. A stream opening, or a wake, ends the wait early; a wake\n // that came during the ask is answered at once.\n const interval =\n state.listens.length > 0 ? LISTENING_POLL_MS : CHANGES_INTERVAL_MS;\n const remaining = state.pending ? 0 : interval - (Date.now() - started);\n\n if (remaining > 0 && streams.size > 0) {\n await new Promise<void>((resolve) => {\n const timer = setTimeout(resolve, remaining);\n\n (timer as { unref?: () => void }).unref?.();\n wake = () => {\n clearTimeout(timer);\n resolve();\n };\n });\n wake = null;\n }\n }\n } finally {\n running = false;\n // A stream that opened as the loop was ending starts it again.\n if (streams.size > 0) void loop();\n }\n}\n\nfunction parseWatch(\n url: URL,\n): { name: string; version: number; signature: string }[] | null {\n const raw = url.searchParams.get(\"w\");\n\n if (!raw) return null;\n\n let entries: unknown;\n\n try {\n entries = JSON.parse(raw);\n } catch {\n return null;\n }\n\n if (!Array.isArray(entries) || entries.length === 0 || entries.length > 200)\n return null;\n\n const parsed: { name: string; version: number; signature: string }[] = [];\n\n for (const entry of entries) {\n if (!Array.isArray(entry) || entry.length !== 3) return null;\n\n const [name, version, signature] = entry as unknown[];\n\n if (\n typeof name !== \"string\" ||\n typeof version !== \"number\" ||\n typeof signature !== \"string\"\n )\n return null;\n\n parsed.push({ name, version, signature });\n }\n\n return parsed;\n}\n\n/**\n * The stream a tab watches its names on: `GET /_rsc/changes?w=[[name, version, signature], …]`.\n *\n * One `data:` line per version that moved, `{ name, version }`, from the moment\n * the stream opens - a name that moved between the render and now is reported\n * at once, so nothing that happened in the gap is missed. Keepalives as an\n * events() route sends them; ends when the browser goes away.\n */\nexport async function changes(request: Request): Promise<Response> {\n const entries = parseWatch(new URL(request.url));\n\n if (!entries) return new Response(\"Bad watch request.\", { status: 400 });\n\n for (const { name, signature } of entries) {\n if (!(await verify(name, signature))) {\n return new Response(\"Not a name this page was rendered with.\", {\n status: 403,\n });\n }\n }\n\n const since: Record<string, number> = {};\n\n for (const { name, version } of entries) since[name] = version;\n\n const encoder = new TextEncoder();\n let stream: Stream | null = null;\n\n const body = new ReadableStream<Uint8Array>({\n start(sink) {\n const write = (text: string) => {\n try {\n sink.enqueue(encoder.encode(text));\n } catch {\n // Closed under us; the cancel below ends the rest.\n }\n };\n\n write(\"retry: 2000\\n\\n\");\n\n const keepalive = setInterval(\n () => write(\": keepalive\\n\\n\"),\n keepaliveMs(),\n );\n\n (keepalive as { unref?: () => void }).unref?.();\n\n const me: Stream = {\n names: new Set(Object.keys(since)),\n send: (name, version) => write(frame({ name, version })),\n };\n\n stream = me;\n streams.add(me);\n\n const end = () => {\n clearInterval(keepalive);\n close(me);\n\n try {\n sink.close();\n } catch {\n // Already closed.\n }\n };\n\n request.signal.addEventListener(\"abort\", end, { once: true });\n\n // What is known already answers at once; what is not is seeded with\n // what this tab holds, so the shared ask compares against it.\n for (const [name, version] of Object.entries(since)) {\n const seen = known.get(name);\n\n if (seen === undefined) known.set(name, version);\n else if (seen !== version) me.send(name, seen);\n }\n\n // This tab's own first ask, holding as long as the source will: the\n // shared ask may be mid-wait on an older set of names, and a name moving\n // in the seconds before this one joins it must not go unnoticed.\n void versionSource()\n .changed(since, CHANGES_WAIT_MS)\n .then((moved) => {\n for (const [name, version] of Object.entries(moved))\n observe(name, version);\n })\n .catch(() => {});\n\n if (!running) void loop();\n else wake?.();\n },\n cancel() {\n if (stream) close(stream);\n },\n });\n\n return new Response(body, {\n headers: {\n \"Content-Type\": \"text/event-stream; charset=utf-8\",\n // no-transform: a proxy or CDN in front must not compress or buffer\n // it either - Cloudflare compresses text unless told not to.\n \"Cache-Control\": \"no-store, no-transform\",\n \"X-Accel-Buffering\": \"no\",\n },\n });\n}\n\n/** For tests: forget every stream and version this process has seen. */\n/** For tests: how many names this process remembers for its open streams. */\nexport function rememberedNames(): number {\n return known.size;\n}\n\nexport function resetChanges(): void {\n delete globals[WAKERS];\n streams.clear();\n known.clear();\n warned = false;\n delete globals[LOCAL_SOURCE];\n}\n\n/** The url a tab opens to watch, for the client. */\nexport const CHANGES_PATH = HEADER.changesPath;\n"]}
|
package/dist/compress.js
CHANGED
|
@@ -34,7 +34,14 @@ export function shouldCompress(request, response) {
|
|
|
34
34
|
return false;
|
|
35
35
|
if (/\bno-transform\b/i.test(response.headers.get("cache-control") ?? ""))
|
|
36
36
|
return false;
|
|
37
|
-
|
|
37
|
+
const type = response.headers.get("content-type") ?? "";
|
|
38
|
+
if (!COMPRESSIBLE.test(type))
|
|
39
|
+
return false;
|
|
40
|
+
// A stream of events is text, and must not be compressed: each message is
|
|
41
|
+
// a few bytes that has to reach the browser the moment it is written, and
|
|
42
|
+
// a compressor between them delivered nothing at all - not even headers -
|
|
43
|
+
// to a browser that sent Accept-Encoding: gzip, which is every browser.
|
|
44
|
+
if (/^text\/event-stream\b/i.test(type))
|
|
38
45
|
return false;
|
|
39
46
|
const length = Number(response.headers.get("content-length"));
|
|
40
47
|
if (Number.isFinite(length) && length > 0 && length < MIN_BYTES)
|
package/dist/compress.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compress.js","sourceRoot":"","sources":["../src/compress.ts"],"names":[],"mappings":"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","sourcesContent":["// Compression for what the host answers, where nothing in front of it will.\n//\n// Behind Cloudflare, Vercel or nginx the platform compresses and this never\n// runs. A bun or node server answering the internet by itself sent every\n// byte raw - a 90 kB document and 145 kB of stylesheet at their full size -\n// and that alone was the mobile Lighthouse regression a port measured:\n// first paint at 3.8 s where 1.2 s was the baseline. Public assets are\n// precompressed by the build and served by Nitro with their encoding; the\n// pages, payloads, stored answers and api routes come through here.\n//\n// gzip, flushed on every chunk. A streamed page is a shell followed by the\n// holes as they fill, and a compressor left to its own buffer would hold\n// the shell back until enough had accumulated - so each write is flushed\n// out, which costs a little ratio and keeps the stream a stream.\n\nimport { deflate } from \"./compressRuntime.js\";\n\n/** What is worth compressing: text of every kind, and the formats that are text. */\nconst COMPRESSIBLE = /^(?:text\\/|application\\/(?:json|javascript|xml|manifest\\+json|rss\\+xml|atom\\+xml)|image\\/svg\\+xml)/i;\n\n/** Answers too small to be worth a compressor's frame. */\nconst MIN_BYTES = 1024;\n\n/** Stored answers, compressed once: the same bytes go to every visitor who accepts gzip. */\nconst stored = new Map<string, Uint8Array>();\n\nconst STORED_CAP = 1000;\n\nfunction acceptsGzip(request: Request): boolean {\n const accept = request.headers.get(\"accept-encoding\") ?? \"\";\n\n return /(?:^|,)\\s*(?:gzip|\\*)\\s*(?:;\\s*q=(?!0(?:\\.0*)?\\s*(?:,|$)))?/i.test(accept);\n}\n\n/** Whether this answer is one to compress at all. */\nexport function shouldCompress(request: Request, response: Response): boolean {\n if (response.body === null || request.method === \"HEAD\") return false;\n if (response.status === 204 || response.status === 304 || response.status < 200) return false;\n if (response.headers.has(\"content-encoding\")) return false;\n if (/\\bno-transform\\b/i.test(response.headers.get(\"cache-control\") ?? \"\")) return false;\n if (!COMPRESSIBLE.test(response.headers.get(\"content-type\") ?? \"\")) return false;\n\n const length = Number(response.headers.get(\"content-length\"));\n\n if (Number.isFinite(length) && length > 0 && length < MIN_BYTES) return false;\n\n return acceptsGzip(request);\n}\n\nfunction withEncoding(response: Response, body: BodyInit): Response {\n const headers = new Headers(response.headers);\n\n headers.set(\"Content-Encoding\", \"gzip\");\n headers.delete(\"Content-Length\");\n\n const vary = headers.get(\"Vary\");\n\n if (!vary) headers.set(\"Vary\", \"Accept-Encoding\");\n else if (!/\\baccept-encoding\\b/i.test(vary) && vary.trim() !== \"*\") headers.set(\"Vary\", vary + \", Accept-Encoding\");\n\n return new Response(body, { status: response.status, statusText: response.statusText, headers });\n}\n\n/**\n * The response, gzipped if the request accepts it and the answer is worth it.\n *\n * `storedKey` names an answer the build wrote - the same bytes for every\n * visitor - so it is compressed once and kept; everything else is streamed\n * through the compressor as it is produced.\n */\nexport async function compressed(\n request: Request,\n response: Response,\n storedKey?: string,\n): Promise<Response> {\n if (!shouldCompress(request, response)) return response;\n\n const gzip = await deflate();\n\n if (!gzip) return response;\n\n if (storedKey !== undefined) {\n let bytes = stored.get(storedKey);\n\n if (!bytes) {\n bytes = await gzip.whole(new Uint8Array(await response.arrayBuffer()));\n\n if (stored.size >= STORED_CAP) stored.clear();\n\n stored.set(storedKey, bytes);\n }\n\n return withEncoding(response, bytes as unknown as BodyInit);\n }\n\n // A Response made from a string or JSON carries no Content-Length for the\n // check above to read, so the body is peeked: what arrives before the\n // first kilobyte decides. An answer that ends inside it - /api/health, a\n // 15-byte \"ok\" - goes out as it was; anything longer is compressed from\n // its first byte, the peeked part included, and keeps streaming.\n const reader = response.body!.getReader();\n const peeked: Uint8Array[] = [];\n let size = 0;\n let ended = false;\n\n while (size < MIN_BYTES) {\n const { done, value } = await reader.read();\n\n if (done) {\n ended = true;\n break;\n }\n\n peeked.push(value);\n size += value.byteLength;\n }\n\n if (ended) {\n return new Response(size === 0 ? null : (concat(peeked, size) as unknown as BodyInit), {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n });\n }\n\n const rest = new ReadableStream<Uint8Array>({\n start(controller) {\n for (const chunk of peeked) controller.enqueue(chunk);\n },\n async pull(controller) {\n const { done, value } = await reader.read();\n\n if (done) controller.close();\n else controller.enqueue(value);\n },\n cancel(reason) {\n return reader.cancel(reason);\n },\n });\n\n return withEncoding(response, gzip.stream(rest));\n}\n\nfunction concat(chunks: Uint8Array[], size: number): Uint8Array {\n const out = new Uint8Array(size);\n let at = 0;\n\n for (const chunk of chunks) {\n out.set(chunk, at);\n at += chunk.byteLength;\n }\n\n return out;\n}\n\n/** For a test, or a redeploy that reuses the process: forget the stored bytes. */\nexport function forgetCompressed(): void {\n stored.clear();\n}\n"]}
|
|
1
|
+
{"version":3,"file":"compress.js","sourceRoot":"","sources":["../src/compress.ts"],"names":[],"mappings":"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","sourcesContent":["// Compression for what the host answers, where nothing in front of it will.\n//\n// Behind Cloudflare, Vercel or nginx the platform compresses and this never\n// runs. A bun or node server answering the internet by itself sent every\n// byte raw - a 90 kB document and 145 kB of stylesheet at their full size -\n// and that alone was the mobile Lighthouse regression a port measured:\n// first paint at 3.8 s where 1.2 s was the baseline. Public assets are\n// precompressed by the build and served by Nitro with their encoding; the\n// pages, payloads, stored answers and api routes come through here.\n//\n// gzip, flushed on every chunk. A streamed page is a shell followed by the\n// holes as they fill, and a compressor left to its own buffer would hold\n// the shell back until enough had accumulated - so each write is flushed\n// out, which costs a little ratio and keeps the stream a stream.\n\nimport { deflate } from \"./compressRuntime.js\";\n\n/** What is worth compressing: text of every kind, and the formats that are text. */\nconst COMPRESSIBLE = /^(?:text\\/|application\\/(?:json|javascript|xml|manifest\\+json|rss\\+xml|atom\\+xml)|image\\/svg\\+xml)/i;\n\n/** Answers too small to be worth a compressor's frame. */\nconst MIN_BYTES = 1024;\n\n/** Stored answers, compressed once: the same bytes go to every visitor who accepts gzip. */\nconst stored = new Map<string, Uint8Array>();\n\nconst STORED_CAP = 1000;\n\nfunction acceptsGzip(request: Request): boolean {\n const accept = request.headers.get(\"accept-encoding\") ?? \"\";\n\n return /(?:^|,)\\s*(?:gzip|\\*)\\s*(?:;\\s*q=(?!0(?:\\.0*)?\\s*(?:,|$)))?/i.test(accept);\n}\n\n/** Whether this answer is one to compress at all. */\nexport function shouldCompress(request: Request, response: Response): boolean {\n if (response.body === null || request.method === \"HEAD\") return false;\n if (response.status === 204 || response.status === 304 || response.status < 200) return false;\n if (response.headers.has(\"content-encoding\")) return false;\n if (/\\bno-transform\\b/i.test(response.headers.get(\"cache-control\") ?? \"\")) return false;\n const type = response.headers.get(\"content-type\") ?? \"\";\n\n if (!COMPRESSIBLE.test(type)) return false;\n // A stream of events is text, and must not be compressed: each message is\n // a few bytes that has to reach the browser the moment it is written, and\n // a compressor between them delivered nothing at all - not even headers -\n // to a browser that sent Accept-Encoding: gzip, which is every browser.\n if (/^text\\/event-stream\\b/i.test(type)) return false;\n\n const length = Number(response.headers.get(\"content-length\"));\n\n if (Number.isFinite(length) && length > 0 && length < MIN_BYTES) return false;\n\n return acceptsGzip(request);\n}\n\nfunction withEncoding(response: Response, body: BodyInit): Response {\n const headers = new Headers(response.headers);\n\n headers.set(\"Content-Encoding\", \"gzip\");\n headers.delete(\"Content-Length\");\n\n const vary = headers.get(\"Vary\");\n\n if (!vary) headers.set(\"Vary\", \"Accept-Encoding\");\n else if (!/\\baccept-encoding\\b/i.test(vary) && vary.trim() !== \"*\") headers.set(\"Vary\", vary + \", Accept-Encoding\");\n\n return new Response(body, { status: response.status, statusText: response.statusText, headers });\n}\n\n/**\n * The response, gzipped if the request accepts it and the answer is worth it.\n *\n * `storedKey` names an answer the build wrote - the same bytes for every\n * visitor - so it is compressed once and kept; everything else is streamed\n * through the compressor as it is produced.\n */\nexport async function compressed(\n request: Request,\n response: Response,\n storedKey?: string,\n): Promise<Response> {\n if (!shouldCompress(request, response)) return response;\n\n const gzip = await deflate();\n\n if (!gzip) return response;\n\n if (storedKey !== undefined) {\n let bytes = stored.get(storedKey);\n\n if (!bytes) {\n bytes = await gzip.whole(new Uint8Array(await response.arrayBuffer()));\n\n if (stored.size >= STORED_CAP) stored.clear();\n\n stored.set(storedKey, bytes);\n }\n\n return withEncoding(response, bytes as unknown as BodyInit);\n }\n\n // A Response made from a string or JSON carries no Content-Length for the\n // check above to read, so the body is peeked: what arrives before the\n // first kilobyte decides. An answer that ends inside it - /api/health, a\n // 15-byte \"ok\" - goes out as it was; anything longer is compressed from\n // its first byte, the peeked part included, and keeps streaming.\n const reader = response.body!.getReader();\n const peeked: Uint8Array[] = [];\n let size = 0;\n let ended = false;\n\n while (size < MIN_BYTES) {\n const { done, value } = await reader.read();\n\n if (done) {\n ended = true;\n break;\n }\n\n peeked.push(value);\n size += value.byteLength;\n }\n\n if (ended) {\n return new Response(size === 0 ? null : (concat(peeked, size) as unknown as BodyInit), {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n });\n }\n\n const rest = new ReadableStream<Uint8Array>({\n start(controller) {\n for (const chunk of peeked) controller.enqueue(chunk);\n },\n async pull(controller) {\n const { done, value } = await reader.read();\n\n if (done) controller.close();\n else controller.enqueue(value);\n },\n cancel(reason) {\n return reader.cancel(reason);\n },\n });\n\n return withEncoding(response, gzip.stream(rest));\n}\n\nfunction concat(chunks: Uint8Array[], size: number): Uint8Array {\n const out = new Uint8Array(size);\n let at = 0;\n\n for (const chunk of chunks) {\n out.set(chunk, at);\n at += chunk.byteLength;\n }\n\n return out;\n}\n\n/** For a test, or a redeploy that reuses the process: forget the stored bytes. */\nexport function forgetCompressed(): void {\n stored.clear();\n}\n"]}
|
package/dist/events.d.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* An ordinary route.ts, so it lives beside its pages, runs the middleware.ts
|
|
12
12
|
* above it and takes the same params. What this adds is the framing - one
|
|
13
|
-
* `data:` line per yield, JSON - a keepalive comment every
|
|
13
|
+
* `data:` line per yield, JSON - a keepalive comment every eight seconds
|
|
14
14
|
* so a proxy does not drop an idle stream, the headers a stream needs, and
|
|
15
15
|
* an end to the generator when the browser goes away, which is what `signal`
|
|
16
16
|
* is for. `useEvents` on the client reads it.
|
|
@@ -44,8 +44,26 @@ declare const NAMED: unique symbol;
|
|
|
44
44
|
export declare function named<const E extends string, D>(event: E, data: D, options?: {
|
|
45
45
|
id?: string;
|
|
46
46
|
}): NamedEvent<E, D>;
|
|
47
|
-
/**
|
|
48
|
-
|
|
47
|
+
/**
|
|
48
|
+
* How often a comment goes out on an idle stream, so nothing between here
|
|
49
|
+
* and the browser closes it for being quiet.
|
|
50
|
+
*
|
|
51
|
+
* Under ten seconds because that is the shortest such limit a stream meets:
|
|
52
|
+
* Bun's server drops a connection idle for ten seconds by default, and at
|
|
53
|
+
* fifteen every stream it served was cut and reopened every twelve or so.
|
|
54
|
+
* Proxies allow longer - nginx sixty, Cloudflare a hundred.
|
|
55
|
+
*/
|
|
56
|
+
export declare const KEEPALIVE_MS = 8000;
|
|
57
|
+
/**
|
|
58
|
+
* The keepalive in use: `RSC_STREAM_KEEPALIVE_MS` when set, else the default.
|
|
59
|
+
*
|
|
60
|
+
* For a server or proxy with a shorter idle timeout than eight seconds - set
|
|
61
|
+
* it below the shortest one in front of the app. Too long, and every stream
|
|
62
|
+
* is cut and reopened each time that timeout passes: the browser reconnects
|
|
63
|
+
* and nothing is lost, but every tab churns. Read when a stream opens, so
|
|
64
|
+
* one build runs under any setting.
|
|
65
|
+
*/
|
|
66
|
+
export declare function keepaliveMs(): number;
|
|
49
67
|
/** One message, framed. */
|
|
50
68
|
export declare function frame(value: unknown): string;
|
|
51
69
|
/**
|
package/dist/events.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* An ordinary route.ts, so it lives beside its pages, runs the middleware.ts
|
|
12
12
|
* above it and takes the same params. What this adds is the framing - one
|
|
13
|
-
* `data:` line per yield, JSON - a keepalive comment every
|
|
13
|
+
* `data:` line per yield, JSON - a keepalive comment every eight seconds
|
|
14
14
|
* so a proxy does not drop an idle stream, the headers a stream needs, and
|
|
15
15
|
* an end to the generator when the browser goes away, which is what `signal`
|
|
16
16
|
* is for. `useEvents` on the client reads it.
|
|
@@ -30,8 +30,31 @@ const NAMED = Symbol.for("@rsc-kit/core.named-event");
|
|
|
30
30
|
export function named(event, data, options = {}) {
|
|
31
31
|
return { [NAMED]: true, event, id: options.id, data };
|
|
32
32
|
}
|
|
33
|
-
/**
|
|
34
|
-
|
|
33
|
+
/**
|
|
34
|
+
* How often a comment goes out on an idle stream, so nothing between here
|
|
35
|
+
* and the browser closes it for being quiet.
|
|
36
|
+
*
|
|
37
|
+
* Under ten seconds because that is the shortest such limit a stream meets:
|
|
38
|
+
* Bun's server drops a connection idle for ten seconds by default, and at
|
|
39
|
+
* fifteen every stream it served was cut and reopened every twelve or so.
|
|
40
|
+
* Proxies allow longer - nginx sixty, Cloudflare a hundred.
|
|
41
|
+
*/
|
|
42
|
+
export const KEEPALIVE_MS = 8_000;
|
|
43
|
+
/**
|
|
44
|
+
* The keepalive in use: `RSC_STREAM_KEEPALIVE_MS` when set, else the default.
|
|
45
|
+
*
|
|
46
|
+
* For a server or proxy with a shorter idle timeout than eight seconds - set
|
|
47
|
+
* it below the shortest one in front of the app. Too long, and every stream
|
|
48
|
+
* is cut and reopened each time that timeout passes: the browser reconnects
|
|
49
|
+
* and nothing is lost, but every tab churns. Read when a stream opens, so
|
|
50
|
+
* one build runs under any setting.
|
|
51
|
+
*/
|
|
52
|
+
export function keepaliveMs() {
|
|
53
|
+
const raw = globalThis.process?.env?.RSC_STREAM_KEEPALIVE_MS;
|
|
54
|
+
const value = Number(raw);
|
|
55
|
+
// A second is the floor: below it, the keepalive is the traffic.
|
|
56
|
+
return raw && Number.isFinite(value) && value >= 1_000 ? value : KEEPALIVE_MS;
|
|
57
|
+
}
|
|
35
58
|
function isNamed(value) {
|
|
36
59
|
return (typeof value === "object" &&
|
|
37
60
|
value !== null &&
|
|
@@ -49,7 +72,7 @@ export function frame(value) {
|
|
|
49
72
|
return lines.join("\n") + "\n\n";
|
|
50
73
|
}
|
|
51
74
|
export function events(produce, options = {}) {
|
|
52
|
-
const
|
|
75
|
+
const every = options.keepaliveMs ?? keepaliveMs();
|
|
53
76
|
return (request, input) => {
|
|
54
77
|
const controller = new AbortController();
|
|
55
78
|
const encoder = new TextEncoder();
|
|
@@ -68,7 +91,7 @@ export function events(produce, options = {}) {
|
|
|
68
91
|
catch {
|
|
69
92
|
// Closed between the check and the write; the loop below ends it.
|
|
70
93
|
}
|
|
71
|
-
},
|
|
94
|
+
}, every);
|
|
72
95
|
try {
|
|
73
96
|
for await (const value of produce({
|
|
74
97
|
...input,
|
|
@@ -103,7 +126,9 @@ export function events(produce, options = {}) {
|
|
|
103
126
|
return new Response(stream, {
|
|
104
127
|
headers: {
|
|
105
128
|
"Content-Type": "text/event-stream; charset=utf-8",
|
|
106
|
-
|
|
129
|
+
// no-transform: a proxy or CDN in front must not compress or buffer
|
|
130
|
+
// it either - Cloudflare compresses text unless told not to.
|
|
131
|
+
"Cache-Control": "no-store, no-transform",
|
|
107
132
|
// Nginx buffers by default, which turns a stream into one late burst.
|
|
108
133
|
"X-Accel-Buffering": "no",
|
|
109
134
|
},
|
package/dist/events.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A route that streams: server-sent events from an async generator.\n *\n * // app/api/orders/[id]/events/route.ts\n * export const GET = events(async function* ({ params, signal }) {\n * for await (const status of orderStatus((await params).id, { signal })) {\n * yield { status }\n * }\n * })\n *\n * An ordinary route.ts, so it lives beside its pages, runs the middleware.ts\n * above it and takes the same params. What this adds is the framing - one\n * `data:` line per yield, JSON - a keepalive comment every fifteen seconds\n * so a proxy does not drop an idle stream, the headers a stream needs, and\n * an end to the generator when the browser goes away, which is what `signal`\n * is for. `useEvents` on the client reads it.\n *\n * Not a query. A query answers once and is cacheable; a stream answers for\n * as long as the tab is open and is not. A server component cannot await a\n * stream, so it has no place in the payload either.\n */\n\nexport interface EventsInput<P = Record<string, string>> {\n params: Promise<P>;\n searchParams: Promise<URLSearchParams>;\n /** Aborted when the browser disconnects. Pass it on, or check `aborted`. */\n signal: AbortSignal;\n request: Request;\n}\n\n/** A message with a name or an id, for a client that listens by name or resumes. Made by `named()`. */\nexport interface NamedEvent<E extends string = string, D = unknown> {\n readonly [NAMED]: true;\n event?: E;\n id?: string;\n data: D;\n}\n\nconst NAMED = Symbol.for(\"@rsc-kit/core.named-event\");\n\n/**\n * A message with a name or an id: `yield named('paid', order)`, or\n * `named('paid', order, { id: order.id })` for one a client can resume from.\n * Built by a function rather than by shape, so a message that happens to\n * have a `data` key is still data, and the generator's own type stays the\n * type of what it yields.\n */\nexport function named<const E extends string, D>(\n event: E,\n data: D,\n options: { id?: string } = {},\n): NamedEvent<E, D> {\n return { [NAMED]: true, event, id: options.id, data };\n}\n\n/** How often a comment goes out on an idle stream, so a proxy keeps it open. */\nexport const KEEPALIVE_MS = 15_000;\n\nfunction isNamed(value: unknown): value is NamedEvent {\n return (\n typeof value === \"object\" &&\n value !== null &&\n (value as { [NAMED]?: true })[NAMED] === true\n );\n}\n\n/** One message, framed. */\nexport function frame(value: unknown): string {\n const named = isNamed(value) ? value : null;\n const lines: string[] = [];\n\n if (named?.event) lines.push(`event: ${named.event}`);\n if (named?.id) lines.push(`id: ${named.id}`);\n lines.push(`data: ${JSON.stringify(named ? named.data : value)}`);\n\n return lines.join(\"\\n\") + \"\\n\\n\";\n}\n\n/**\n * What `events()` returns: a GET handler that also says, as a type only, what\n * its generator yields. Nothing reads `~events` at runtime; the build records\n * it per url, which is how `useEvents('/api/orders/42/events')` types a\n * message without being told - and stops compiling when the route changes\n * what it sends.\n */\nexport type EventsRoute<P = Record<string, string>, Y = unknown> = ((\n request: Request,\n input: { params: Promise<P>; searchParams: Promise<URLSearchParams> },\n) => Response) & { readonly \"~events\"?: { yields: Y } };\n\n/** The unnamed messages among a stream's yields: what `useEvents` receives without `event`. */\nexport type MessageOf<Y> = Exclude<Y, NamedEvent<string, unknown>>;\n\n/** The data of the messages named `E`. A name computed at runtime matches any. */\nexport type NamedMessageOf<Y, E extends string> =\n Y extends NamedEvent<infer N, infer D> ? (E extends N ? D : never) : never;\n\nexport function events<P = Record<string, string>, Y = unknown>(\n produce: (input: EventsInput<P>) => AsyncIterable<Y>,\n options: { keepaliveMs?: number } = {},\n): EventsRoute<P, Y> {\n const keepaliveMs = options.keepaliveMs ?? KEEPALIVE_MS;\n\n return (\n request: Request,\n input: { params: Promise<P>; searchParams: Promise<URLSearchParams> },\n ) => {\n const controller = new AbortController();\n const encoder = new TextEncoder();\n\n request.signal.addEventListener(\"abort\", () => controller.abort(), {\n once: true,\n });\n\n const stream = new ReadableStream<Uint8Array>({\n async start(sink) {\n // The retry hint first, so a browser that loses the connection waits\n // a moment rather than hammering.\n sink.enqueue(encoder.encode(\"retry: 1000\\n\\n\"));\n\n const keepalive = setInterval(() => {\n try {\n sink.enqueue(encoder.encode(\": keepalive\\n\\n\"));\n } catch {\n // Closed between the check and the write; the loop below ends it.\n }\n }, keepaliveMs);\n\n try {\n for await (const value of produce({\n ...input,\n signal: controller.signal,\n request,\n })) {\n if (controller.signal.aborted) break;\n\n sink.enqueue(encoder.encode(frame(value)));\n }\n } catch (error) {\n if (!controller.signal.aborted) sink.error(error);\n } finally {\n clearInterval(keepalive);\n\n if (!controller.signal.aborted) {\n try {\n sink.close();\n } catch {\n // Already closed by the consumer.\n }\n }\n }\n },\n cancel() {\n controller.abort();\n },\n });\n\n return new Response(stream, {\n headers: {\n \"Content-Type\": \"text/event-stream; charset=utf-8\",\n \"Cache-Control\": \"no-store\",\n // Nginx buffers by default, which turns a stream into one late burst.\n \"X-Accel-Buffering\": \"no\",\n },\n });\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A route that streams: server-sent events from an async generator.\n *\n * // app/api/orders/[id]/events/route.ts\n * export const GET = events(async function* ({ params, signal }) {\n * for await (const status of orderStatus((await params).id, { signal })) {\n * yield { status }\n * }\n * })\n *\n * An ordinary route.ts, so it lives beside its pages, runs the middleware.ts\n * above it and takes the same params. What this adds is the framing - one\n * `data:` line per yield, JSON - a keepalive comment every eight seconds\n * so a proxy does not drop an idle stream, the headers a stream needs, and\n * an end to the generator when the browser goes away, which is what `signal`\n * is for. `useEvents` on the client reads it.\n *\n * Not a query. A query answers once and is cacheable; a stream answers for\n * as long as the tab is open and is not. A server component cannot await a\n * stream, so it has no place in the payload either.\n */\n\nexport interface EventsInput<P = Record<string, string>> {\n params: Promise<P>;\n searchParams: Promise<URLSearchParams>;\n /** Aborted when the browser disconnects. Pass it on, or check `aborted`. */\n signal: AbortSignal;\n request: Request;\n}\n\n/** A message with a name or an id, for a client that listens by name or resumes. Made by `named()`. */\nexport interface NamedEvent<E extends string = string, D = unknown> {\n readonly [NAMED]: true;\n event?: E;\n id?: string;\n data: D;\n}\n\nconst NAMED = Symbol.for(\"@rsc-kit/core.named-event\");\n\n/**\n * A message with a name or an id: `yield named('paid', order)`, or\n * `named('paid', order, { id: order.id })` for one a client can resume from.\n * Built by a function rather than by shape, so a message that happens to\n * have a `data` key is still data, and the generator's own type stays the\n * type of what it yields.\n */\nexport function named<const E extends string, D>(\n event: E,\n data: D,\n options: { id?: string } = {},\n): NamedEvent<E, D> {\n return { [NAMED]: true, event, id: options.id, data };\n}\n\n/**\n * How often a comment goes out on an idle stream, so nothing between here\n * and the browser closes it for being quiet.\n *\n * Under ten seconds because that is the shortest such limit a stream meets:\n * Bun's server drops a connection idle for ten seconds by default, and at\n * fifteen every stream it served was cut and reopened every twelve or so.\n * Proxies allow longer - nginx sixty, Cloudflare a hundred.\n */\nexport const KEEPALIVE_MS = 8_000;\n\n/**\n * The keepalive in use: `RSC_STREAM_KEEPALIVE_MS` when set, else the default.\n *\n * For a server or proxy with a shorter idle timeout than eight seconds - set\n * it below the shortest one in front of the app. Too long, and every stream\n * is cut and reopened each time that timeout passes: the browser reconnects\n * and nothing is lost, but every tab churns. Read when a stream opens, so\n * one build runs under any setting.\n */\nexport function keepaliveMs(): number {\n const raw = (\n globalThis as { process?: { env?: Record<string, string | undefined> } }\n ).process?.env?.RSC_STREAM_KEEPALIVE_MS;\n const value = Number(raw);\n\n // A second is the floor: below it, the keepalive is the traffic.\n return raw && Number.isFinite(value) && value >= 1_000 ? value : KEEPALIVE_MS;\n}\n\nfunction isNamed(value: unknown): value is NamedEvent {\n return (\n typeof value === \"object\" &&\n value !== null &&\n (value as { [NAMED]?: true })[NAMED] === true\n );\n}\n\n/** One message, framed. */\nexport function frame(value: unknown): string {\n const named = isNamed(value) ? value : null;\n const lines: string[] = [];\n\n if (named?.event) lines.push(`event: ${named.event}`);\n if (named?.id) lines.push(`id: ${named.id}`);\n lines.push(`data: ${JSON.stringify(named ? named.data : value)}`);\n\n return lines.join(\"\\n\") + \"\\n\\n\";\n}\n\n/**\n * What `events()` returns: a GET handler that also says, as a type only, what\n * its generator yields. Nothing reads `~events` at runtime; the build records\n * it per url, which is how `useEvents('/api/orders/42/events')` types a\n * message without being told - and stops compiling when the route changes\n * what it sends.\n */\nexport type EventsRoute<P = Record<string, string>, Y = unknown> = ((\n request: Request,\n input: { params: Promise<P>; searchParams: Promise<URLSearchParams> },\n) => Response) & { readonly \"~events\"?: { yields: Y } };\n\n/** The unnamed messages among a stream's yields: what `useEvents` receives without `event`. */\nexport type MessageOf<Y> = Exclude<Y, NamedEvent<string, unknown>>;\n\n/** The data of the messages named `E`. A name computed at runtime matches any. */\nexport type NamedMessageOf<Y, E extends string> =\n Y extends NamedEvent<infer N, infer D> ? (E extends N ? D : never) : never;\n\nexport function events<P = Record<string, string>, Y = unknown>(\n produce: (input: EventsInput<P>) => AsyncIterable<Y>,\n options: { keepaliveMs?: number } = {},\n): EventsRoute<P, Y> {\n const every = options.keepaliveMs ?? keepaliveMs();\n\n return (\n request: Request,\n input: { params: Promise<P>; searchParams: Promise<URLSearchParams> },\n ) => {\n const controller = new AbortController();\n const encoder = new TextEncoder();\n\n request.signal.addEventListener(\"abort\", () => controller.abort(), {\n once: true,\n });\n\n const stream = new ReadableStream<Uint8Array>({\n async start(sink) {\n // The retry hint first, so a browser that loses the connection waits\n // a moment rather than hammering.\n sink.enqueue(encoder.encode(\"retry: 1000\\n\\n\"));\n\n const keepalive = setInterval(() => {\n try {\n sink.enqueue(encoder.encode(\": keepalive\\n\\n\"));\n } catch {\n // Closed between the check and the write; the loop below ends it.\n }\n }, every);\n\n try {\n for await (const value of produce({\n ...input,\n signal: controller.signal,\n request,\n })) {\n if (controller.signal.aborted) break;\n\n sink.enqueue(encoder.encode(frame(value)));\n }\n } catch (error) {\n if (!controller.signal.aborted) sink.error(error);\n } finally {\n clearInterval(keepalive);\n\n if (!controller.signal.aborted) {\n try {\n sink.close();\n } catch {\n // Already closed by the consumer.\n }\n }\n }\n },\n cancel() {\n controller.abort();\n },\n });\n\n return new Response(stream, {\n headers: {\n \"Content-Type\": \"text/event-stream; charset=utf-8\",\n // no-transform: a proxy or CDN in front must not compress or buffer\n // it either - Cloudflare compresses text unless told not to.\n \"Cache-Control\": \"no-store, no-transform\",\n // Nginx buffers by default, which turns a stream into one late burst.\n \"X-Accel-Buffering\": \"no\",\n },\n });\n };\n}\n"]}
|
package/dist/js/refreshOn.d.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* What a refreshOn function is given: the page's params and search params,
|
|
4
|
+
* already awaited, beside whatever props the section was rendered with.
|
|
5
|
+
*/
|
|
6
|
+
export type RefreshOnInput<P> = Omit<P, "params" | "searchParams"> & {
|
|
7
|
+
params: Record<string, string>;
|
|
8
|
+
searchParams: URLSearchParams | Record<string, unknown>;
|
|
9
|
+
};
|
|
2
10
|
/** What a section or page refreshes on: names, or a function of its props that makes them. */
|
|
3
|
-
export type RefreshOnList<P> = string[] | ((
|
|
11
|
+
export type RefreshOnList<P> = string[] | ((input: RefreshOnInput<P>) => string[] | Promise<string[]>);
|
|
4
12
|
/**
|
|
5
13
|
* The names a region refreshes on, read for this render and signed for the tab.
|
|
6
14
|
*
|
package/dist/js/refreshOn.js
CHANGED
|
@@ -34,7 +34,18 @@ async function Resolve({ target, refreshOn, props, }) {
|
|
|
34
34
|
await connection();
|
|
35
35
|
// The page's params and searchParams, under whatever the page passed: a
|
|
36
36
|
// section rendered by its page is given nothing, and alone is given these.
|
|
37
|
-
const
|
|
37
|
+
const given = { ...currentPageProps(), ...props };
|
|
38
|
+
// Awaited here, so `({ params }) => [\`team:${params.team}\`]` - the way
|
|
39
|
+
// anyone writes it - means the team. A page's params and search params
|
|
40
|
+
// are promises, and read without awaiting they made `team:undefined`:
|
|
41
|
+
// one name for every team, so each refreshed on all the others' changes.
|
|
42
|
+
// Awaiting costs nothing here; the names are made per request anyway.
|
|
43
|
+
const input = {
|
|
44
|
+
...given,
|
|
45
|
+
params: ((await given.params) ?? {}),
|
|
46
|
+
searchParams: ((await given.searchParams) ??
|
|
47
|
+
new URLSearchParams()),
|
|
48
|
+
};
|
|
38
49
|
const named = typeof refreshOn === "function" ? await refreshOn(input) : refreshOn;
|
|
39
50
|
const unique = [...new Set(named)].filter((name) => typeof name === "string" && name !== "");
|
|
40
51
|
if (unique.length === 0)
|
package/dist/js/refreshOn.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"refreshOn.js","sourceRoot":"","sources":["../../src/js/refreshOn.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAEhD,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEjD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"refreshOn.js","sourceRoot":"","sources":["../../src/js/refreshOn.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Suspense, createElement } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { connection, currentPageProps } from \"../request\";\nimport { sign, versionSource } from \"../changed\";\nimport type { SignedName } from \"../changed\";\nimport { Changes } from \"./Changes\";\n\n/**\n * What a refreshOn function is given: the page's params and search params,\n * already awaited, beside whatever props the section was rendered with.\n */\nexport type RefreshOnInput<P> = Omit<P, \"params\" | \"searchParams\"> & {\n params: Record<string, string>;\n searchParams: URLSearchParams | Record<string, unknown>;\n};\n\n/** What a section or page refreshes on: names, or a function of its props that makes them. */\nexport type RefreshOnList<P> =\n string[] | ((input: RefreshOnInput<P>) => string[] | Promise<string[]>);\n\n/**\n * The names a region refreshes on, read for this render and signed for the tab.\n *\n * A server component, rendered beside the region: it works out the names\n * from the props, asks the source what version each is at, and hands them to\n * the client as `Changes`. Under its own Suspense boundary, so the ask never\n * holds up the region's paint - the data is what the visitor is waiting on,\n * not the version numbers behind it.\n *\n * Renders nothing when the source cannot answer - a backend that is down\n * for this one call. The region still renders; it just does not refresh on\n * its names until the next time it is rendered.\n */\nexport function RefreshOn<P>({\n target,\n refreshOn,\n props,\n}: {\n target: string;\n refreshOn: RefreshOnList<P>;\n props: P;\n}): ReactNode {\n return createElement(\n Suspense,\n { fallback: null },\n createElement(Resolve as never, { target, refreshOn, props }),\n );\n}\n\nconst warned = new Set<string>();\n\n/**\n * Never throws. An error here would become an error row inside the region's\n * payload, and the browser decodes that as the region failing - a blank\n * document for a version lookup that did not go through. Said once per\n * region instead, and the region renders without refreshing on anything.\n */\nasync function Resolve<P>({\n target,\n refreshOn,\n props,\n}: {\n target: string;\n refreshOn: RefreshOnList<P>;\n props: P;\n}): Promise<ReactNode> {\n try {\n // Per request, never at build. A version read at build is stale by the\n // time anyone loads the page, and a name signed at build is signed with\n // whatever key the build machine had. At build this suspends and the\n // names become a hole in the stored shell, filled per request.\n await connection();\n\n // The page's params and searchParams, under whatever the page passed: a\n // section rendered by its page is given nothing, and alone is given these.\n const given = { ...currentPageProps(), ...(props as object) } as Record<\n string,\n unknown\n >;\n\n // Awaited here, so `({ params }) => [\\`team:${params.team}\\`]` - the way\n // anyone writes it - means the team. A page's params and search params\n // are promises, and read without awaiting they made `team:undefined`:\n // one name for every team, so each refreshed on all the others' changes.\n // Awaiting costs nothing here; the names are made per request anyway.\n const input = {\n ...given,\n params: ((await given.params) ?? {}) as Record<string, string>,\n searchParams: ((await given.searchParams) ??\n new URLSearchParams()) as URLSearchParams,\n } as RefreshOnInput<P>;\n const named =\n typeof refreshOn === \"function\" ? await refreshOn(input) : refreshOn;\n const unique = [...new Set(named)].filter(\n (name) => typeof name === \"string\" && name !== \"\",\n );\n\n if (unique.length === 0) return null;\n\n // -1 is a version nothing is at, so every name comes back with its current version.\n const versions = await versionSource().changed(\n Object.fromEntries(unique.map((name) => [name, -1])),\n 0,\n );\n const signed: Record<string, SignedName> = {};\n\n for (const name of unique)\n signed[name] = [versions[name] ?? 0, await sign(name)];\n\n return createElement(Changes, { target, names: signed });\n } catch (error) {\n if (!warned.has(target)) {\n warned.add(target);\n console.warn(\n `[rsc-kit] what ${JSON.stringify(target)} refreshes on could not be read, so it will not: ` +\n (error instanceof Error ? error.message : String(error)),\n );\n }\n\n return null;\n }\n}\n"]}
|
package/dist/testHost.d.ts
CHANGED
|
@@ -14,11 +14,22 @@ export declare const HOST_MIDDLEWARE = "__rsc.middleware";
|
|
|
14
14
|
* `host` answers `__rsc.changed` the way an adapter does - holding the call up
|
|
15
15
|
* to `wait` for a name to move - and `changed` moves one.
|
|
16
16
|
*/
|
|
17
|
+
/**
|
|
18
|
+
* The test host's answer to `__rsc.changed`: given what any test host is
|
|
19
|
+
* given, answering a `ChangedAnswer`.
|
|
20
|
+
*
|
|
21
|
+
* Both halves matter. It takes `HostCallInput`, so an untyped host of
|
|
22
|
+
* `HostHandler`s accepts it; it returns a `ChangedAnswer`, so a host typed
|
|
23
|
+
* from the app's rsc-host.json - whose `__rsc.changed` must answer one -
|
|
24
|
+
* accepts it too. Typed as `HostHandler`, it returned `unknown`, and
|
|
25
|
+
* spreading it into a typed host did not compile.
|
|
26
|
+
*/
|
|
27
|
+
export interface ChangedHost {
|
|
28
|
+
[CHANGED_FUNCTION]: (call: HostCallInput) => Promise<ChangedAnswer>;
|
|
29
|
+
}
|
|
17
30
|
export declare function testChanges(): {
|
|
18
|
-
host:
|
|
19
|
-
|
|
20
|
-
};
|
|
21
|
-
changed: (...tags: string[]) => void;
|
|
31
|
+
host: ChangedHost;
|
|
32
|
+
changed: (...names: string[]) => void;
|
|
22
33
|
};
|
|
23
34
|
/** What a test handler is given: the call's arguments, and the headers the renderer forwarded. */
|
|
24
35
|
export interface HostCallInput {
|