@rsc-kit/core 0.30.45 → 0.30.47
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/changed.d.ts +2 -2
- package/dist/changed.js +3 -3
- package/dist/changed.js.map +1 -1
- package/dist/pageCache.d.ts +7 -0
- package/dist/pageCache.js +26 -4
- package/dist/pageCache.js.map +1 -1
- package/dist/purge.d.ts +9 -7
- package/dist/purge.js +31 -18
- package/dist/purge.js.map +1 -1
- package/package.json +1 -1
package/dist/changed.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* // and whatever changes it says so
|
|
10
10
|
* changed(`team:${team}:repos`) // here, from a route.ts or an action
|
|
11
|
-
*
|
|
11
|
+
* reg.Changed(ctx, "team:1:repos") // or the backend, from its webhook
|
|
12
12
|
* Rsc::changed("team:$team:repos"); // Laravel
|
|
13
13
|
*
|
|
14
14
|
* A name has a version: a number that moves whenever something says the name
|
|
@@ -271,7 +271,7 @@ export declare function versionSource(): VersionSource;
|
|
|
271
271
|
* }
|
|
272
272
|
*
|
|
273
273
|
* For an app that keeps its versions here. With a backend the versions are
|
|
274
|
-
* its, and so is the call: `
|
|
274
|
+
* its, and so is the call: `reg.Changed`, `Rsc::changed`.
|
|
275
275
|
*/
|
|
276
276
|
export declare function changed(...names: string[]): Promise<void>;
|
|
277
277
|
/** Thrown when a production server has no key to sign names with. */
|
package/dist/changed.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* // and whatever changes it says so
|
|
10
10
|
* changed(`team:${team}:repos`) // here, from a route.ts or an action
|
|
11
|
-
*
|
|
11
|
+
* reg.Changed(ctx, "team:1:repos") // or the backend, from its webhook
|
|
12
12
|
* Rsc::changed("team:$team:repos"); // Laravel
|
|
13
13
|
*
|
|
14
14
|
* A name has a version: a number that moves whenever something says the name
|
|
@@ -142,7 +142,7 @@ export function backendVersions(call) {
|
|
|
142
142
|
if (local)
|
|
143
143
|
return local.bump(names);
|
|
144
144
|
throw new Error("changed() bumps versions this process keeps, and a backend keeps these: say the name changed there - " +
|
|
145
|
-
"
|
|
145
|
+
"reg.Changed(ctx, ...) in Go, Rsc::changed(...) in Laravel.");
|
|
146
146
|
},
|
|
147
147
|
};
|
|
148
148
|
}
|
|
@@ -445,7 +445,7 @@ export function versionSource() {
|
|
|
445
445
|
* }
|
|
446
446
|
*
|
|
447
447
|
* For an app that keeps its versions here. With a backend the versions are
|
|
448
|
-
* its, and so is the call: `
|
|
448
|
+
* its, and so is the call: `reg.Changed`, `Rsc::changed`.
|
|
449
449
|
*/
|
|
450
450
|
export async function changed(...names) {
|
|
451
451
|
await versionSource().bump(names);
|
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 { frame, keepaliveMs } from \"./events.js\";\nimport { HEADER } from \"./headers.js\";\nimport { purgeLater } from \"./purge.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 - a counter is not a version.\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, health: ListenerHealth) => 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: Date.now() })\n * .onConflictDoUpdate({ target: rscVersions.name,\n * set: { version: sql`GREATEST(${rscVersions.version} + 1, excluded.version)` } }) },\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 * // nextVersion in one step: the larger of one past the old value and now.\n * bump: async (names) => { await Promise.all(names.map((n) => redis.eval(\n * \"local v = math.max(tonumber(redis.call('GET', KEYS[1]) or '0') + 1, tonumber(ARGV[1])) \" +\n * \"redis.call('SET', KEYS[1], v) return v\", 1, k(n), Date.now()))) },\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. Required, not a\n * suggestion: a version must never repeat, which is what makes it safe to\n * delete a name, and a counter repeats the moment one is 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 * Given `health` too: a store that finds it hears nothing - a Postgres\n * listener behind a pooler in transaction mode - calls `health.deaf(why)`,\n * and rsc-kit says so and goes back to reading every second.\n */\n listen?(wake: () => void, health: ListenerHealth): 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 let listening = Boolean(store.listen);\n const poll = () => options.poll ?? (listening ? 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) {\n const listen = store.listen.bind(store);\n\n wakeOn((wake, health) =>\n listen(wake, {\n deaf: (reason) => {\n listening = false;\n health.deaf(reason);\n },\n }),\n );\n }\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 and Bun.sql have it; with it, a waiting ask hears another instance at once. */\n listen?(channel: string, onNotify: (payload?: string) => 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: {\n table?: string;\n channel?: string;\n poll?: number;\n /**\n * A client to listen on, when `sql` goes through a connection pooler in\n * transaction mode - PgBouncer, or DigitalOcean's, Supabase's or Neon's\n * pooled connection - which hands the server connection back after each\n * statement, so a LISTEN on it hears nothing. A direct or session-mode\n * connection; reads and writes stay on `sql`. One connection per process.\n */\n listenWith?: PostgresClient;\n /**\n * `false`: do not listen - read every second instead, on purpose. For a\n * database whose direct connections are too few to spend one per process\n * on, behind a pooler that cannot listen. No listener, so no probe and no\n * warning about one. A change made in this process still reaches its own\n * tabs at once; one made elsewhere within about a second.\n */\n listen?: boolean;\n } = {},\n): PrunableVersions {\n const channel = options.channel ?? \"rsc_versions\";\n const listener = options.listenWith ?? sql;\n const listenOn =\n options.listen === false ? undefined : listener.listen?.bind(listener);\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: listenOn\n ? async (wake, health) => {\n // A probe, sent the way a change is - through `sql` - and listened\n // for on the listening connection. Behind a transaction-mode pooler\n // the LISTEN lands on a connection the pooler takes back, sending\n // still works, and nothing says the listener is deaf: changes from\n // other instances would wait for the safety-net read. The probe\n // not arriving is how that is found out - once, as listening starts.\n const probe = \"rsc-kit:probe:\" + crypto.randomUUID();\n let heard = false;\n\n await listenOn(channel, (payload) => {\n if (payload === probe) heard = true;\n else wake();\n });\n await sql.unsafe(\"SELECT pg_notify($1, $2)\", [channel, probe]);\n\n const timer = setTimeout(() => {\n if (heard) return;\n\n health.deaf(\n \"listening on Postgres receives no notifications - is the connection going through a pooler \" +\n \"in transaction mode (PgBouncer; DigitalOcean, Supabase or Neon pooled connections)? \" +\n \"Pass a direct or session-mode connection: postgresVersions(sql, { listenWith }). \" +\n \"Reading every second meanwhile.\",\n );\n }, LISTEN_PROBE_MS);\n\n (timer as { unref?: () => void }).unref?.();\n }\n : undefined,\n });\n}\n\n/** How long a listener's probe may take to come back before it counts as deaf. */\nexport const LISTEN_PROBE_MS = 5_000;\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(\n source: VersionSource | (() => VersionSource) | null,\n): 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 // A factory, made into the store on first use: a store made in register()\n // would make its database client in the build too, which runs register()\n // without the database's settings.\n if (typeof globals[SOURCE] === \"function\")\n globals[SOURCE] = (globals[SOURCE] as () => VersionSource)();\n\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, nor make the answers it \" +\n \"keeps with cache() tags miss before they expire. 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/live-data#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 // A CDN keeping pages tagged with these names drops them, once the answer\n // has gone - when the app installed a purge, or runs on Homeport.\n purgeLater(names);\n // On Workers with a hub, the tabs are held by the hub, not this isolate:\n // tell it, so they hear now rather than at the hub's next read.\n await pokeHub();\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 /** The key RSC_SIGNING_SECRET_PREVIOUS makes, for checking only; null when there is none. */\n previous?: Promise<CryptoKey | null> | null;\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.previous = 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\n/**\n * The key from RSC_SIGNING_SECRET_PREVIOUS: the secret before a rotation,\n * accepted when checking a signature and never used to make one.\n *\n * Rotating the secret otherwise refused every tab already open - its names\n * were signed with the old key - and they stopped refreshing until reloaded.\n * With the old secret here for a while, pages rendered after the rotation\n * carry new signatures and the tabs from before keep working until they are\n * closed. Remove it once those have gone; a day is plenty.\n */\nfunction previousKey(): Promise<CryptoKey | null> {\n const state = keying();\n\n return (state.previous ??= (async () => {\n const secret = env(\"RSC_SIGNING_SECRET_PREVIOUS\");\n\n if (!secret) return null;\n\n return await crypto.subtle.importKey(\n \"raw\",\n new TextEncoder().encode(secret),\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"],\n );\n })());\n}\n\nasync function signWith(keyed: CryptoKey, name: string): Promise<string> {\n const mac = await crypto.subtle.sign(\"HMAC\", keyed, new TextEncoder().encode(name));\n\n return base64url(new Uint8Array(mac).slice(0, 16));\n}\n\nfunction sameSignature(expected: string, signature: string): boolean {\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\nasync function verify(name: string, signature: string): Promise<boolean> {\n if (sameSignature(await sign(name), signature)) return true;\n\n // Signed before a rotation: the old secret, if it is still accepted.\n const previous = await previousKey();\n\n return previous !== null && sameSignature(await signWith(previous, name), signature);\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, health: ListenerHealth) => 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 */\n/** What a listener is told besides how to wake: how to say it hears nothing. */\nexport interface ListenerHealth {\n /** This listener will never wake anything: say why, once, and stop counting on it. */\n deaf(reason: string): void;\n}\n\nexport function wakeOn(\n listen: (wake: () => void, health: ListenerHealth) => unknown,\n): 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(\n listen: (wake: () => void, health: ListenerHealth) => unknown,\n failures = 0,\n): void {\n const health: ListenerHealth = {\n deaf: (reason) => {\n const state = wakers();\n const at = state.listens.indexOf(listen);\n\n // No longer counted: the renderer goes back to asking on the shorter\n // interval, as it does with nothing to wake it.\n if (at === -1) return;\n state.listens.splice(at, 1);\n console.warn(\"[rsc-kit] \" + reason);\n },\n };\n\n void Promise.resolve()\n .then(() => listen(signal, health))\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 warnedHub = false;\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\n// ── The hub ──────────────────────────────────────────────────────────────────\n//\n// On Workers each isolate watches for the tabs it happens to hold, which is\n// often one: an ask per tab per interval, a listener per isolate. A Durable\n// Object is one place every isolate can reach. With RSC_CHANGES_HUB naming\n// its binding, every tab's stream is forwarded to one instance of it, which\n// runs the shared ask above for all of them - one ask an interval for the\n// whole deployment, one listener, and a changed() anywhere wakes it at once.\n//\n// Opt-in, by name: the binding is the app's, declared in its wrangler config,\n// and nothing here guesses that a Durable Object is there to use.\n\n/** Marks a request the hub sent itself; its value is signed, so a browser cannot. */\nconst HUB_HEADER = \"x-rsc-changes-hub\";\n/** The one instance every isolate's streams go to. */\nconst HUB_INSTANCE = \"rsc-kit-changes\";\n/** What the header's value is the signature of. */\nconst HUB_PROOF = \"rsc-kit:changes-hub\";\n\ninterface HubNamespace {\n idFromName(name: string): unknown;\n get(id: unknown): { fetch(request: Request): Promise<Response> };\n}\n\nlet warnedHub = false;\n\n/** The hub's stub, when RSC_CHANGES_HUB names a Durable Object binding; null otherwise. */\nasync function hubStub(\n request?: Request,\n): Promise<{ fetch(request: Request): Promise<Response> } | null> {\n const binding = env(\"RSC_CHANGES_HUB\");\n\n if (!binding) return null;\n\n let bindings = (\n request as { runtime?: { cloudflare?: { env?: Record<string, unknown> } } } | undefined\n )?.runtime?.cloudflare?.env;\n\n // Nitro's Workers entry keeps the bindings here for code with no request -\n // changed() from a webhook's handler.\n bindings ??= (globalThis as { __env__?: Record<string, unknown> }).__env__;\n\n if (!bindings) {\n try {\n // Computed, so neither the bundler nor tsc looks for a module only the\n // Workers runtime has.\n const specifier = \"cloudflare:workers\";\n\n bindings = ((await import(/* @vite-ignore */ specifier)) as { env: Record<string, unknown> }).env;\n } catch {\n bindings = undefined;\n }\n }\n\n const namespace = bindings?.[binding] as HubNamespace | undefined;\n\n if (!namespace || typeof namespace.idFromName !== \"function\") {\n if (!warnedHub) {\n warnedHub = true;\n console.warn(\n `[rsc-kit] RSC_CHANGES_HUB names ${JSON.stringify(binding)}, which is not a Durable Object binding ` +\n \"here: each isolate watches for its own tabs. Declare the binding in wrangler config: \" +\n \"https://docs.rsc-kit.dev/guides/live-data#one-hub-on-workers\",\n );\n }\n\n return null;\n }\n\n return namespace.get(namespace.idFromName(HUB_INSTANCE));\n}\n\n/** Whether this request came from an isolate forwarding to the hub. */\nasync function fromIsolate(request: Request): Promise<boolean> {\n const proof = request.headers.get(HUB_HEADER);\n\n return proof !== null && (await verify(HUB_PROOF, proof));\n}\n\n/** Tell the hub something changed, so the tabs it holds hear at once. */\nasync function pokeHub(): Promise<void> {\n let stub: Awaited<ReturnType<typeof hubStub>>;\n\n try {\n stub = await hubStub();\n } catch {\n return;\n }\n\n if (!stub) return;\n\n try {\n await stub.fetch(\n new Request(\"https://hub\" + HEADER.changesPath, {\n method: \"POST\",\n headers: { [HUB_HEADER]: await sign(HUB_PROOF) },\n }),\n );\n } catch (error) {\n // The change is in the store; the hub reads it at its next ask.\n if (!warnedHub) {\n warnedHub = true;\n console.warn(\n \"[rsc-kit] telling the changes hub failed; its tabs hear at its next read: \" +\n (error instanceof Error ? error.message : String(error)),\n );\n }\n }\n}\n\n/**\n * The changes endpoint: a tab's stream here, or - with a hub - in the hub,\n * where every tab's is. The hub itself answers what isolates forward to it:\n * a stream to hold, or a poke to ask now.\n */\nexport async function serveChanges(request: Request): Promise<Response> {\n // Forwarded, but not with this key: never forwarded again, which would be\n // a loop - the hub reaching itself - and never served as if from the hub.\n if (request.headers.has(HUB_HEADER) && !(await fromIsolate(request))) {\n return new Response(\"Not from this deployment's isolates.\", { status: 403 });\n }\n\n if (request.headers.has(HUB_HEADER)) {\n if (request.method === \"POST\") {\n signal();\n\n return new Response(null, { status: 204 });\n }\n\n return await changes(request);\n }\n\n if (request.method !== \"GET\") return new Response(\"Method not allowed.\", { status: 405 });\n\n const stub = await hubStub(request);\n\n if (!stub) return await changes(request);\n\n const headers = new Headers(request.headers);\n\n headers.set(HUB_HEADER, await sign(HUB_PROOF));\n\n const held = await stub.fetch(new Request(request.url, { method: \"GET\", headers, signal: request.signal }));\n\n // A stub's response has immutable headers, and the response is still the\n // app's to finish - the server adds its own on the way out.\n return new Response(held.body, held);\n}\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 * reg.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 { purgeLater } from \"./purge.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 - a counter is not a version.\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 \"reg.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, health: ListenerHealth) => 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: Date.now() })\n * .onConflictDoUpdate({ target: rscVersions.name,\n * set: { version: sql`GREATEST(${rscVersions.version} + 1, excluded.version)` } }) },\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 * // nextVersion in one step: the larger of one past the old value and now.\n * bump: async (names) => { await Promise.all(names.map((n) => redis.eval(\n * \"local v = math.max(tonumber(redis.call('GET', KEYS[1]) or '0') + 1, tonumber(ARGV[1])) \" +\n * \"redis.call('SET', KEYS[1], v) return v\", 1, k(n), Date.now()))) },\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. Required, not a\n * suggestion: a version must never repeat, which is what makes it safe to\n * delete a name, and a counter repeats the moment one is 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 * Given `health` too: a store that finds it hears nothing - a Postgres\n * listener behind a pooler in transaction mode - calls `health.deaf(why)`,\n * and rsc-kit says so and goes back to reading every second.\n */\n listen?(wake: () => void, health: ListenerHealth): 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 let listening = Boolean(store.listen);\n const poll = () => options.poll ?? (listening ? 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) {\n const listen = store.listen.bind(store);\n\n wakeOn((wake, health) =>\n listen(wake, {\n deaf: (reason) => {\n listening = false;\n health.deaf(reason);\n },\n }),\n );\n }\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 and Bun.sql have it; with it, a waiting ask hears another instance at once. */\n listen?(channel: string, onNotify: (payload?: string) => 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: {\n table?: string;\n channel?: string;\n poll?: number;\n /**\n * A client to listen on, when `sql` goes through a connection pooler in\n * transaction mode - PgBouncer, or DigitalOcean's, Supabase's or Neon's\n * pooled connection - which hands the server connection back after each\n * statement, so a LISTEN on it hears nothing. A direct or session-mode\n * connection; reads and writes stay on `sql`. One connection per process.\n */\n listenWith?: PostgresClient;\n /**\n * `false`: do not listen - read every second instead, on purpose. For a\n * database whose direct connections are too few to spend one per process\n * on, behind a pooler that cannot listen. No listener, so no probe and no\n * warning about one. A change made in this process still reaches its own\n * tabs at once; one made elsewhere within about a second.\n */\n listen?: boolean;\n } = {},\n): PrunableVersions {\n const channel = options.channel ?? \"rsc_versions\";\n const listener = options.listenWith ?? sql;\n const listenOn =\n options.listen === false ? undefined : listener.listen?.bind(listener);\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: listenOn\n ? async (wake, health) => {\n // A probe, sent the way a change is - through `sql` - and listened\n // for on the listening connection. Behind a transaction-mode pooler\n // the LISTEN lands on a connection the pooler takes back, sending\n // still works, and nothing says the listener is deaf: changes from\n // other instances would wait for the safety-net read. The probe\n // not arriving is how that is found out - once, as listening starts.\n const probe = \"rsc-kit:probe:\" + crypto.randomUUID();\n let heard = false;\n\n await listenOn(channel, (payload) => {\n if (payload === probe) heard = true;\n else wake();\n });\n await sql.unsafe(\"SELECT pg_notify($1, $2)\", [channel, probe]);\n\n const timer = setTimeout(() => {\n if (heard) return;\n\n health.deaf(\n \"listening on Postgres receives no notifications - is the connection going through a pooler \" +\n \"in transaction mode (PgBouncer; DigitalOcean, Supabase or Neon pooled connections)? \" +\n \"Pass a direct or session-mode connection: postgresVersions(sql, { listenWith }). \" +\n \"Reading every second meanwhile.\",\n );\n }, LISTEN_PROBE_MS);\n\n (timer as { unref?: () => void }).unref?.();\n }\n : undefined,\n });\n}\n\n/** How long a listener's probe may take to come back before it counts as deaf. */\nexport const LISTEN_PROBE_MS = 5_000;\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(\n source: VersionSource | (() => VersionSource) | null,\n): 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 // A factory, made into the store on first use: a store made in register()\n // would make its database client in the build too, which runs register()\n // without the database's settings.\n if (typeof globals[SOURCE] === \"function\")\n globals[SOURCE] = (globals[SOURCE] as () => VersionSource)();\n\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, nor make the answers it \" +\n \"keeps with cache() tags miss before they expire. 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/live-data#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: `reg.Changed`, `Rsc::changed`.\n */\nexport async function changed(...names: string[]): Promise<void> {\n await versionSource().bump(names);\n // A CDN keeping pages tagged with these names drops them, once the answer\n // has gone - when the app installed a purge, or runs on Homeport.\n purgeLater(names);\n // On Workers with a hub, the tabs are held by the hub, not this isolate:\n // tell it, so they hear now rather than at the hub's next read.\n await pokeHub();\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 /** The key RSC_SIGNING_SECRET_PREVIOUS makes, for checking only; null when there is none. */\n previous?: Promise<CryptoKey | null> | null;\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.previous = 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\n/**\n * The key from RSC_SIGNING_SECRET_PREVIOUS: the secret before a rotation,\n * accepted when checking a signature and never used to make one.\n *\n * Rotating the secret otherwise refused every tab already open - its names\n * were signed with the old key - and they stopped refreshing until reloaded.\n * With the old secret here for a while, pages rendered after the rotation\n * carry new signatures and the tabs from before keep working until they are\n * closed. Remove it once those have gone; a day is plenty.\n */\nfunction previousKey(): Promise<CryptoKey | null> {\n const state = keying();\n\n return (state.previous ??= (async () => {\n const secret = env(\"RSC_SIGNING_SECRET_PREVIOUS\");\n\n if (!secret) return null;\n\n return await crypto.subtle.importKey(\n \"raw\",\n new TextEncoder().encode(secret),\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"],\n );\n })());\n}\n\nasync function signWith(keyed: CryptoKey, name: string): Promise<string> {\n const mac = await crypto.subtle.sign(\"HMAC\", keyed, new TextEncoder().encode(name));\n\n return base64url(new Uint8Array(mac).slice(0, 16));\n}\n\nfunction sameSignature(expected: string, signature: string): boolean {\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\nasync function verify(name: string, signature: string): Promise<boolean> {\n if (sameSignature(await sign(name), signature)) return true;\n\n // Signed before a rotation: the old secret, if it is still accepted.\n const previous = await previousKey();\n\n return previous !== null && sameSignature(await signWith(previous, name), signature);\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, health: ListenerHealth) => 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 */\n/** What a listener is told besides how to wake: how to say it hears nothing. */\nexport interface ListenerHealth {\n /** This listener will never wake anything: say why, once, and stop counting on it. */\n deaf(reason: string): void;\n}\n\nexport function wakeOn(\n listen: (wake: () => void, health: ListenerHealth) => unknown,\n): 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(\n listen: (wake: () => void, health: ListenerHealth) => unknown,\n failures = 0,\n): void {\n const health: ListenerHealth = {\n deaf: (reason) => {\n const state = wakers();\n const at = state.listens.indexOf(listen);\n\n // No longer counted: the renderer goes back to asking on the shorter\n // interval, as it does with nothing to wake it.\n if (at === -1) return;\n state.listens.splice(at, 1);\n console.warn(\"[rsc-kit] \" + reason);\n },\n };\n\n void Promise.resolve()\n .then(() => listen(signal, health))\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 warnedHub = false;\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\n// ── The hub ──────────────────────────────────────────────────────────────────\n//\n// On Workers each isolate watches for the tabs it happens to hold, which is\n// often one: an ask per tab per interval, a listener per isolate. A Durable\n// Object is one place every isolate can reach. With RSC_CHANGES_HUB naming\n// its binding, every tab's stream is forwarded to one instance of it, which\n// runs the shared ask above for all of them - one ask an interval for the\n// whole deployment, one listener, and a changed() anywhere wakes it at once.\n//\n// Opt-in, by name: the binding is the app's, declared in its wrangler config,\n// and nothing here guesses that a Durable Object is there to use.\n\n/** Marks a request the hub sent itself; its value is signed, so a browser cannot. */\nconst HUB_HEADER = \"x-rsc-changes-hub\";\n/** The one instance every isolate's streams go to. */\nconst HUB_INSTANCE = \"rsc-kit-changes\";\n/** What the header's value is the signature of. */\nconst HUB_PROOF = \"rsc-kit:changes-hub\";\n\ninterface HubNamespace {\n idFromName(name: string): unknown;\n get(id: unknown): { fetch(request: Request): Promise<Response> };\n}\n\nlet warnedHub = false;\n\n/** The hub's stub, when RSC_CHANGES_HUB names a Durable Object binding; null otherwise. */\nasync function hubStub(\n request?: Request,\n): Promise<{ fetch(request: Request): Promise<Response> } | null> {\n const binding = env(\"RSC_CHANGES_HUB\");\n\n if (!binding) return null;\n\n let bindings = (\n request as { runtime?: { cloudflare?: { env?: Record<string, unknown> } } } | undefined\n )?.runtime?.cloudflare?.env;\n\n // Nitro's Workers entry keeps the bindings here for code with no request -\n // changed() from a webhook's handler.\n bindings ??= (globalThis as { __env__?: Record<string, unknown> }).__env__;\n\n if (!bindings) {\n try {\n // Computed, so neither the bundler nor tsc looks for a module only the\n // Workers runtime has.\n const specifier = \"cloudflare:workers\";\n\n bindings = ((await import(/* @vite-ignore */ specifier)) as { env: Record<string, unknown> }).env;\n } catch {\n bindings = undefined;\n }\n }\n\n const namespace = bindings?.[binding] as HubNamespace | undefined;\n\n if (!namespace || typeof namespace.idFromName !== \"function\") {\n if (!warnedHub) {\n warnedHub = true;\n console.warn(\n `[rsc-kit] RSC_CHANGES_HUB names ${JSON.stringify(binding)}, which is not a Durable Object binding ` +\n \"here: each isolate watches for its own tabs. Declare the binding in wrangler config: \" +\n \"https://docs.rsc-kit.dev/guides/live-data#one-hub-on-workers\",\n );\n }\n\n return null;\n }\n\n return namespace.get(namespace.idFromName(HUB_INSTANCE));\n}\n\n/** Whether this request came from an isolate forwarding to the hub. */\nasync function fromIsolate(request: Request): Promise<boolean> {\n const proof = request.headers.get(HUB_HEADER);\n\n return proof !== null && (await verify(HUB_PROOF, proof));\n}\n\n/** Tell the hub something changed, so the tabs it holds hear at once. */\nasync function pokeHub(): Promise<void> {\n let stub: Awaited<ReturnType<typeof hubStub>>;\n\n try {\n stub = await hubStub();\n } catch {\n return;\n }\n\n if (!stub) return;\n\n try {\n await stub.fetch(\n new Request(\"https://hub\" + HEADER.changesPath, {\n method: \"POST\",\n headers: { [HUB_HEADER]: await sign(HUB_PROOF) },\n }),\n );\n } catch (error) {\n // The change is in the store; the hub reads it at its next ask.\n if (!warnedHub) {\n warnedHub = true;\n console.warn(\n \"[rsc-kit] telling the changes hub failed; its tabs hear at its next read: \" +\n (error instanceof Error ? error.message : String(error)),\n );\n }\n }\n}\n\n/**\n * The changes endpoint: a tab's stream here, or - with a hub - in the hub,\n * where every tab's is. The hub itself answers what isolates forward to it:\n * a stream to hold, or a poke to ask now.\n */\nexport async function serveChanges(request: Request): Promise<Response> {\n // Forwarded, but not with this key: never forwarded again, which would be\n // a loop - the hub reaching itself - and never served as if from the hub.\n if (request.headers.has(HUB_HEADER) && !(await fromIsolate(request))) {\n return new Response(\"Not from this deployment's isolates.\", { status: 403 });\n }\n\n if (request.headers.has(HUB_HEADER)) {\n if (request.method === \"POST\") {\n signal();\n\n return new Response(null, { status: 204 });\n }\n\n return await changes(request);\n }\n\n if (request.method !== \"GET\") return new Response(\"Method not allowed.\", { status: 405 });\n\n const stub = await hubStub(request);\n\n if (!stub) return await changes(request);\n\n const headers = new Headers(request.headers);\n\n headers.set(HUB_HEADER, await sign(HUB_PROOF));\n\n const held = await stub.fetch(new Request(request.url, { method: \"GET\", headers, signal: request.signal }));\n\n // A stub's response has immutable headers, and the response is still the\n // app's to finish - the server adds its own on the way out.\n return new Response(held.body, held);\n}\n"]}
|
package/dist/pageCache.d.ts
CHANGED
|
@@ -34,6 +34,13 @@ export declare function noteReadsSoFar(entry: PageCaching): void;
|
|
|
34
34
|
* or a copy older than the page would ever have been kept for.
|
|
35
35
|
*/
|
|
36
36
|
export declare function publicCacheControl(revalidate: number, status: number): string;
|
|
37
|
+
/**
|
|
38
|
+
* The same lifetime for a CDN that reads CDN-Cache-Control (RFC 9213), which
|
|
39
|
+
* is meant for it alone: there max-age is the CDN's own, not a browser's. Some
|
|
40
|
+
* edges keep a page only on this header, and Cache-Control stays beside it for
|
|
41
|
+
* every cache that does not read it.
|
|
42
|
+
*/
|
|
43
|
+
export declare function cdnCacheControl(revalidate: number, status: number): string;
|
|
37
44
|
/**
|
|
38
45
|
* The tags of every cache() answer a kept page used, as a CDN reads them:
|
|
39
46
|
* `Cache-Tag: pages,post:1`. A purge by any of them drops the copy. A tag the
|
package/dist/pageCache.js
CHANGED
|
@@ -110,9 +110,20 @@ function bypass(response, body, entry, refused) {
|
|
|
110
110
|
headers.set("Cache-Control", PER_CLIENT);
|
|
111
111
|
headers.set("Cache-Status", `rsc-kit; fwd=bypass; detail=${detailOf(refused)}`);
|
|
112
112
|
headers.delete("ETag");
|
|
113
|
+
// A CDN that reads it reads it over Cache-Control, so a private answer never has it.
|
|
114
|
+
headers.delete("CDN-Cache-Control");
|
|
113
115
|
logRefusal(entry, refused);
|
|
114
116
|
return new Response(body, { status: response.status, statusText: response.statusText, headers });
|
|
115
117
|
}
|
|
118
|
+
/** How long a shared cache keeps a public answer, which both of its headers state. */
|
|
119
|
+
function sharedLifetime(revalidate, status) {
|
|
120
|
+
const notFound = status === 404;
|
|
121
|
+
const stale = notFound ? NOT_FOUND_LIMIT : STALE_WINDOW;
|
|
122
|
+
return {
|
|
123
|
+
fresh: notFound ? Math.min(revalidate, NOT_FOUND_LIMIT) : revalidate,
|
|
124
|
+
windows: `stale-while-revalidate=${stale}, stale-if-error=${stale}`,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
116
127
|
/**
|
|
117
128
|
* What a public answer says: kept by shared caches for N seconds, refreshed
|
|
118
129
|
* in the background for a day after that, never by the browser. A 404 for at
|
|
@@ -123,9 +134,18 @@ function bypass(response, body, entry, refused) {
|
|
|
123
134
|
* or a copy older than the page would ever have been kept for.
|
|
124
135
|
*/
|
|
125
136
|
export function publicCacheControl(revalidate, status) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
137
|
+
const { fresh, windows } = sharedLifetime(revalidate, status);
|
|
138
|
+
return `public, max-age=0, s-maxage=${fresh}, ${windows}`;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* The same lifetime for a CDN that reads CDN-Cache-Control (RFC 9213), which
|
|
142
|
+
* is meant for it alone: there max-age is the CDN's own, not a browser's. Some
|
|
143
|
+
* edges keep a page only on this header, and Cache-Control stays beside it for
|
|
144
|
+
* every cache that does not read it.
|
|
145
|
+
*/
|
|
146
|
+
export function cdnCacheControl(revalidate, status) {
|
|
147
|
+
const { fresh, windows } = sharedLifetime(revalidate, status);
|
|
148
|
+
return `max-age=${fresh}, ${windows}`;
|
|
129
149
|
}
|
|
130
150
|
/** A tag a Cache-Tag list can carry: visible ASCII, no comma, which separates them. */
|
|
131
151
|
const TAG = /^[\x21-\x2b\x2d-\x7e]+$/;
|
|
@@ -192,7 +212,9 @@ saidNotFound = () => false) {
|
|
|
192
212
|
return bypass(response, body, entry, refused);
|
|
193
213
|
const headers = new Headers(response.headers);
|
|
194
214
|
const etag = await etagOf(body);
|
|
195
|
-
|
|
215
|
+
const status = saidNotFound() ? 404 : response.status;
|
|
216
|
+
headers.set("Cache-Control", publicCacheControl(entry.revalidate, status));
|
|
217
|
+
headers.set("CDN-Cache-Control", cdnCacheControl(entry.revalidate, status));
|
|
196
218
|
headers.set("ETag", etag);
|
|
197
219
|
const tags = cacheTagHeader(cacheTagsRead());
|
|
198
220
|
if (tags)
|
package/dist/pageCache.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pageCache.js","sourceRoot":"","sources":["../src/pageCache.ts"],"names":[],"mappings":"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","sourcesContent":["// A page a shared cache may keep: `export const revalidate = N`.\n//\n// The page is rendered whole, judged, then sent. Public only when every check\n// passed for that render; otherwise `private, no-store`, exactly as before,\n// with `Cache-Status: rsc-kit; fwd=bypass; detail=<check>` saying which one\n// failed and a line in the log saying where. Cache-Control is the contract a\n// host caches by; Cache-Status is for counting and for the developer asking\n// why a page was not kept.\n//\n// The checks, in the order they can be made:\n//\n// 1. payload-url - a GET or HEAD, and a payload asked for at its own `?_rsc=<key>` url, and a\n// document never at one: a cache that ignores Vary keys on\n// the url alone, so either the other way round hands one\n// kind of answer to a request for the other\n// 2. the render read nothing that belongs to one visitor: cookies(),\n// headers(), request(), connection() - in the page, a layout, a\n// Suspense'd child, metadata or middleware.ts\n// 3. host-middleware - Laravel route middleware runs against the session\n// 4. set-cookie - nothing set a cookie\n// 5. nonce - no CSP nonce, which a shared copy would give everyone\n// 6. status - 200 or 404; a redirect, a 5xx or a render React reported\n// as failed inside the payload (failed-render) never is\n//\n// A public answer carries `Cache-Tag`: the tags of every cache() answer the\n// render used, so a CDN can purge the copy when one of them changes.\n//\n// Backends render as nobody - see renderAsNobody() - so a session cookie set\n// by the backend on every call does not make every page private.\n\nimport { HEADER, PER_CLIENT } from \"./headers.js\";\nimport { PAYLOAD_PARAM, payloadKey, withoutPayloadKey } from \"./js/payloadKey.js\";\nimport { cacheTagsRead, cacheTagVersionsRead, renderFailed, visitorReads } from \"./request.js\";\nimport type { CacheStore } from \"./cache.js\";\nimport { versionSource } from \"./changed.js\";\n\n/** What a request for a `revalidate` page carries from the moment it arrives. */\nexport interface PageCaching {\n /** Seconds a shared cache may keep the page. */\n revalidate: number;\n /** The url path, for the log line. */\n path: string;\n /** A check that failed before anything rendered, or after the page said notFound(). */\n refused: string | null;\n}\n\n/** A 404 is kept no longer than this, however long its page may be. */\nconst NOT_FOUND_LIMIT = 60;\n\n/** How long a cache may serve a stale page while one request refreshes it, or while the origin is failing. */\nconst STALE_WINDOW = 86_400;\n\nconst caching = new WeakMap<Request, PageCaching>();\n\n/**\n * Decide, for a request as it arrived, whether its answer may be judged at all.\n *\n * Null for anything but a GET or HEAD of a page that exports revalidate - the\n * answer is then what it always was. Keyed by the request the app sees, so the\n * entry in front, which renders a 404, finds it again.\n */\nexport function cachingFor(arrived: Request, revalidate: number | undefined): PageCaching | null {\n // A post is never kept, and its answer already says so.\n if (!revalidate || revalidate <= 0 || (arrived.method !== \"GET\" && arrived.method !== \"HEAD\")) return null;\n\n const url = new URL(arrived.url);\n const entry: PageCaching = { revalidate, path: url.pathname, refused: urlRefusal(arrived, url) };\n\n caching.set(withoutPayloadKey(arrived), entry);\n\n return entry;\n}\n\n/** What cachingFor() decided for this request, if anything. */\nexport function cachingOf(request: Request): PageCaching | null {\n return caching.get(withoutPayloadKey(request)) ?? null;\n}\n\n/**\n * Check 1. A payload answers at `?_rsc=<key>`, with the key its headers give;\n * a document never answers there. Either way round, a cache keying on the url\n * alone would hand a request one kind of answer for the other.\n */\nfunction urlRefusal(arrived: Request, url: URL): string | null {\n const key = url.searchParams.get(PAYLOAD_PARAM);\n const payload = arrived.headers.get(HEADER.rsc) !== null;\n\n if (payload ? key !== payloadKey(arrived.headers) : key !== null) return \"payload-url\";\n\n return null;\n}\n\n/**\n * Note what the render has read so far, for an answer that will be finished\n * somewhere else: the page said notFound(), and the entry in front renders\n * not-found.tsx in a scope of its own, where these reads are gone.\n */\nexport function noteReadsSoFar(entry: PageCaching): void {\n entry.refused ??= readRefusal();\n}\n\nfunction readRefusal(): string | null {\n if (renderFailed()) return \"failed-render\";\n\n const read = visitorReads()[0];\n\n return read ?? null;\n}\n\n/** The detail token a read is reported under: `cookies() in Header` is `cookies`. */\nfunction detailOf(refused: string): string {\n return refused.replace(/\\(.*$/, \"\").replace(/[^a-z-]/gi, \"-\");\n}\n\nconst logged = new Set<string>();\n\nconst REASONS: Record<string, string> = {\n \"payload-url\":\n \"a payload was asked for without its ?_rsc= key, or a document with one. The router always adds it; this was another client.\",\n \"host-middleware\": \"the route has host middleware, which runs against the visitor's session.\",\n \"set-cookie\": \"the answer set a cookie.\",\n nonce: \"the answer carries a CSP nonce, which a shared copy would give every visitor.\",\n status: \"only a 200 or a 404 is kept.\",\n \"failed-render\": \"React reported a failure inside the payload.\",\n};\n\n/** Once per page and reason: a page refused on every request says so once. */\nfunction logRefusal(entry: PageCaching, refused: string): void {\n const key = `${entry.path}\\n${refused}`;\n\n if (logged.has(key) || logged.size > 500) return;\n\n logged.add(key);\n\n // A read names what read, and where: `cookies() in Header`.\n const why = REASONS[refused] ?? `${refused} reads this visitor.`;\n\n console.warn(`[rsc-kit] ${entry.path} exports revalidate, and was sent private: ${why}`);\n}\n\nfunction bypass(response: Response, body: BodyInit | null, entry: PageCaching, refused: string): Response {\n const headers = new Headers(response.headers);\n\n headers.set(\"Cache-Control\", PER_CLIENT);\n headers.set(\"Cache-Status\", `rsc-kit; fwd=bypass; detail=${detailOf(refused)}`);\n headers.delete(\"ETag\");\n logRefusal(entry, refused);\n\n return new Response(body, { status: response.status, statusText: response.statusText, headers });\n}\n\n/**\n * What a public answer says: kept by shared caches for N seconds, refreshed\n * in the background for a day after that, never by the browser. A 404 for at\n * most a minute, so a scanner walking random urls cannot hold pages out.\n *\n * stale-if-error says the same window outright for an origin that is down,\n * erroring or mid-deploy: a cache left to choose may serve the error instead,\n * or a copy older than the page would ever have been kept for.\n */\nexport function publicCacheControl(revalidate: number, status: number): string {\n return status === 404\n ? `public, max-age=0, s-maxage=${Math.min(revalidate, NOT_FOUND_LIMIT)}, stale-while-revalidate=${NOT_FOUND_LIMIT}, stale-if-error=${NOT_FOUND_LIMIT}`\n : `public, max-age=0, s-maxage=${revalidate}, stale-while-revalidate=${STALE_WINDOW}, stale-if-error=${STALE_WINDOW}`;\n}\n\n/** A tag a Cache-Tag list can carry: visible ASCII, no comma, which separates them. */\nconst TAG = /^[\\x21-\\x2b\\x2d-\\x7e]+$/;\n\nlet warnedTag = false;\n\n/**\n * The tags of every cache() answer a kept page used, as a CDN reads them:\n * `Cache-Tag: pages,post:1`. A purge by any of them drops the copy. A tag the\n * header cannot carry is left out, and said once: the copy then goes stale on\n * its own time, not on that change.\n */\nexport function cacheTagHeader(tags: readonly string[]): string | null {\n const kept = tags.filter((tag) => TAG.test(tag));\n\n if (kept.length < tags.length && !warnedTag) {\n warnedTag = true;\n const bad = tags.find((tag) => !TAG.test(tag));\n console.warn(\n `[rsc-kit] cache() tag ${JSON.stringify(bad)} cannot go in a Cache-Tag header (a comma, a space or a non-ASCII letter), so a CDN cannot purge by it.`,\n );\n }\n\n return kept.length ? kept.join(\",\") : null;\n}\n\nasync function etagOf(body: ArrayBuffer): Promise<string> {\n const digest = new Uint8Array(await crypto.subtle.digest(\"SHA-256\", body));\n let hex = \"\";\n\n for (let i = 0; i < 12; i++) hex += digest[i].toString(16).padStart(2, \"0\");\n\n return `\"${hex}\"`;\n}\n\nfunction matches(ifNoneMatch: string | null, etag: string): boolean {\n if (!ifNoneMatch) return false;\n\n return ifNoneMatch\n .split(\",\")\n .map((tag) => tag.trim().replace(/^W\\//, \"\"))\n .some((tag) => tag === etag || tag === \"*\");\n}\n\n/**\n * Finish the answer to a `revalidate` page: rendered whole, then judged.\n *\n * Called inside the request's scope, after every header the request will get\n * is on the answer - cookies included - so what it judges is what is sent.\n * Reading the body to its end is what waits for the render: a Suspense'd\n * child that reads cookies() is still running when the headers exist.\n */\nexport async function finishCacheable(\n request: Request,\n response: Response,\n entry: PageCaching,\n /**\n * Whether the answer turned out to be not-found.tsx, asked once the body is\n * read: a page can say notFound() in the middle of its stream, after a 200\n * went out. A payload is a 200 either way - the router reads the page from\n * it - but a not-found one is kept no longer than a 404.\n */\n saidNotFound: () => boolean = () => false,\n): Promise<Response> {\n if (entry.refused) return bypass(response, response.body, entry, entry.refused);\n\n if (response.status !== 200 && response.status !== 404) return bypass(response, response.body, entry, \"status\");\n\n const type = response.headers.get(\"Content-Type\") ?? \"\";\n\n if (!type.startsWith(\"text/html\") && !type.startsWith(\"text/x-component\")) {\n return bypass(response, response.body, entry, \"status\");\n }\n\n const body = await response.arrayBuffer();\n const refused =\n readRefusal() ??\n (response.headers.getSetCookie().length > 0 ? \"set-cookie\" : null) ??\n (/'nonce-/.test(response.headers.get(\"Content-Security-Policy\") ?? \"\") ? \"nonce\" : null);\n\n if (refused) return bypass(response, body, entry, refused);\n\n const headers = new Headers(response.headers);\n const etag = await etagOf(body);\n\n headers.set(\"Cache-Control\", publicCacheControl(entry.revalidate, saidNotFound() ? 404 : response.status));\n headers.set(\"ETag\", etag);\n\n const tags = cacheTagHeader(cacheTagsRead());\n\n if (tags) headers.set(\"Cache-Tag\", tags);\n\n const kept: Keepable = { status: response.status, body, versions: cacheTagVersionsRead(), revalidate: entry.revalidate, notFound: saidNotFound() };\n\n if (response.status === 200 && matches(request.headers.get(\"If-None-Match\"), etag)) {\n headers.delete(\"Content-Length\");\n\n const answer = new Response(null, { status: 304, headers });\n\n keepable.set(answer, kept);\n\n return answer;\n }\n\n const answer = new Response(request.method === \"HEAD\" ? null : body, { status: response.status, statusText: response.statusText, headers });\n\n keepable.set(answer, kept);\n\n return answer;\n}\n\n// The engine's own copy of a public page: ISR on every host.\n//\n// A kept page is versioned, not purged. Its entry carries the version of\n// every tag its cache() answers were read under, and a lookup asks the\n// version source whether any moved since. changed(tag) bumps a version every\n// instance reads, so every pod misses at its next request with nothing sent\n// to it - and a render that was running when the change landed is stored\n// under the older version, so it is never served. Correctness needs only a\n// version source every instance shares; the store decides how often a page\n// is rendered, never whether a stale one is served.\n\n/** What finishCacheable() sent public, kept beside the answer for the page cache to store. */\ninterface Keepable {\n status: number;\n body: ArrayBuffer;\n versions: Record<string, number>;\n revalidate: number;\n /** A not-found page, kept no longer than a 404. */\n notFound: boolean;\n}\n\nconst keepable = new WeakMap<Response, Keepable>();\n\n/** A kept page, as the store holds it. */\ninterface KeptPage {\n /** Status. */\n s: number;\n /** Headers, as name and value pairs. */\n h: [string, string][];\n /** Body, as text: HTML and Flight both are. */\n b: string;\n /** Tag versions it was rendered under. */\n v: Record<string, number>;\n /** When it was rendered, in ms. */\n at: number;\n /** Seconds it is fresh for. */\n r: number;\n /** Seconds past that it may still be served while one render replaces it. */\n w: number;\n}\n\n/** Headers that belong to one answer, never to the kept page. */\nconst NOT_KEPT = new Set([\"date\", \"set-cookie\", \"content-length\", \"x-rsc-kit\", \"x-powered-by\", \"age\"]);\n\nconst utf8 = new TextDecoder(\"utf-8\", { fatal: true });\n\n/**\n * Pages kept in this process, by the size of what they hold: a page is tens\n * or hundreds of KB, so a count says nothing about memory. Least recently\n * used goes first.\n */\nexport function pageMemoryStore(maxBytes = 64 * 1024 * 1024): CacheStore {\n const entries = new Map<string, { value: string; until: number }>();\n let bytes = 0;\n\n const drop = (key: string) => {\n const entry = entries.get(key);\n\n if (!entry) return;\n\n entries.delete(key);\n bytes -= entry.value.length;\n };\n\n return {\n get(key) {\n const entry = entries.get(key);\n\n if (!entry) return undefined;\n\n if (entry.until <= Date.now()) {\n drop(key);\n\n return undefined;\n }\n\n // Re-inserted, so the Map's order is least recently used first.\n entries.delete(key);\n entries.set(key, entry);\n\n return entry.value;\n },\n set(key, value, ttl) {\n drop(key);\n\n if (value.length > maxBytes) return;\n\n entries.set(key, { value, until: Date.now() + ttl * 1000 });\n bytes += value.length;\n\n while (bytes > maxBytes) drop(entries.keys().next().value!);\n },\n };\n}\n\n/** A kept answer, and whether it is past its time: served, while one render replaces it. */\nexport interface KeptAnswer {\n response: Response;\n stale: boolean;\n}\n\nexport interface PageCache {\n /** The kept answer for this request, or null to render it now. */\n lookup(key: string, request: Request): Promise<KeptAnswer | null>;\n /** Whether a render for this key is running in this process. */\n busy(key: string): boolean;\n /** Keep the answer, when finishCacheable() sent it public. */\n keep(key: string, answer: Response): Promise<void>;\n /** Run one render per key at a time: a burst for a page nobody has kept waits for the first. */\n once<T>(key: string, render: () => Promise<T>): Promise<{ led: boolean; value?: T }>;\n}\n\nlet warnedPageStore = false;\n\n/** A store that fails is a render, not a broken page: said once. */\nfunction pageStoreFailed(error: unknown): void {\n if (warnedPageStore) return;\n warnedPageStore = true;\n console.warn(\"[rsc-kit] the page cache's store failed, so pages are being rendered each time instead:\", error);\n}\n\n/** The engine's page cache, over a store: the app's, or one of its own in memory. */\nexport function createPageCache(store: CacheStore): PageCache {\n const inFlight = new Map<string, Promise<unknown>>();\n\n return {\n async lookup(key, request) {\n let raw: string | null | undefined;\n\n try {\n raw = await store.get(`rsc-page|${key}`);\n } catch (error) {\n pageStoreFailed(error);\n\n return null;\n }\n\n if (!raw) return null;\n\n let kept: KeptPage;\n\n try {\n kept = JSON.parse(raw);\n } catch {\n return null;\n }\n\n const now = Date.now();\n const age = now - kept.at;\n\n // The store may keep it longer than asked; the age is checked here.\n // Past its time it is still served, for a while, as the stale copy a\n // render in the background replaces: no visitor waits on an expiry.\n if (age >= (kept.r + (kept.w ?? 0)) * 1000) return null;\n\n // A tag that moved is never served stale: whoever changed it is\n // looking for the change, so this request renders.\n if (Object.keys(kept.v).length > 0) {\n try {\n const moved = await versionSource().changed(kept.v, 0);\n\n if (Object.keys(moved).some((tag) => moved[tag] !== kept.v[tag])) return null;\n } catch {\n // Not knowing whether it changed is not knowing it did not.\n return null;\n }\n }\n\n const headers = new Headers(kept.h);\n\n headers.set(\"Age\", String(Math.floor((now - kept.at) / 1000)));\n\n const etag = headers.get(\"ETag\");\n\n const stale = age >= kept.r * 1000;\n\n if (kept.s === 200 && etag && matches(request.headers.get(\"If-None-Match\"), etag)) {\n return { response: new Response(null, { status: 304, headers }), stale };\n }\n\n return { response: new Response(request.method === \"HEAD\" ? null : kept.b, { status: kept.s, headers }), stale };\n },\n\n busy(key) {\n return inFlight.has(key);\n },\n\n async keep(key, answer) {\n const kept = keepable.get(answer);\n\n if (!kept) return;\n\n let body: string;\n\n try {\n body = utf8.decode(kept.body);\n } catch {\n // Not text: sent, never kept.\n return;\n }\n\n const headers: [string, string][] = [];\n\n answer.headers.forEach((value, name) => {\n if (!NOT_KEPT.has(name.toLowerCase())) headers.push([name, value]);\n });\n\n const notFound = kept.status === 404 || kept.notFound;\n const revalidate = notFound ? Math.min(kept.revalidate, NOT_FOUND_LIMIT) : kept.revalidate;\n // The same windows the answer's Cache-Control gives a CDN.\n const window = notFound ? NOT_FOUND_LIMIT : STALE_WINDOW;\n const page: KeptPage = { s: kept.status, h: headers, b: body, v: kept.versions, at: Date.now(), r: revalidate, w: window };\n\n try {\n await store.set(`rsc-page|${key}`, JSON.stringify(page), Math.ceil(revalidate + window));\n } catch (error) {\n pageStoreFailed(error);\n }\n },\n\n async once(key, render) {\n const pending = inFlight.get(key);\n\n if (pending) {\n await pending.catch(() => {});\n\n return { led: false };\n }\n\n const work = render();\n\n inFlight.set(key, work);\n\n try {\n return { led: true, value: await work };\n } finally {\n inFlight.delete(key);\n }\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"pageCache.js","sourceRoot":"","sources":["../src/pageCache.ts"],"names":[],"mappings":"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","sourcesContent":["// A page a shared cache may keep: `export const revalidate = N`.\n//\n// The page is rendered whole, judged, then sent. Public only when every check\n// passed for that render; otherwise `private, no-store`, exactly as before,\n// with `Cache-Status: rsc-kit; fwd=bypass; detail=<check>` saying which one\n// failed and a line in the log saying where. Cache-Control is the contract a\n// host caches by; Cache-Status is for counting and for the developer asking\n// why a page was not kept.\n//\n// The checks, in the order they can be made:\n//\n// 1. payload-url - a GET or HEAD, and a payload asked for at its own `?_rsc=<key>` url, and a\n// document never at one: a cache that ignores Vary keys on\n// the url alone, so either the other way round hands one\n// kind of answer to a request for the other\n// 2. the render read nothing that belongs to one visitor: cookies(),\n// headers(), request(), connection() - in the page, a layout, a\n// Suspense'd child, metadata or middleware.ts\n// 3. host-middleware - Laravel route middleware runs against the session\n// 4. set-cookie - nothing set a cookie\n// 5. nonce - no CSP nonce, which a shared copy would give everyone\n// 6. status - 200 or 404; a redirect, a 5xx or a render React reported\n// as failed inside the payload (failed-render) never is\n//\n// A public answer carries `Cache-Tag`: the tags of every cache() answer the\n// render used, so a CDN can purge the copy when one of them changes.\n//\n// Backends render as nobody - see renderAsNobody() - so a session cookie set\n// by the backend on every call does not make every page private.\n\nimport { HEADER, PER_CLIENT } from \"./headers.js\";\nimport { PAYLOAD_PARAM, payloadKey, withoutPayloadKey } from \"./js/payloadKey.js\";\nimport { cacheTagsRead, cacheTagVersionsRead, renderFailed, visitorReads } from \"./request.js\";\nimport type { CacheStore } from \"./cache.js\";\nimport { versionSource } from \"./changed.js\";\n\n/** What a request for a `revalidate` page carries from the moment it arrives. */\nexport interface PageCaching {\n /** Seconds a shared cache may keep the page. */\n revalidate: number;\n /** The url path, for the log line. */\n path: string;\n /** A check that failed before anything rendered, or after the page said notFound(). */\n refused: string | null;\n}\n\n/** A 404 is kept no longer than this, however long its page may be. */\nconst NOT_FOUND_LIMIT = 60;\n\n/** How long a cache may serve a stale page while one request refreshes it, or while the origin is failing. */\nconst STALE_WINDOW = 86_400;\n\nconst caching = new WeakMap<Request, PageCaching>();\n\n/**\n * Decide, for a request as it arrived, whether its answer may be judged at all.\n *\n * Null for anything but a GET or HEAD of a page that exports revalidate - the\n * answer is then what it always was. Keyed by the request the app sees, so the\n * entry in front, which renders a 404, finds it again.\n */\nexport function cachingFor(arrived: Request, revalidate: number | undefined): PageCaching | null {\n // A post is never kept, and its answer already says so.\n if (!revalidate || revalidate <= 0 || (arrived.method !== \"GET\" && arrived.method !== \"HEAD\")) return null;\n\n const url = new URL(arrived.url);\n const entry: PageCaching = { revalidate, path: url.pathname, refused: urlRefusal(arrived, url) };\n\n caching.set(withoutPayloadKey(arrived), entry);\n\n return entry;\n}\n\n/** What cachingFor() decided for this request, if anything. */\nexport function cachingOf(request: Request): PageCaching | null {\n return caching.get(withoutPayloadKey(request)) ?? null;\n}\n\n/**\n * Check 1. A payload answers at `?_rsc=<key>`, with the key its headers give;\n * a document never answers there. Either way round, a cache keying on the url\n * alone would hand a request one kind of answer for the other.\n */\nfunction urlRefusal(arrived: Request, url: URL): string | null {\n const key = url.searchParams.get(PAYLOAD_PARAM);\n const payload = arrived.headers.get(HEADER.rsc) !== null;\n\n if (payload ? key !== payloadKey(arrived.headers) : key !== null) return \"payload-url\";\n\n return null;\n}\n\n/**\n * Note what the render has read so far, for an answer that will be finished\n * somewhere else: the page said notFound(), and the entry in front renders\n * not-found.tsx in a scope of its own, where these reads are gone.\n */\nexport function noteReadsSoFar(entry: PageCaching): void {\n entry.refused ??= readRefusal();\n}\n\nfunction readRefusal(): string | null {\n if (renderFailed()) return \"failed-render\";\n\n const read = visitorReads()[0];\n\n return read ?? null;\n}\n\n/** The detail token a read is reported under: `cookies() in Header` is `cookies`. */\nfunction detailOf(refused: string): string {\n return refused.replace(/\\(.*$/, \"\").replace(/[^a-z-]/gi, \"-\");\n}\n\nconst logged = new Set<string>();\n\nconst REASONS: Record<string, string> = {\n \"payload-url\":\n \"a payload was asked for without its ?_rsc= key, or a document with one. The router always adds it; this was another client.\",\n \"host-middleware\": \"the route has host middleware, which runs against the visitor's session.\",\n \"set-cookie\": \"the answer set a cookie.\",\n nonce: \"the answer carries a CSP nonce, which a shared copy would give every visitor.\",\n status: \"only a 200 or a 404 is kept.\",\n \"failed-render\": \"React reported a failure inside the payload.\",\n};\n\n/** Once per page and reason: a page refused on every request says so once. */\nfunction logRefusal(entry: PageCaching, refused: string): void {\n const key = `${entry.path}\\n${refused}`;\n\n if (logged.has(key) || logged.size > 500) return;\n\n logged.add(key);\n\n // A read names what read, and where: `cookies() in Header`.\n const why = REASONS[refused] ?? `${refused} reads this visitor.`;\n\n console.warn(`[rsc-kit] ${entry.path} exports revalidate, and was sent private: ${why}`);\n}\n\nfunction bypass(response: Response, body: BodyInit | null, entry: PageCaching, refused: string): Response {\n const headers = new Headers(response.headers);\n\n headers.set(\"Cache-Control\", PER_CLIENT);\n headers.set(\"Cache-Status\", `rsc-kit; fwd=bypass; detail=${detailOf(refused)}`);\n headers.delete(\"ETag\");\n // A CDN that reads it reads it over Cache-Control, so a private answer never has it.\n headers.delete(\"CDN-Cache-Control\");\n logRefusal(entry, refused);\n\n return new Response(body, { status: response.status, statusText: response.statusText, headers });\n}\n\n/** How long a shared cache keeps a public answer, which both of its headers state. */\nfunction sharedLifetime(revalidate: number, status: number): { fresh: number; windows: string } {\n const notFound = status === 404;\n const stale = notFound ? NOT_FOUND_LIMIT : STALE_WINDOW;\n\n return {\n fresh: notFound ? Math.min(revalidate, NOT_FOUND_LIMIT) : revalidate,\n windows: `stale-while-revalidate=${stale}, stale-if-error=${stale}`,\n };\n}\n\n/**\n * What a public answer says: kept by shared caches for N seconds, refreshed\n * in the background for a day after that, never by the browser. A 404 for at\n * most a minute, so a scanner walking random urls cannot hold pages out.\n *\n * stale-if-error says the same window outright for an origin that is down,\n * erroring or mid-deploy: a cache left to choose may serve the error instead,\n * or a copy older than the page would ever have been kept for.\n */\nexport function publicCacheControl(revalidate: number, status: number): string {\n const { fresh, windows } = sharedLifetime(revalidate, status);\n\n return `public, max-age=0, s-maxage=${fresh}, ${windows}`;\n}\n\n/**\n * The same lifetime for a CDN that reads CDN-Cache-Control (RFC 9213), which\n * is meant for it alone: there max-age is the CDN's own, not a browser's. Some\n * edges keep a page only on this header, and Cache-Control stays beside it for\n * every cache that does not read it.\n */\nexport function cdnCacheControl(revalidate: number, status: number): string {\n const { fresh, windows } = sharedLifetime(revalidate, status);\n\n return `max-age=${fresh}, ${windows}`;\n}\n\n/** A tag a Cache-Tag list can carry: visible ASCII, no comma, which separates them. */\nconst TAG = /^[\\x21-\\x2b\\x2d-\\x7e]+$/;\n\nlet warnedTag = false;\n\n/**\n * The tags of every cache() answer a kept page used, as a CDN reads them:\n * `Cache-Tag: pages,post:1`. A purge by any of them drops the copy. A tag the\n * header cannot carry is left out, and said once: the copy then goes stale on\n * its own time, not on that change.\n */\nexport function cacheTagHeader(tags: readonly string[]): string | null {\n const kept = tags.filter((tag) => TAG.test(tag));\n\n if (kept.length < tags.length && !warnedTag) {\n warnedTag = true;\n const bad = tags.find((tag) => !TAG.test(tag));\n console.warn(\n `[rsc-kit] cache() tag ${JSON.stringify(bad)} cannot go in a Cache-Tag header (a comma, a space or a non-ASCII letter), so a CDN cannot purge by it.`,\n );\n }\n\n return kept.length ? kept.join(\",\") : null;\n}\n\nasync function etagOf(body: ArrayBuffer): Promise<string> {\n const digest = new Uint8Array(await crypto.subtle.digest(\"SHA-256\", body));\n let hex = \"\";\n\n for (let i = 0; i < 12; i++) hex += digest[i].toString(16).padStart(2, \"0\");\n\n return `\"${hex}\"`;\n}\n\nfunction matches(ifNoneMatch: string | null, etag: string): boolean {\n if (!ifNoneMatch) return false;\n\n return ifNoneMatch\n .split(\",\")\n .map((tag) => tag.trim().replace(/^W\\//, \"\"))\n .some((tag) => tag === etag || tag === \"*\");\n}\n\n/**\n * Finish the answer to a `revalidate` page: rendered whole, then judged.\n *\n * Called inside the request's scope, after every header the request will get\n * is on the answer - cookies included - so what it judges is what is sent.\n * Reading the body to its end is what waits for the render: a Suspense'd\n * child that reads cookies() is still running when the headers exist.\n */\nexport async function finishCacheable(\n request: Request,\n response: Response,\n entry: PageCaching,\n /**\n * Whether the answer turned out to be not-found.tsx, asked once the body is\n * read: a page can say notFound() in the middle of its stream, after a 200\n * went out. A payload is a 200 either way - the router reads the page from\n * it - but a not-found one is kept no longer than a 404.\n */\n saidNotFound: () => boolean = () => false,\n): Promise<Response> {\n if (entry.refused) return bypass(response, response.body, entry, entry.refused);\n\n if (response.status !== 200 && response.status !== 404) return bypass(response, response.body, entry, \"status\");\n\n const type = response.headers.get(\"Content-Type\") ?? \"\";\n\n if (!type.startsWith(\"text/html\") && !type.startsWith(\"text/x-component\")) {\n return bypass(response, response.body, entry, \"status\");\n }\n\n const body = await response.arrayBuffer();\n const refused =\n readRefusal() ??\n (response.headers.getSetCookie().length > 0 ? \"set-cookie\" : null) ??\n (/'nonce-/.test(response.headers.get(\"Content-Security-Policy\") ?? \"\") ? \"nonce\" : null);\n\n if (refused) return bypass(response, body, entry, refused);\n\n const headers = new Headers(response.headers);\n const etag = await etagOf(body);\n\n const status = saidNotFound() ? 404 : response.status;\n\n headers.set(\"Cache-Control\", publicCacheControl(entry.revalidate, status));\n headers.set(\"CDN-Cache-Control\", cdnCacheControl(entry.revalidate, status));\n headers.set(\"ETag\", etag);\n\n const tags = cacheTagHeader(cacheTagsRead());\n\n if (tags) headers.set(\"Cache-Tag\", tags);\n\n const kept: Keepable = { status: response.status, body, versions: cacheTagVersionsRead(), revalidate: entry.revalidate, notFound: saidNotFound() };\n\n if (response.status === 200 && matches(request.headers.get(\"If-None-Match\"), etag)) {\n headers.delete(\"Content-Length\");\n\n const answer = new Response(null, { status: 304, headers });\n\n keepable.set(answer, kept);\n\n return answer;\n }\n\n const answer = new Response(request.method === \"HEAD\" ? null : body, { status: response.status, statusText: response.statusText, headers });\n\n keepable.set(answer, kept);\n\n return answer;\n}\n\n// The engine's own copy of a public page: ISR on every host.\n//\n// A kept page is versioned, not purged. Its entry carries the version of\n// every tag its cache() answers were read under, and a lookup asks the\n// version source whether any moved since. changed(tag) bumps a version every\n// instance reads, so every pod misses at its next request with nothing sent\n// to it - and a render that was running when the change landed is stored\n// under the older version, so it is never served. Correctness needs only a\n// version source every instance shares; the store decides how often a page\n// is rendered, never whether a stale one is served.\n\n/** What finishCacheable() sent public, kept beside the answer for the page cache to store. */\ninterface Keepable {\n status: number;\n body: ArrayBuffer;\n versions: Record<string, number>;\n revalidate: number;\n /** A not-found page, kept no longer than a 404. */\n notFound: boolean;\n}\n\nconst keepable = new WeakMap<Response, Keepable>();\n\n/** A kept page, as the store holds it. */\ninterface KeptPage {\n /** Status. */\n s: number;\n /** Headers, as name and value pairs. */\n h: [string, string][];\n /** Body, as text: HTML and Flight both are. */\n b: string;\n /** Tag versions it was rendered under. */\n v: Record<string, number>;\n /** When it was rendered, in ms. */\n at: number;\n /** Seconds it is fresh for. */\n r: number;\n /** Seconds past that it may still be served while one render replaces it. */\n w: number;\n}\n\n/** Headers that belong to one answer, never to the kept page. */\nconst NOT_KEPT = new Set([\"date\", \"set-cookie\", \"content-length\", \"x-rsc-kit\", \"x-powered-by\", \"age\"]);\n\nconst utf8 = new TextDecoder(\"utf-8\", { fatal: true });\n\n/**\n * Pages kept in this process, by the size of what they hold: a page is tens\n * or hundreds of KB, so a count says nothing about memory. Least recently\n * used goes first.\n */\nexport function pageMemoryStore(maxBytes = 64 * 1024 * 1024): CacheStore {\n const entries = new Map<string, { value: string; until: number }>();\n let bytes = 0;\n\n const drop = (key: string) => {\n const entry = entries.get(key);\n\n if (!entry) return;\n\n entries.delete(key);\n bytes -= entry.value.length;\n };\n\n return {\n get(key) {\n const entry = entries.get(key);\n\n if (!entry) return undefined;\n\n if (entry.until <= Date.now()) {\n drop(key);\n\n return undefined;\n }\n\n // Re-inserted, so the Map's order is least recently used first.\n entries.delete(key);\n entries.set(key, entry);\n\n return entry.value;\n },\n set(key, value, ttl) {\n drop(key);\n\n if (value.length > maxBytes) return;\n\n entries.set(key, { value, until: Date.now() + ttl * 1000 });\n bytes += value.length;\n\n while (bytes > maxBytes) drop(entries.keys().next().value!);\n },\n };\n}\n\n/** A kept answer, and whether it is past its time: served, while one render replaces it. */\nexport interface KeptAnswer {\n response: Response;\n stale: boolean;\n}\n\nexport interface PageCache {\n /** The kept answer for this request, or null to render it now. */\n lookup(key: string, request: Request): Promise<KeptAnswer | null>;\n /** Whether a render for this key is running in this process. */\n busy(key: string): boolean;\n /** Keep the answer, when finishCacheable() sent it public. */\n keep(key: string, answer: Response): Promise<void>;\n /** Run one render per key at a time: a burst for a page nobody has kept waits for the first. */\n once<T>(key: string, render: () => Promise<T>): Promise<{ led: boolean; value?: T }>;\n}\n\nlet warnedPageStore = false;\n\n/** A store that fails is a render, not a broken page: said once. */\nfunction pageStoreFailed(error: unknown): void {\n if (warnedPageStore) return;\n warnedPageStore = true;\n console.warn(\"[rsc-kit] the page cache's store failed, so pages are being rendered each time instead:\", error);\n}\n\n/** The engine's page cache, over a store: the app's, or one of its own in memory. */\nexport function createPageCache(store: CacheStore): PageCache {\n const inFlight = new Map<string, Promise<unknown>>();\n\n return {\n async lookup(key, request) {\n let raw: string | null | undefined;\n\n try {\n raw = await store.get(`rsc-page|${key}`);\n } catch (error) {\n pageStoreFailed(error);\n\n return null;\n }\n\n if (!raw) return null;\n\n let kept: KeptPage;\n\n try {\n kept = JSON.parse(raw);\n } catch {\n return null;\n }\n\n const now = Date.now();\n const age = now - kept.at;\n\n // The store may keep it longer than asked; the age is checked here.\n // Past its time it is still served, for a while, as the stale copy a\n // render in the background replaces: no visitor waits on an expiry.\n if (age >= (kept.r + (kept.w ?? 0)) * 1000) return null;\n\n // A tag that moved is never served stale: whoever changed it is\n // looking for the change, so this request renders.\n if (Object.keys(kept.v).length > 0) {\n try {\n const moved = await versionSource().changed(kept.v, 0);\n\n if (Object.keys(moved).some((tag) => moved[tag] !== kept.v[tag])) return null;\n } catch {\n // Not knowing whether it changed is not knowing it did not.\n return null;\n }\n }\n\n const headers = new Headers(kept.h);\n\n headers.set(\"Age\", String(Math.floor((now - kept.at) / 1000)));\n\n const etag = headers.get(\"ETag\");\n\n const stale = age >= kept.r * 1000;\n\n if (kept.s === 200 && etag && matches(request.headers.get(\"If-None-Match\"), etag)) {\n return { response: new Response(null, { status: 304, headers }), stale };\n }\n\n return { response: new Response(request.method === \"HEAD\" ? null : kept.b, { status: kept.s, headers }), stale };\n },\n\n busy(key) {\n return inFlight.has(key);\n },\n\n async keep(key, answer) {\n const kept = keepable.get(answer);\n\n if (!kept) return;\n\n let body: string;\n\n try {\n body = utf8.decode(kept.body);\n } catch {\n // Not text: sent, never kept.\n return;\n }\n\n const headers: [string, string][] = [];\n\n answer.headers.forEach((value, name) => {\n if (!NOT_KEPT.has(name.toLowerCase())) headers.push([name, value]);\n });\n\n const notFound = kept.status === 404 || kept.notFound;\n const revalidate = notFound ? Math.min(kept.revalidate, NOT_FOUND_LIMIT) : kept.revalidate;\n // The same windows the answer's Cache-Control gives a CDN.\n const window = notFound ? NOT_FOUND_LIMIT : STALE_WINDOW;\n const page: KeptPage = { s: kept.status, h: headers, b: body, v: kept.versions, at: Date.now(), r: revalidate, w: window };\n\n try {\n await store.set(`rsc-page|${key}`, JSON.stringify(page), Math.ceil(revalidate + window));\n } catch (error) {\n pageStoreFailed(error);\n }\n },\n\n async once(key, render) {\n const pending = inFlight.get(key);\n\n if (pending) {\n await pending.catch(() => {});\n\n return { led: false };\n }\n\n const work = render();\n\n inFlight.set(key, work);\n\n try {\n return { led: true, value: await work };\n } finally {\n inFlight.delete(key);\n }\n },\n };\n}\n"]}
|
package/dist/purge.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A `revalidate` page goes out with `Cache-Tag`, and a CDN that purges by tag
|
|
5
5
|
* can drop it the moment one of those tags changes, rather than waiting out
|
|
6
|
-
* `
|
|
6
|
+
* its `max-age` in `CDN-Cache-Control`. What calls the purge is the app's: every CDN has its own API, so
|
|
7
7
|
* the engine has one hook, `installPurge()`, and `changed(tag)` calls it with
|
|
8
8
|
* the names that changed - after the answer has gone, through `after()`, so a
|
|
9
9
|
* slow CDN never holds up the action that made the change.
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* hook is `homeportPurge()` unless the app installed another.
|
|
13
13
|
*
|
|
14
14
|
* A Laravel or Go backend changes its names itself (`Rsc::changed()`,
|
|
15
|
-
* `
|
|
15
|
+
* `reg.Changed()`), and the engine never hears of it, so each backend
|
|
16
16
|
* purges for its own changes. This hook covers `changed()` called here.
|
|
17
17
|
*/
|
|
18
18
|
/** What a purge drops: pages carrying any of these tags, pages at these paths, or everything. */
|
|
@@ -36,10 +36,10 @@ export declare function purgeLater(tags: readonly string[]): void;
|
|
|
36
36
|
* Send a purge now, through the installed purge. For an app's own use - a
|
|
37
37
|
* deploy step, a webhook - as well as changed()'s. Never throws: the change
|
|
38
38
|
* it follows is already made, and a CDN that missed it still drops the copy
|
|
39
|
-
* at `
|
|
39
|
+
* at its `max-age` in `CDN-Cache-Control`. A failure is logged.
|
|
40
40
|
*/
|
|
41
41
|
export declare function purge(what: Purge): Promise<void>;
|
|
42
|
-
/** Homeport takes at most this many tags
|
|
42
|
+
/** Homeport takes at most this many tags and paths, together, per call. */
|
|
43
43
|
export declare const HOMEPORT_PURGE_LIMIT = 100;
|
|
44
44
|
export interface HomeportPurgeOptions {
|
|
45
45
|
/** Where to post. Default `HOMEPORT_PURGE_URL`, which Homeport sets. */
|
|
@@ -50,8 +50,10 @@ export interface HomeportPurgeOptions {
|
|
|
50
50
|
/**
|
|
51
51
|
* Homeport's purge: `POST {tags, paths, all}` to the url Homeport gives the
|
|
52
52
|
* app, no token - it is reachable only from inside the app's own network.
|
|
53
|
-
* Split into calls of 100 tags and
|
|
54
|
-
*
|
|
55
|
-
*
|
|
53
|
+
* Split into calls of 100 tags and paths together. A tag or path too long for
|
|
54
|
+
* it is left out: one would cost the whole call, and the edge keeps no page
|
|
55
|
+
* with a tag it could not purge. Only a 202 is accepted. A 429 is tried once
|
|
56
|
+
* more after its `Retry-After` (at most five seconds); anything else is that
|
|
57
|
+
* call lost. The rest are still sent, then the purge rejects.
|
|
56
58
|
*/
|
|
57
59
|
export declare function homeportPurge(options?: HomeportPurgeOptions): Purger;
|
package/dist/purge.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A `revalidate` page goes out with `Cache-Tag`, and a CDN that purges by tag
|
|
5
5
|
* can drop it the moment one of those tags changes, rather than waiting out
|
|
6
|
-
* `
|
|
6
|
+
* its `max-age` in `CDN-Cache-Control`. What calls the purge is the app's: every CDN has its own API, so
|
|
7
7
|
* the engine has one hook, `installPurge()`, and `changed(tag)` calls it with
|
|
8
8
|
* the names that changed - after the answer has gone, through `after()`, so a
|
|
9
9
|
* slow CDN never holds up the action that made the change.
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* hook is `homeportPurge()` unless the app installed another.
|
|
13
13
|
*
|
|
14
14
|
* A Laravel or Go backend changes its names itself (`Rsc::changed()`,
|
|
15
|
-
* `
|
|
15
|
+
* `reg.Changed()`), and the engine never hears of it, so each backend
|
|
16
16
|
* purges for its own changes. This hook covers `changed()` called here.
|
|
17
17
|
*/
|
|
18
18
|
function hooks() {
|
|
@@ -71,12 +71,11 @@ export function purgeLater(tags) {
|
|
|
71
71
|
return purge({ tags: [...gathered] });
|
|
72
72
|
});
|
|
73
73
|
}
|
|
74
|
-
let warnedPurge = false;
|
|
75
74
|
/**
|
|
76
75
|
* Send a purge now, through the installed purge. For an app's own use - a
|
|
77
76
|
* deploy step, a webhook - as well as changed()'s. Never throws: the change
|
|
78
77
|
* it follows is already made, and a CDN that missed it still drops the copy
|
|
79
|
-
* at `
|
|
78
|
+
* at its `max-age` in `CDN-Cache-Control`. A failure is logged.
|
|
80
79
|
*/
|
|
81
80
|
export async function purge(what) {
|
|
82
81
|
const purger = installedPurge();
|
|
@@ -86,21 +85,26 @@ export async function purge(what) {
|
|
|
86
85
|
await purger(what);
|
|
87
86
|
}
|
|
88
87
|
catch (error) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
console.warn("[rsc-kit] purging the CDN failed; its copy is dropped at s-maxage instead: " +
|
|
92
|
-
(error instanceof Error ? error.message : String(error)));
|
|
93
|
-
}
|
|
88
|
+
console.warn("[rsc-kit] purging the CDN failed; its copy is dropped at CDN-Cache-Control's max-age instead: " +
|
|
89
|
+
(error instanceof Error ? error.message : String(error)));
|
|
94
90
|
}
|
|
95
91
|
}
|
|
96
|
-
/** Homeport takes at most this many tags
|
|
92
|
+
/** Homeport takes at most this many tags and paths, together, per call. */
|
|
97
93
|
export const HOMEPORT_PURGE_LIMIT = 100;
|
|
94
|
+
/** The longest tag and path Homeport takes, in bytes. */
|
|
95
|
+
const HOMEPORT_TAG_BYTES = 1024;
|
|
96
|
+
const HOMEPORT_PATH_BYTES = 2048;
|
|
97
|
+
const bytes = new TextEncoder();
|
|
98
|
+
/** How long one call may take, so a purge url that hangs leaves nothing pending. */
|
|
99
|
+
const HOMEPORT_TIMEOUT_MS = 5_000;
|
|
98
100
|
/**
|
|
99
101
|
* Homeport's purge: `POST {tags, paths, all}` to the url Homeport gives the
|
|
100
102
|
* app, no token - it is reachable only from inside the app's own network.
|
|
101
|
-
* Split into calls of 100 tags and
|
|
102
|
-
*
|
|
103
|
-
*
|
|
103
|
+
* Split into calls of 100 tags and paths together. A tag or path too long for
|
|
104
|
+
* it is left out: one would cost the whole call, and the edge keeps no page
|
|
105
|
+
* with a tag it could not purge. Only a 202 is accepted. A 429 is tried once
|
|
106
|
+
* more after its `Retry-After` (at most five seconds); anything else is that
|
|
107
|
+
* call lost. The rest are still sent, then the purge rejects.
|
|
104
108
|
*/
|
|
105
109
|
export function homeportPurge(options = {}) {
|
|
106
110
|
return async (what) => {
|
|
@@ -108,24 +112,32 @@ export function homeportPurge(options = {}) {
|
|
|
108
112
|
if (!url)
|
|
109
113
|
throw new Error("homeportPurge() has no url: HOMEPORT_PURGE_URL is not set.");
|
|
110
114
|
const send = options.fetch ?? fetch;
|
|
111
|
-
const tags =
|
|
112
|
-
const paths =
|
|
115
|
+
const tags = (what.tags ?? []).filter((tag) => bytes.encode(tag).length <= HOMEPORT_TAG_BYTES);
|
|
116
|
+
const paths = (what.paths ?? []).filter((path) => bytes.encode(path).length <= HOMEPORT_PATH_BYTES);
|
|
113
117
|
const bodies = [];
|
|
114
118
|
if (what.all)
|
|
115
119
|
bodies.push({ all: true });
|
|
116
120
|
while (tags.length > 0 || paths.length > 0) {
|
|
117
121
|
const body = {};
|
|
118
122
|
const someTags = tags.splice(0, HOMEPORT_PURGE_LIMIT);
|
|
119
|
-
const somePaths = paths.splice(0, HOMEPORT_PURGE_LIMIT);
|
|
123
|
+
const somePaths = paths.splice(0, HOMEPORT_PURGE_LIMIT - someTags.length);
|
|
120
124
|
if (someTags.length > 0)
|
|
121
125
|
body.tags = someTags;
|
|
122
126
|
if (somePaths.length > 0)
|
|
123
127
|
body.paths = somePaths;
|
|
124
128
|
bodies.push(body);
|
|
125
129
|
}
|
|
130
|
+
const failed = [];
|
|
126
131
|
for (const body of bodies) {
|
|
127
|
-
|
|
132
|
+
try {
|
|
133
|
+
await post(send, url, body);
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
failed.push(error instanceof Error ? error.message : String(error));
|
|
137
|
+
}
|
|
128
138
|
}
|
|
139
|
+
if (failed.length > 0)
|
|
140
|
+
throw new Error(failed.join(" "));
|
|
129
141
|
};
|
|
130
142
|
}
|
|
131
143
|
async function post(send, url, body, retried = false) {
|
|
@@ -133,9 +145,10 @@ async function post(send, url, body, retried = false) {
|
|
|
133
145
|
method: "POST",
|
|
134
146
|
headers: { "Content-Type": "application/json" },
|
|
135
147
|
body: JSON.stringify(body),
|
|
148
|
+
signal: AbortSignal.timeout(HOMEPORT_TIMEOUT_MS),
|
|
136
149
|
});
|
|
137
150
|
await response.body?.cancel();
|
|
138
|
-
if (response.
|
|
151
|
+
if (response.status === 202)
|
|
139
152
|
return;
|
|
140
153
|
if (response.status === 429 && !retried) {
|
|
141
154
|
const seconds = Math.min(Number(response.headers.get("Retry-After")) || 1, 5);
|
package/dist/purge.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"purge.js","sourceRoot":"","sources":["../src/purge.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Telling a CDN that kept pages changed.\n *\n * A `revalidate` page goes out with `Cache-Tag`, and a CDN that purges by tag\n * can drop it the moment one of those tags changes, rather than waiting out\n * `s-maxage`. What calls the purge is the app's: every CDN has its own API, so\n * the engine has one hook, `installPurge()`, and `changed(tag)` calls it with\n * the names that changed - after the answer has gone, through `after()`, so a\n * slow CDN never holds up the action that made the change.\n *\n * Homeport is the one installed for you: with `HOMEPORT_PURGE_URL` set, the\n * hook is `homeportPurge()` unless the app installed another.\n *\n * A Laravel or Go backend changes its names itself (`Rsc::changed()`,\n * `rsckit.Changed()`), and the engine never hears of it, so each backend\n * purges for its own changes. This hook covers `changed()` called here.\n */\n\n// Not imported: @rsc-kit/core/changed reaches this file and a client component\n// may import that, so request.ts - server-only - is found through the global\n// it sets when the server loads it. Without it, there is no request to wait\n// for, and a purge goes out at once.\ntype AfterHooks = { after: (work: () => unknown) => void; requestKey: () => object | null };\n\nfunction hooks(): AfterHooks {\n return (\n ((globalThis as Record<symbol, unknown>)[Symbol.for(\"rsc-kit.after\")] as AfterHooks | undefined) ?? {\n after: (work) => void Promise.resolve().then(work),\n requestKey: () => null,\n }\n );\n}\n\n/** What a purge drops: pages carrying any of these tags, pages at these paths, or everything. */\nexport interface Purge {\n tags?: readonly string[];\n paths?: readonly string[];\n all?: boolean;\n}\n\n/** Sends a purge to a CDN. A rejection is logged; it never fails the change. */\nexport type Purger = (purge: Purge) => Promise<void> | void;\n\n// On the global, like the cache store: an app installs through its own copy\n// of the package, and the engine's copy is the one that calls it.\nconst PURGER = Symbol.for(\"rsc-kit.purger\");\nconst globals = globalThis as unknown as Record<symbol, Purger | null | undefined>;\n\n/** Install the app's purge. `null` takes it out, and Homeport's default with it. */\nexport function installPurge(purger: Purger | null): void {\n globals[PURGER] = purger;\n}\n\n/** The purge in use: the app's, else Homeport's when its url is set, else none. */\nexport function installedPurge(): Purger | null {\n const installed = globals[PURGER];\n\n if (installed !== undefined) return installed;\n\n const url = homeportPurgeUrl();\n\n return url ? homeportPurge({ url }) : null;\n}\n\nfunction homeportPurgeUrl(): string | undefined {\n return (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env?.HOMEPORT_PURGE_URL || undefined;\n}\n\n// The names an action changed go out as one purge when its answer has gone:\n// three changed() calls send one request, not three. Gathered per request,\n// so a request whose after() work never ran cannot hold back another's.\nconst pendingFor = new WeakMap<object, Set<string>>();\n\n/**\n * @internal For changed(): purge these tags once the answer has gone, or at\n * once outside a request. Does nothing when no purge is installed.\n */\nexport function purgeLater(tags: readonly string[]): void {\n if (tags.length === 0 || !installedPurge()) return;\n\n const { after, requestKey } = hooks();\n const key = requestKey();\n\n if (!key) {\n after(() => purge({ tags }));\n\n return;\n }\n\n let pending = pendingFor.get(key);\n\n if (pending) {\n for (const tag of tags) pending.add(tag);\n\n return;\n }\n\n pending = new Set(tags);\n pendingFor.set(key, pending);\n\n const gathered = pending;\n\n after(() => {\n pendingFor.delete(key);\n\n return purge({ tags: [...gathered] });\n });\n}\n\nlet warnedPurge = false;\n\n/**\n * Send a purge now, through the installed purge. For an app's own use - a\n * deploy step, a webhook - as well as changed()'s. Never throws: the change\n * it follows is already made, and a CDN that missed it still drops the copy\n * at `s-maxage`. The first failure is logged.\n */\nexport async function purge(what: Purge): Promise<void> {\n const purger = installedPurge();\n\n if (!purger) return;\n\n try {\n await purger(what);\n } catch (error) {\n if (!warnedPurge) {\n warnedPurge = true;\n console.warn(\n \"[rsc-kit] purging the CDN failed; its copy is dropped at s-maxage instead: \" +\n (error instanceof Error ? error.message : String(error)),\n );\n }\n }\n}\n\n/** Homeport takes at most this many tags, and this many paths, per call. */\nexport const HOMEPORT_PURGE_LIMIT = 100;\n\nexport interface HomeportPurgeOptions {\n /** Where to post. Default `HOMEPORT_PURGE_URL`, which Homeport sets. */\n url?: string;\n /** For tests. */\n fetch?: typeof fetch;\n}\n\n/**\n * Homeport's purge: `POST {tags, paths, all}` to the url Homeport gives the\n * app, no token - it is reachable only from inside the app's own network.\n * Split into calls of 100 tags and 100 paths. A 429 is tried once more after\n * its `Retry-After` (at most five seconds); anything else that is not a 2xx\n * rejects.\n */\nexport function homeportPurge(options: HomeportPurgeOptions = {}): Purger {\n return async (what) => {\n const url = options.url ?? homeportPurgeUrl();\n\n if (!url) throw new Error(\"homeportPurge() has no url: HOMEPORT_PURGE_URL is not set.\");\n\n const send = options.fetch ?? fetch;\n const tags = [...(what.tags ?? [])];\n const paths = [...(what.paths ?? [])];\n const bodies: Purge[] = [];\n\n if (what.all) bodies.push({ all: true });\n\n while (tags.length > 0 || paths.length > 0) {\n const body: Purge = {};\n const someTags = tags.splice(0, HOMEPORT_PURGE_LIMIT);\n const somePaths = paths.splice(0, HOMEPORT_PURGE_LIMIT);\n\n if (someTags.length > 0) body.tags = someTags;\n if (somePaths.length > 0) body.paths = somePaths;\n bodies.push(body);\n }\n\n for (const body of bodies) {\n await post(send, url, body);\n }\n };\n}\n\nasync function post(send: typeof fetch, url: string, body: Purge, retried = false): Promise<void> {\n const response = await send(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n\n await response.body?.cancel();\n\n if (response.ok) return;\n\n if (response.status === 429 && !retried) {\n const seconds = Math.min(Number(response.headers.get(\"Retry-After\")) || 1, 5);\n\n await new Promise((resolve) => setTimeout(resolve, seconds * 1000));\n\n return post(send, url, body, true);\n }\n\n throw new Error(`Homeport answered ${response.status} to a purge of ${JSON.stringify(body)}.`);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"purge.js","sourceRoot":"","sources":["../src/purge.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Telling a CDN that kept pages changed.\n *\n * A `revalidate` page goes out with `Cache-Tag`, and a CDN that purges by tag\n * can drop it the moment one of those tags changes, rather than waiting out\n * its `max-age` in `CDN-Cache-Control`. What calls the purge is the app's: every CDN has its own API, so\n * the engine has one hook, `installPurge()`, and `changed(tag)` calls it with\n * the names that changed - after the answer has gone, through `after()`, so a\n * slow CDN never holds up the action that made the change.\n *\n * Homeport is the one installed for you: with `HOMEPORT_PURGE_URL` set, the\n * hook is `homeportPurge()` unless the app installed another.\n *\n * A Laravel or Go backend changes its names itself (`Rsc::changed()`,\n * `reg.Changed()`), and the engine never hears of it, so each backend\n * purges for its own changes. This hook covers `changed()` called here.\n */\n\n// Not imported: @rsc-kit/core/changed reaches this file and a client component\n// may import that, so request.ts - server-only - is found through the global\n// it sets when the server loads it. Without it, there is no request to wait\n// for, and a purge goes out at once.\ntype AfterHooks = { after: (work: () => unknown) => void; requestKey: () => object | null };\n\nfunction hooks(): AfterHooks {\n return (\n ((globalThis as Record<symbol, unknown>)[Symbol.for(\"rsc-kit.after\")] as AfterHooks | undefined) ?? {\n after: (work) => void Promise.resolve().then(work),\n requestKey: () => null,\n }\n );\n}\n\n/** What a purge drops: pages carrying any of these tags, pages at these paths, or everything. */\nexport interface Purge {\n tags?: readonly string[];\n paths?: readonly string[];\n all?: boolean;\n}\n\n/** Sends a purge to a CDN. A rejection is logged; it never fails the change. */\nexport type Purger = (purge: Purge) => Promise<void> | void;\n\n// On the global, like the cache store: an app installs through its own copy\n// of the package, and the engine's copy is the one that calls it.\nconst PURGER = Symbol.for(\"rsc-kit.purger\");\nconst globals = globalThis as unknown as Record<symbol, Purger | null | undefined>;\n\n/** Install the app's purge. `null` takes it out, and Homeport's default with it. */\nexport function installPurge(purger: Purger | null): void {\n globals[PURGER] = purger;\n}\n\n/** The purge in use: the app's, else Homeport's when its url is set, else none. */\nexport function installedPurge(): Purger | null {\n const installed = globals[PURGER];\n\n if (installed !== undefined) return installed;\n\n const url = homeportPurgeUrl();\n\n return url ? homeportPurge({ url }) : null;\n}\n\nfunction homeportPurgeUrl(): string | undefined {\n return (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env?.HOMEPORT_PURGE_URL || undefined;\n}\n\n// The names an action changed go out as one purge when its answer has gone:\n// three changed() calls send one request, not three. Gathered per request,\n// so a request whose after() work never ran cannot hold back another's.\nconst pendingFor = new WeakMap<object, Set<string>>();\n\n/**\n * @internal For changed(): purge these tags once the answer has gone, or at\n * once outside a request. Does nothing when no purge is installed.\n */\nexport function purgeLater(tags: readonly string[]): void {\n if (tags.length === 0 || !installedPurge()) return;\n\n const { after, requestKey } = hooks();\n const key = requestKey();\n\n if (!key) {\n after(() => purge({ tags }));\n\n return;\n }\n\n let pending = pendingFor.get(key);\n\n if (pending) {\n for (const tag of tags) pending.add(tag);\n\n return;\n }\n\n pending = new Set(tags);\n pendingFor.set(key, pending);\n\n const gathered = pending;\n\n after(() => {\n pendingFor.delete(key);\n\n return purge({ tags: [...gathered] });\n });\n}\n\n/**\n * Send a purge now, through the installed purge. For an app's own use - a\n * deploy step, a webhook - as well as changed()'s. Never throws: the change\n * it follows is already made, and a CDN that missed it still drops the copy\n * at its `max-age` in `CDN-Cache-Control`. A failure is logged.\n */\nexport async function purge(what: Purge): Promise<void> {\n const purger = installedPurge();\n\n if (!purger) return;\n\n try {\n await purger(what);\n } catch (error) {\n console.warn(\n \"[rsc-kit] purging the CDN failed; its copy is dropped at CDN-Cache-Control's max-age instead: \" +\n (error instanceof Error ? error.message : String(error)),\n );\n }\n}\n\n/** Homeport takes at most this many tags and paths, together, per call. */\nexport const HOMEPORT_PURGE_LIMIT = 100;\n\n/** The longest tag and path Homeport takes, in bytes. */\nconst HOMEPORT_TAG_BYTES = 1024;\nconst HOMEPORT_PATH_BYTES = 2048;\n\nconst bytes = new TextEncoder();\n\n/** How long one call may take, so a purge url that hangs leaves nothing pending. */\nconst HOMEPORT_TIMEOUT_MS = 5_000;\n\nexport interface HomeportPurgeOptions {\n /** Where to post. Default `HOMEPORT_PURGE_URL`, which Homeport sets. */\n url?: string;\n /** For tests. */\n fetch?: typeof fetch;\n}\n\n/**\n * Homeport's purge: `POST {tags, paths, all}` to the url Homeport gives the\n * app, no token - it is reachable only from inside the app's own network.\n * Split into calls of 100 tags and paths together. A tag or path too long for\n * it is left out: one would cost the whole call, and the edge keeps no page\n * with a tag it could not purge. Only a 202 is accepted. A 429 is tried once\n * more after its `Retry-After` (at most five seconds); anything else is that\n * call lost. The rest are still sent, then the purge rejects.\n */\nexport function homeportPurge(options: HomeportPurgeOptions = {}): Purger {\n return async (what) => {\n const url = options.url ?? homeportPurgeUrl();\n\n if (!url) throw new Error(\"homeportPurge() has no url: HOMEPORT_PURGE_URL is not set.\");\n\n const send = options.fetch ?? fetch;\n const tags = (what.tags ?? []).filter((tag) => bytes.encode(tag).length <= HOMEPORT_TAG_BYTES);\n const paths = (what.paths ?? []).filter((path) => bytes.encode(path).length <= HOMEPORT_PATH_BYTES);\n const bodies: Purge[] = [];\n\n if (what.all) bodies.push({ all: true });\n\n while (tags.length > 0 || paths.length > 0) {\n const body: Purge = {};\n const someTags = tags.splice(0, HOMEPORT_PURGE_LIMIT);\n const somePaths = paths.splice(0, HOMEPORT_PURGE_LIMIT - someTags.length);\n\n if (someTags.length > 0) body.tags = someTags;\n if (somePaths.length > 0) body.paths = somePaths;\n bodies.push(body);\n }\n\n const failed: string[] = [];\n\n for (const body of bodies) {\n try {\n await post(send, url, body);\n } catch (error) {\n failed.push(error instanceof Error ? error.message : String(error));\n }\n }\n\n if (failed.length > 0) throw new Error(failed.join(\" \"));\n };\n}\n\nasync function post(send: typeof fetch, url: string, body: Purge, retried = false): Promise<void> {\n const response = await send(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(HOMEPORT_TIMEOUT_MS),\n });\n\n await response.body?.cancel();\n\n if (response.status === 202) return;\n\n if (response.status === 429 && !retried) {\n const seconds = Math.min(Number(response.headers.get(\"Retry-After\")) || 1, 5);\n\n await new Promise((resolve) => setTimeout(resolve, seconds * 1000));\n\n return post(send, url, body, true);\n }\n\n throw new Error(`Homeport answered ${response.status} to a purge of ${JSON.stringify(body)}.`);\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rsc-kit/core",
|
|
3
|
-
"version": "0.30.
|
|
3
|
+
"version": "0.30.47",
|
|
4
4
|
"description": "React Server Components as a Vite plugin. Routing, streaming, server actions, typed urls and build-time prerendering — deploy to Bun, Node, Workers, Vercel or Deno.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|