@rsc-kit/core 0.26.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/changed.d.ts CHANGED
@@ -64,9 +64,137 @@ type HostFn = (name: string, ...args: unknown[]) => Promise<unknown>;
64
64
  * on it: one instance, versions bumped by `changed()` here.
65
65
  */
66
66
  export declare function backendVersions(call: HostFn): VersionSource;
67
- /** Install where versions are read from; `null` restores this process's own. */
67
+ /** Runs one statement with positional parameters and resolves to its rows. */
68
+ export type SqlQuery = (text: string, params: unknown[]) => Promise<readonly Record<string, unknown>[]>;
69
+ export interface SqlVersionsOptions {
70
+ /**
71
+ * Your driver, as a function of SQL text and parameters:
72
+ *
73
+ * query: (text, params) => sql.unsafe(text, params) // postgres.js, Bun.sql
74
+ * query: async (text, params) => (await pool.query(text, params)).rows // node-postgres
75
+ */
76
+ query: SqlQuery;
77
+ /** The table: `CREATE TABLE rsc_versions (name TEXT PRIMARY KEY, version BIGINT NOT NULL)`. */
78
+ table?: string;
79
+ /** How the n-th parameter is written (1-based). Default `?`; Postgres is `(n) => '$' + n`. */
80
+ placeholder?: (n: number) => string;
81
+ /**
82
+ * Called once with a function that wakes every waiting ask, for a database
83
+ * that can say the moment a version moved - Postgres's LISTEN:
84
+ *
85
+ * listen: (wake) => sql.listen('rsc_versions', wake)
86
+ *
87
+ * Without it, a waiting ask reads the table every `poll` ms.
88
+ */
89
+ listen?: (wake: () => void) => unknown;
90
+ /**
91
+ * Run after every bump, to tell the other instances - Postgres's NOTIFY:
92
+ *
93
+ * notify: () => sql.notify('rsc_versions', '')
94
+ */
95
+ notify?: () => unknown;
96
+ /** How often a waiting ask reads the table. Default 1000ms. */
97
+ poll?: number;
98
+ }
99
+ /**
100
+ * Versions in a table every instance shares: the web servers, and the worker
101
+ * that finishes a job and says so. The same table and columns Go's
102
+ * `SQLVersions` uses, so a Go service and a JavaScript worker can share one.
103
+ *
104
+ * installVersionSource(sqlVersions({
105
+ * query: (text, params) => sql.unsafe(text, params),
106
+ * placeholder: (n) => '$' + n,
107
+ * listen: (wake) => sql.listen('rsc_versions', wake),
108
+ * notify: () => sql.notify('rsc_versions', ''),
109
+ * }))
110
+ *
111
+ * Written with an UPDATE and, the first time, an INSERT - two statements any
112
+ * SQL dialect has - rather than an upsert each spells differently. Takes the
113
+ * app's own driver, so the engine has no database dependency of its own.
114
+ */
115
+ export declare function sqlVersions(options: SqlVersionsOptions): VersionSource;
116
+ /**
117
+ * Where versions are kept, as the app writes it for its own database: the
118
+ * three things rsc-kit cannot know about your storage, and nothing else.
119
+ * `createVersions` does the rest - comparing, waiting, waking.
120
+ *
121
+ * // Drizzle
122
+ * read: async (names) => Object.fromEntries(
123
+ * (await db.select().from(rscVersions).where(inArray(rscVersions.name, names)))
124
+ * .map((r) => [r.name, Number(r.version)])),
125
+ * bump: async (names) => { for (const name of names) await db.insert(rscVersions)
126
+ * .values({ name, version: 1 })
127
+ * .onConflictDoUpdate({ target: rscVersions.name, set: { version: sql`${rscVersions.version} + 1` } }) },
128
+ *
129
+ * // Redis
130
+ * read: async (names) => Object.fromEntries((await redis.mget(names.map(k)))
131
+ * .map((v, i) => [names[i], Number(v ?? 0)])),
132
+ * bump: async (names) => { await Promise.all(names.map((n) => redis.incr(k(n)))) },
133
+ */
134
+ export interface VersionStore {
135
+ /** The current version of each name asked for. A name it has never seen may be left out: it is 0. */
136
+ read(names: string[]): Promise<Record<string, number>>;
137
+ /** Move each name's version - by one, or to anything new: watchers compare, they do not count. */
138
+ bump(names: string[]): Promise<void>;
139
+ /**
140
+ * Called once with a function that wakes every waiting ask, when the store
141
+ * can say the moment a version moved - Postgres LISTEN, Redis SUBSCRIBE.
142
+ * Without it a waiting ask reads again every `poll` ms.
143
+ */
144
+ listen?(wake: () => void): unknown;
145
+ /** Run after a bump, to tell the other instances listening: Postgres NOTIFY, Redis PUBLISH. */
146
+ notify?(): unknown;
147
+ }
148
+ /**
149
+ * A version source over any store: SQL through any driver or ORM - Drizzle,
150
+ * Prisma, Kysely, Knex - or Redis, or anything with a read and an increment.
151
+ *
152
+ * installVersionSource(createVersions({ read, bump, listen, notify }))
153
+ *
154
+ * rsc-kit owns the rest: an ask waits up to the time it was given for a
155
+ * version to differ, reading every `poll` ms (1000 by default) or the moment
156
+ * `listen` wakes it, and a bump in this process wakes this process's asks at
157
+ * once whatever the store.
158
+ */
159
+ export declare function createVersions(store: VersionStore, options?: {
160
+ poll?: number;
161
+ }): VersionSource;
162
+ /** The parts of a Postgres client `postgresVersions` uses: postgres.js and `Bun.sql` both have them. */
163
+ export interface PostgresClient {
164
+ unsafe(text: string, params?: unknown[]): PromiseLike<readonly Record<string, unknown>[]>;
165
+ /** postgres.js has it; with it, a waiting ask hears another instance at once. */
166
+ listen?(channel: string, onNotify: () => void): unknown;
167
+ notify?(channel: string, payload: string): unknown;
168
+ }
169
+ /**
170
+ * Versions in Postgres, from the client the app already has:
171
+ *
172
+ * installVersionSource(postgresVersions(sql))
173
+ *
174
+ * `sqlVersions` with Postgres's placeholders, and - when the client can
175
+ * LISTEN, as postgres.js can - a change on one instance wakes the waiting asks
176
+ * on every other at once, rather than on their next read of the table.
177
+ * Without it (`Bun.sql`), the table is read once a second while an ask waits.
178
+ *
179
+ * CREATE TABLE rsc_versions (name TEXT PRIMARY KEY, version BIGINT NOT NULL)
180
+ */
181
+ export declare function postgresVersions(sql: PostgresClient, options?: {
182
+ table?: string;
183
+ channel?: string;
184
+ poll?: number;
185
+ }): VersionSource;
186
+ /**
187
+ * Install where versions are read from - a table, Redis, any store - for this
188
+ * process; `null` takes it out again.
189
+ *
190
+ * The app's choice wins over the backend's: with a Go or Laravel backend that
191
+ * writes its versions to a store this process can read too, reading it here
192
+ * means watching costs the backend nothing.
193
+ */
68
194
  export declare function installVersionSource(source: VersionSource | null): void;
69
- /** The source in use: the backend's when a host is installed, this process's otherwise. */
195
+ /** The engine's, when a backend is installed: its versions, read through it. Not for apps. */
196
+ export declare function installBackendVersionSource(source: VersionSource | null): void;
197
+ /** The source in use: the app's, else the backend's, else this process's own. */
70
198
  export declare function versionSource(): VersionSource;
71
199
  /**
72
200
  * Say these names changed, so every open tab showing them refreshes.
@@ -81,14 +209,33 @@ export declare function versionSource(): VersionSource;
81
209
  * its, and so is the call: `rsckit.Changed`, `Rsc::changed`.
82
210
  */
83
211
  export declare function changed(...names: string[]): Promise<void>;
212
+ /** Thrown when a production server has no key to sign names with. */
213
+ export declare class MissingSigningSecret extends Error {
214
+ constructor();
215
+ }
84
216
  /**
85
- * The key names are signed with. Set from `RSC_HOST_CALL_SECRET` when it is
86
- * read; otherwise random, which is right for one process and wrong for two -
87
- * a tab served by one instance presents its names to another and is refused.
217
+ * The secret names are signed with, when it does not come from the
218
+ * environment. Normally it does: `RSC_SIGNING_SECRET`, the same on every
219
+ * instance, or a tab served by one is refused by another.
220
+ *
221
+ * Its own secret, not the backend's host-call secret: that one authorises
222
+ * calls between the renderer and a backend, and an app with no backend has
223
+ * no reason to have it. Two jobs, two keys - one leaking does not hand out
224
+ * the other.
88
225
  */
89
226
  export declare function configureChanged(options: {
90
227
  secret?: string | null;
91
228
  }): void;
229
+ /**
230
+ * Refuse, in production, to sign with a key nobody else has.
231
+ *
232
+ * A random key is right for one process - a dev server - and silently wrong
233
+ * for two: every tab served by one instance is refused by the next, and never
234
+ * refreshes. The server calls this on its first request when the app uses
235
+ * refreshOn, so the misconfiguration is an error at once rather than a page
236
+ * that quietly stops updating.
237
+ */
238
+ export declare function assertSigningSecret(): void;
92
239
  /** A name's signature: the first 16 bytes of an HMAC, base64url, which is what rides in a url. */
93
240
  export declare function sign(name: string): Promise<string>;
94
241
  /** What a page hands the client for one name: its version at render, and its signature. */
package/dist/changed.js CHANGED
@@ -107,15 +107,167 @@ export function backendVersions(call) {
107
107
  },
108
108
  };
109
109
  }
110
+ /**
111
+ * Versions in a table every instance shares: the web servers, and the worker
112
+ * that finishes a job and says so. The same table and columns Go's
113
+ * `SQLVersions` uses, so a Go service and a JavaScript worker can share one.
114
+ *
115
+ * installVersionSource(sqlVersions({
116
+ * query: (text, params) => sql.unsafe(text, params),
117
+ * placeholder: (n) => '$' + n,
118
+ * listen: (wake) => sql.listen('rsc_versions', wake),
119
+ * notify: () => sql.notify('rsc_versions', ''),
120
+ * }))
121
+ *
122
+ * Written with an UPDATE and, the first time, an INSERT - two statements any
123
+ * SQL dialect has - rather than an upsert each spells differently. Takes the
124
+ * app's own driver, so the engine has no database dependency of its own.
125
+ */
126
+ export function sqlVersions(options) {
127
+ const { query } = options;
128
+ const table = options.table ?? "rsc_versions";
129
+ const mark = options.placeholder ?? (() => "?");
130
+ return createVersions({
131
+ async read(names) {
132
+ const rows = await query(`SELECT name, version FROM ${table} WHERE name IN (${names.map((_, i) => mark(i + 1)).join(", ")})`, names);
133
+ // A BIGINT comes back as a string from most drivers.
134
+ return Object.fromEntries(rows.map((row) => [String(row.name), Number(row.version)]));
135
+ },
136
+ async bump(names) {
137
+ const update = `UPDATE ${table} SET version = version + 1 WHERE name = ${mark(1)}`;
138
+ const insert = `INSERT INTO ${table} (name, version) VALUES (${mark(1)}, 1)`;
139
+ for (const name of names) {
140
+ // The row count is not something every driver reports the same way,
141
+ // so ask: a name with no row yet gets one.
142
+ const [exists] = await query(`SELECT 1 AS found FROM ${table} WHERE name = ${mark(1)}`, [name]);
143
+ if (exists) {
144
+ await query(update, [name]);
145
+ continue;
146
+ }
147
+ try {
148
+ await query(insert, [name]);
149
+ }
150
+ catch {
151
+ // Another instance inserted it first: bump that one.
152
+ await query(update, [name]);
153
+ }
154
+ }
155
+ },
156
+ listen: options.listen,
157
+ notify: options.notify,
158
+ }, { poll: options.poll });
159
+ }
160
+ /**
161
+ * A version source over any store: SQL through any driver or ORM - Drizzle,
162
+ * Prisma, Kysely, Knex - or Redis, or anything with a read and an increment.
163
+ *
164
+ * installVersionSource(createVersions({ read, bump, listen, notify }))
165
+ *
166
+ * rsc-kit owns the rest: an ask waits up to the time it was given for a
167
+ * version to differ, reading every `poll` ms (1000 by default) or the moment
168
+ * `listen` wakes it, and a bump in this process wakes this process's asks at
169
+ * once whatever the store.
170
+ */
171
+ export function createVersions(store, options = {}) {
172
+ const poll = options.poll ?? 1_000;
173
+ const waiters = new Set();
174
+ const wakeAll = () => {
175
+ for (const wake of [...waiters])
176
+ wake();
177
+ };
178
+ if (store.listen)
179
+ void Promise.resolve(store.listen(wakeAll)).catch(() => { });
180
+ const read = async (names) => {
181
+ if (names.length === 0)
182
+ return {};
183
+ const found = await store.read(names);
184
+ return Object.fromEntries(names.map((name) => [name, Number(found[name] ?? 0)]));
185
+ };
186
+ return {
187
+ async changed(since, wait) {
188
+ const names = Object.keys(since);
189
+ const deadline = Date.now() + Math.max(0, wait);
190
+ for (;;) {
191
+ const versions = await read(names);
192
+ const differ = {};
193
+ for (const name of names)
194
+ if (versions[name] !== since[name])
195
+ differ[name] = versions[name] ?? 0;
196
+ const remaining = deadline - Date.now();
197
+ if (Object.keys(differ).length > 0 || remaining <= 0)
198
+ return differ;
199
+ await new Promise((resolve) => {
200
+ const done = () => {
201
+ clearTimeout(timer);
202
+ waiters.delete(done);
203
+ resolve();
204
+ };
205
+ const timer = setTimeout(done, Math.min(poll, remaining));
206
+ timer.unref?.();
207
+ waiters.add(done);
208
+ });
209
+ }
210
+ },
211
+ async bump(names) {
212
+ const unique = [...new Set(names)];
213
+ if (unique.length === 0)
214
+ return;
215
+ await store.bump(unique);
216
+ wakeAll();
217
+ if (store.notify)
218
+ await store.notify();
219
+ },
220
+ };
221
+ }
222
+ /**
223
+ * Versions in Postgres, from the client the app already has:
224
+ *
225
+ * installVersionSource(postgresVersions(sql))
226
+ *
227
+ * `sqlVersions` with Postgres's placeholders, and - when the client can
228
+ * LISTEN, as postgres.js can - a change on one instance wakes the waiting asks
229
+ * on every other at once, rather than on their next read of the table.
230
+ * Without it (`Bun.sql`), the table is read once a second while an ask waits.
231
+ *
232
+ * CREATE TABLE rsc_versions (name TEXT PRIMARY KEY, version BIGINT NOT NULL)
233
+ */
234
+ export function postgresVersions(sql, options = {}) {
235
+ const channel = options.channel ?? "rsc_versions";
236
+ const listen = sql.listen?.bind(sql);
237
+ const notify = sql.notify?.bind(sql);
238
+ return sqlVersions({
239
+ query: async (text, params) => await sql.unsafe(text, params),
240
+ placeholder: (n) => "$" + n,
241
+ table: options.table,
242
+ poll: options.poll,
243
+ listen: listen ? (wake) => listen(channel, wake) : undefined,
244
+ notify: listen && notify ? () => notify(channel, "") : undefined,
245
+ });
246
+ }
110
247
  const SOURCE = Symbol.for("rsc-kit.version-source");
248
+ const BACKEND_SOURCE = Symbol.for("rsc-kit.backend-version-source");
249
+ const LOCAL_SOURCE = Symbol.for("rsc-kit.local-version-source");
111
250
  const globals = globalThis;
112
- /** Install where versions are read from; `null` restores this process's own. */
251
+ /**
252
+ * Install where versions are read from - a table, Redis, any store - for this
253
+ * process; `null` takes it out again.
254
+ *
255
+ * The app's choice wins over the backend's: with a Go or Laravel backend that
256
+ * writes its versions to a store this process can read too, reading it here
257
+ * means watching costs the backend nothing.
258
+ */
113
259
  export function installVersionSource(source) {
114
260
  globals[SOURCE] = source;
115
261
  }
116
- /** The source in use: the backend's when a host is installed, this process's otherwise. */
262
+ /** The engine's, when a backend is installed: its versions, read through it. Not for apps. */
263
+ export function installBackendVersionSource(source) {
264
+ globals[BACKEND_SOURCE] = source;
265
+ }
266
+ /** The source in use: the app's, else the backend's, else this process's own. */
117
267
  export function versionSource() {
118
- return (globals[SOURCE] ??= memoryVersions());
268
+ return (globals[SOURCE] ??
269
+ globals[BACKEND_SOURCE] ??
270
+ (globals[LOCAL_SOURCE] ??= memoryVersions()));
119
271
  }
120
272
  /**
121
273
  * Say these names changed, so every open tab showing them refreshes.
@@ -139,34 +291,87 @@ export async function changed(...names) {
139
291
  // between requests - every name goes to the client signed, and comes back
140
292
  // with its signature. Verified, not looked up, so a watch request is
141
293
  // stateless, and a browser cannot make up a name to watch.
142
- let secret = null;
143
- let keyed = null;
144
- const signatures = new Map();
294
+ /** Thrown when a production server has no key to sign names with. */
295
+ export class MissingSigningSecret extends Error {
296
+ constructor() {
297
+ super("refreshOn needs a signing secret in production, the same on every instance: set RSC_SIGNING_SECRET " +
298
+ "to a long random string. Without one, each instance would sign with its own random key, and a tab " +
299
+ "served by one would be refused by the next.");
300
+ this.name = "MissingSigningSecret";
301
+ }
302
+ }
303
+ // On the global rather than in this module: an app imports this file through
304
+ // its own copy of the package, and a key configured there must be the key the
305
+ // engine's copy signs and verifies with.
306
+ const KEY = Symbol.for("rsc-kit.signing-key");
307
+ function keying() {
308
+ return (globals[KEY] ??= {
309
+ secret: null,
310
+ keyed: null,
311
+ signatures: new Map(),
312
+ });
313
+ }
314
+ const env = (name) => globalThis
315
+ .process?.env?.[name] || undefined;
316
+ const production = () => env("NODE_ENV") === "production";
145
317
  /**
146
- * The key names are signed with. Set from `RSC_HOST_CALL_SECRET` when it is
147
- * read; otherwise random, which is right for one process and wrong for two -
148
- * a tab served by one instance presents its names to another and is refused.
318
+ * The secret names are signed with, when it does not come from the
319
+ * environment. Normally it does: `RSC_SIGNING_SECRET`, the same on every
320
+ * instance, or a tab served by one is refused by another.
321
+ *
322
+ * Its own secret, not the backend's host-call secret: that one authorises
323
+ * calls between the renderer and a backend, and an app with no backend has
324
+ * no reason to have it. Two jobs, two keys - one leaking does not hand out
325
+ * the other.
149
326
  */
150
327
  export function configureChanged(options) {
151
- secret = options.secret ?? null;
152
- keyed = null;
153
- signatures.clear();
328
+ const state = keying();
329
+ state.secret = options.secret ?? null;
330
+ state.keyed = null;
331
+ state.signatures.clear();
332
+ }
333
+ /** The secret in use: configured, or from the environment; null when there is none. */
334
+ function secretInUse() {
335
+ const state = keying();
336
+ return state.secret ?? env("RSC_SIGNING_SECRET") ?? null;
154
337
  }
338
+ /**
339
+ * Refuse, in production, to sign with a key nobody else has.
340
+ *
341
+ * A random key is right for one process - a dev server - and silently wrong
342
+ * for two: every tab served by one instance is refused by the next, and never
343
+ * refreshes. The server calls this on its first request when the app uses
344
+ * refreshOn, so the misconfiguration is an error at once rather than a page
345
+ * that quietly stops updating.
346
+ */
347
+ export function assertSigningSecret() {
348
+ if (!secretInUse() && production())
349
+ throw new MissingSigningSecret();
350
+ }
351
+ let warnedRandom = false;
155
352
  async function key() {
156
- return (keyed ??= (async () => {
353
+ const state = keying();
354
+ return (state.keyed ??= (async () => {
355
+ const secret = secretInUse();
157
356
  let raw;
158
- if (!secret && typeof process !== "undefined") {
159
- secret = process.env?.RSC_HOST_CALL_SECRET ?? null;
160
- }
161
357
  if (secret)
162
358
  raw = new TextEncoder().encode(secret);
163
- else
359
+ else if (production())
360
+ throw new MissingSigningSecret();
361
+ else {
362
+ if (!warnedRandom) {
363
+ warnedRandom = true;
364
+ console.warn("[rsc-kit] refreshOn is signing with a random key, which is fine for one dev server. " +
365
+ "Set RSC_SIGNING_SECRET before running more than one instance.");
366
+ }
164
367
  raw = crypto.getRandomValues(new Uint8Array(32));
368
+ }
165
369
  return await crypto.subtle.importKey("raw", raw, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
166
370
  })());
167
371
  }
168
372
  /** A name's signature: the first 16 bytes of an HMAC, base64url, which is what rides in a url. */
169
373
  export function sign(name) {
374
+ const { signatures } = keying();
170
375
  let pending = signatures.get(name);
171
376
  if (!pending) {
172
377
  pending = (async () => {
@@ -393,6 +598,7 @@ export function resetChanges() {
393
598
  streams.clear();
394
599
  known.clear();
395
600
  warned = false;
601
+ delete globals[LOCAL_SOURCE];
396
602
  }
397
603
  /** The url a tab opens to watch, for the client. */
398
604
  export const CHANGES_PATH = HEADER.changesPath;
@@ -1 +1 @@
1
- {"version":3,"file":"changed.js","sourceRoot":"","sources":["../src/changed.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Names: a name for data a page depends on, so whatever changes it can say so\n * from anywhere - a webhook, a job, another visitor's action - and every open\n * tab showing it refreshes.\n *\n * // a section says what it depends on\n * export default section('repos', Repos, { refreshOn: ({ params }) => [`team:${params.team}:repos`] })\n *\n * // and whatever changes it says so\n * changed(`team:${team}:repos`) // here, from a route.ts or an action\n * rsckit.Changed(ctx, \"team:1:repos\") // or the backend, from its webhook\n * Rsc::changed(\"team:$team:repos\"); // Laravel\n *\n * A name has a version: a number that moves whenever something says the name\n * changed. Nothing else - not what changed, not the data. A page learns the\n * versions at render; one stream per open tab watches them; when one moves,\n * the sections carrying that name ask for themselves again, the way refresh()\n * does. Data is never pushed, which is what keeps this working on a host that\n * cannot hold state: a version is a read.\n *\n * Where versions live is the source's business. Without a backend they are in\n * this process, which is one instance. With one, the backend keeps them - in\n * its cache or a table, shared by every instance it runs - and answers\n * `__rsc.changed`: given the versions a watcher holds, the ones that differ now.\n * A backend that can wait holds the call until one does; one that cannot\n * answers at once, and the watcher asks again on its interval. The protocol\n * is the same either way, and so is the page.\n */\n\nimport { KEEPALIVE_MS, frame } from \"./events.js\";\nimport { HEADER } from \"./headers.js\";\n\n/** The reserved name a backend answers name versions on. */\nexport const CHANGED_FUNCTION = \"__rsc.changed\";\n\n/** What `__rsc.changed` is asked. */\nexport interface ChangedQuery {\n /** The versions the asker holds. A name never changed is 0; -1 asks for every current version. */\n since: Record<string, number>;\n /** How long the backend may hold the call waiting for one to differ, in ms. It may answer at once. */\n wait?: number;\n}\n\n/** What `__rsc.changed` answers: the versions among `since` that differ now. */\nexport interface ChangedAnswer {\n versions: Record<string, number>;\n}\n\n/**\n * Where name versions are read from, and - when they live here - bumped.\n *\n * `changed` answers the versions among `since` that differ now, and may wait\n * up to `wait` ms for one to. Nothing else is required of it, which is why a\n * backend's cache, a table, this process's memory, or a pub/sub that answers\n * the moment a name moves all fit behind it.\n */\nexport interface VersionSource {\n changed(\n since: Record<string, number>,\n wait: number,\n ): Promise<Record<string, number>>;\n /** Say these names changed. Only a source that holds its own versions can. */\n bump(names: string[]): void | Promise<void>;\n}\n\n/** Versions kept in this process: the source without a backend, and the one tests use. */\nexport function memoryVersions(): VersionSource {\n const versions = new Map<string, number>();\n const waiters = new Set<() => void>();\n\n const differing = (since: Record<string, number>): Record<string, number> => {\n const out: Record<string, number> = {};\n\n for (const [name, held] of Object.entries(since)) {\n const current = versions.get(name) ?? 0;\n\n if (current !== held) out[name] = current;\n }\n\n return out;\n };\n\n return {\n async changed(since, wait) {\n const now = differing(since);\n\n if (Object.keys(now).length > 0 || wait <= 0) return now;\n\n await new Promise<void>((resolve) => {\n const done = () => {\n clearTimeout(timer);\n waiters.delete(done);\n resolve();\n };\n const timer = setTimeout(done, wait);\n\n // A test or a build must not be held open by a watcher's wait.\n (timer as { unref?: () => void }).unref?.();\n waiters.add(done);\n });\n\n return differing(since);\n },\n bump(names) {\n for (const name of names) versions.set(name, (versions.get(name) ?? 0) + 1);\n for (const wake of [...waiters]) wake();\n },\n };\n}\n\ntype HostFn = (name: string, ...args: unknown[]) => Promise<unknown>;\n\n/**\n * Versions kept by the backend, read through `__rsc.changed`.\n *\n * A backend that has not got the function - an adapter from before names, or\n * a host of plain rpc functions - is answered from this process instead,\n * found out on the first ask and remembered, so a page with names still works\n * on it: one instance, versions bumped by `changed()` here.\n */\nexport function backendVersions(call: HostFn): VersionSource {\n let local: VersionSource | null = null;\n\n // A backend's \"no such function\", or the test host's \"no handler for\".\n const unknown = (error: unknown): boolean =>\n /no host function named|has no handler for/i.test(\n error instanceof Error ? error.message : String(error),\n );\n\n return {\n async changed(since, wait) {\n if (local) return local.changed(since, wait);\n\n try {\n const answer = (await call(CHANGED_FUNCTION, {\n since,\n wait,\n } satisfies ChangedQuery)) as Partial<ChangedAnswer> | null;\n\n return answer?.versions ?? {};\n } catch (error) {\n if (!unknown(error)) throw error;\n\n local = memoryVersions();\n\n return local.changed(since, wait);\n }\n },\n bump(names) {\n if (local) return local.bump(names);\n\n throw new Error(\n \"changed() bumps versions this process keeps, and a backend keeps these: say the name changed there - \" +\n \"rsckit.Changed(ctx, ...) in Go, Rsc::changed(...) in Laravel.\",\n );\n },\n };\n}\n\nconst SOURCE = Symbol.for(\"rsc-kit.version-source\");\nconst globals = globalThis as Record<symbol, unknown>;\n\n/** Install where versions are read from; `null` restores this process's own. */\nexport function installVersionSource(source: VersionSource | null): void {\n globals[SOURCE] = source;\n}\n\n/** The source in use: the backend's when a host is installed, this process's otherwise. */\nexport function versionSource(): VersionSource {\n return ((globals[SOURCE] as VersionSource | undefined) ??= memoryVersions());\n}\n\n/**\n * Say these names changed, so every open tab showing them refreshes.\n *\n * export async function POST(request: Request) { // a webhook, in a route.ts\n * const { team } = await request.json()\n * changed(`team:${team}:repos`)\n * return new Response(null, { status: 204 })\n * }\n *\n * For an app that keeps its versions here. With a backend the versions are\n * its, and so is the call: `rsckit.Changed`, `Rsc::changed`.\n */\nexport async function changed(...names: string[]): Promise<void> {\n await versionSource().bump(names);\n}\n\n// ── Tokens ───────────────────────────────────────────────────────────────────\n//\n// A tab may watch exactly the names its page was rendered with. Rather than\n// remembering what each tab was given - state, which a Worker cannot keep\n// between requests - every name goes to the client signed, and comes back\n// with its signature. Verified, not looked up, so a watch request is\n// stateless, and a browser cannot make up a name to watch.\n\nlet secret: string | null = null;\nlet keyed: Promise<CryptoKey> | null = null;\nconst signatures = new Map<string, Promise<string>>();\n\n/**\n * The key names are signed with. Set from `RSC_HOST_CALL_SECRET` when it is\n * read; otherwise random, which is right for one process and wrong for two -\n * a tab served by one instance presents its names to another and is refused.\n */\nexport function configureChanged(options: { secret?: string | null }): void {\n secret = options.secret ?? null;\n keyed = null;\n signatures.clear();\n}\n\nasync function key(): Promise<CryptoKey> {\n return (keyed ??= (async () => {\n let raw: Uint8Array<ArrayBuffer>;\n\n if (!secret && typeof process !== \"undefined\") {\n secret = process.env?.RSC_HOST_CALL_SECRET ?? null;\n }\n\n if (secret) raw = new TextEncoder().encode(secret);\n else raw = crypto.getRandomValues(new Uint8Array(32));\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 let pending = signatures.get(name);\n\n if (!pending) {\n pending = (async () => {\n const mac = await crypto.subtle.sign(\n \"HMAC\",\n await key(),\n new TextEncoder().encode(name),\n );\n\n return base64url(new Uint8Array(mac).slice(0, 16));\n })();\n\n // Bounded: a name with a visitor's id in it is a new entry per visitor.\n if (signatures.size > 10_000) signatures.clear();\n signatures.set(name, pending);\n }\n\n return pending;\n}\n\nasync function verify(name: string, signature: string): Promise<boolean> {\n const expected = await sign(name);\n\n if (expected.length !== signature.length) return false;\n\n let differ = 0;\n\n for (let i = 0; i < expected.length; i++)\n differ |= expected.charCodeAt(i) ^ signature.charCodeAt(i);\n\n return differ === 0;\n}\n\nfunction base64url(bytes: Uint8Array): string {\n let text = \"\";\n\n for (const byte of bytes) text += String.fromCharCode(byte);\n\n return btoa(text).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n/** What a page hands the client for one name: its version at render, and its signature. */\nexport type SignedName = [version: number, signature: string];\n\n// ── Watching ─────────────────────────────────────────────────────────────────\n//\n// One stream per open tab, and one ask of the source per process: every\n// stream's names are asked about together, on an interval, and a version that\n// moved is written to every stream holding its name. A thousand tabs on a\n// long-lived server cost the backend one call per interval, not a thousand.\n// On a host that answers each request in its own isolate the sharing is\n// whatever the isolate serves at once, and a lone stream asks for itself -\n// the same code, degrading to one small call per tab per interval.\n\n/** How often the source is asked, when it answered at once. */\nexport const CHANGES_INTERVAL_MS = 2_000;\n\n/** How long the source may hold one ask. Bounds how long a new tab waits to join the shared ask. */\nexport const CHANGES_WAIT_MS = 5_000;\n\ninterface Stream {\n names: Set<string>;\n send(name: string, version: number): void;\n}\n\nconst streams = new Set<Stream>();\n\n/** The latest version seen for each name any stream holds. */\nconst known = new Map<string, number>();\n\nlet running = false;\nlet wake: (() => void) | null = null;\nlet warned = false;\n\nfunction observe(name: string, version: number): void {\n if (known.get(name) === version) return;\n\n known.set(name, version);\n\n for (const stream of streams)\n if (stream.names.has(name)) stream.send(name, version);\n}\n\n/** Mapped to this process's set of streams: what to ask about, relative to what is known. */\nfunction sinceAll(): Record<string, number> {\n const since: Record<string, number> = {};\n\n for (const stream of streams)\n for (const name of stream.names) since[name] = known.get(name) ?? 0;\n\n return since;\n}\n\nasync function loop(): Promise<void> {\n running = true;\n\n try {\n while (streams.size > 0) {\n const started = Date.now();\n\n try {\n const moved = await versionSource().changed(\n sinceAll(),\n CHANGES_WAIT_MS,\n );\n\n for (const [name, version] of Object.entries(moved))\n observe(name, version);\n } catch (error) {\n // A backend blip is not the end of watching. Said once, not per interval.\n if (!warned) {\n warned = true;\n console.warn(\n \"[rsc-kit] asking the backend for name versions failed: \" +\n (error instanceof Error ? error.message : String(error)),\n );\n }\n }\n\n // A source that waited used the interval; one that answered at once is\n // asked again after it. A stream opening meanwhile wakes this early.\n const remaining = CHANGES_INTERVAL_MS - (Date.now() - started);\n\n if (remaining > 0 && streams.size > 0) {\n await new Promise<void>((resolve) => {\n const timer = setTimeout(resolve, remaining);\n\n (timer as { unref?: () => void }).unref?.();\n wake = () => {\n clearTimeout(timer);\n resolve();\n };\n });\n wake = null;\n }\n }\n } finally {\n running = false;\n // A stream that opened as the loop was ending starts it again.\n if (streams.size > 0) void loop();\n }\n}\n\nfunction parseWatch(\n url: URL,\n): { name: string; version: number; signature: string }[] | null {\n const raw = url.searchParams.get(\"w\");\n\n if (!raw) return null;\n\n let entries: unknown;\n\n try {\n entries = JSON.parse(raw);\n } catch {\n return null;\n }\n\n if (!Array.isArray(entries) || entries.length === 0 || entries.length > 200)\n return null;\n\n const parsed: { name: string; version: number; signature: string }[] = [];\n\n for (const entry of entries) {\n if (!Array.isArray(entry) || entry.length !== 3) return null;\n\n const [name, version, signature] = entry as unknown[];\n\n if (\n typeof name !== \"string\" ||\n typeof version !== \"number\" ||\n typeof signature !== \"string\"\n )\n return null;\n\n parsed.push({ name, version, signature });\n }\n\n return parsed;\n}\n\n/**\n * The stream a tab watches its names on: `GET /_rsc/changes?w=[[name, version, signature], …]`.\n *\n * One `data:` line per version that moved, `{ name, version }`, from the moment\n * the stream opens - a name that moved between the render and now is reported\n * at once, so nothing that happened in the gap is missed. Keepalives as an\n * events() route sends them; ends when the browser goes away.\n */\nexport async function changes(request: Request): Promise<Response> {\n const entries = parseWatch(new URL(request.url));\n\n if (!entries) return new Response(\"Bad watch request.\", { status: 400 });\n\n for (const { name, signature } of entries) {\n if (!(await verify(name, signature))) {\n return new Response(\"Not a name this page was rendered with.\", {\n status: 403,\n });\n }\n }\n\n const since: Record<string, number> = {};\n\n for (const { name, version } of entries) since[name] = version;\n\n const encoder = new TextEncoder();\n let stream: Stream | null = null;\n\n const body = new ReadableStream<Uint8Array>({\n start(sink) {\n const write = (text: string) => {\n try {\n sink.enqueue(encoder.encode(text));\n } catch {\n // Closed under us; the cancel below ends the rest.\n }\n };\n\n write(\"retry: 2000\\n\\n\");\n\n const keepalive = setInterval(\n () => write(\": keepalive\\n\\n\"),\n KEEPALIVE_MS,\n );\n\n (keepalive as { unref?: () => void }).unref?.();\n\n const me: Stream = {\n names: new Set(Object.keys(since)),\n send: (name, version) => write(frame({ name, version })),\n };\n\n stream = me;\n streams.add(me);\n\n const end = () => {\n clearInterval(keepalive);\n streams.delete(me);\n\n try {\n sink.close();\n } catch {\n // Already closed.\n }\n };\n\n request.signal.addEventListener(\"abort\", end, { once: true });\n\n // What is known already answers at once; what is not is seeded with\n // what this tab holds, so the shared ask compares against it.\n for (const [name, version] of Object.entries(since)) {\n const seen = known.get(name);\n\n if (seen === undefined) known.set(name, version);\n else if (seen !== version) me.send(name, seen);\n }\n\n // This tab's own first ask, holding as long as the source will: the\n // shared ask may be mid-wait on an older set of names, and a name moving\n // in the seconds before this one joins it must not go unnoticed.\n void versionSource()\n .changed(since, CHANGES_WAIT_MS)\n .then((moved) => {\n for (const [name, version] of Object.entries(moved))\n observe(name, version);\n })\n .catch(() => {});\n\n if (!running) void loop();\n else wake?.();\n },\n cancel() {\n if (stream) streams.delete(stream);\n },\n });\n\n return new Response(body, {\n headers: {\n \"Content-Type\": \"text/event-stream; charset=utf-8\",\n \"Cache-Control\": \"no-store\",\n \"X-Accel-Buffering\": \"no\",\n },\n });\n}\n\n/** For tests: forget every stream and version this process has seen. */\nexport function resetChanges(): void {\n streams.clear();\n known.clear();\n warned = false;\n}\n\n/** The url a tab opens to watch, for the client. */\nexport const CHANGES_PATH = HEADER.changesPath;\n"]}
1
+ {"version":3,"file":"changed.js","sourceRoot":"","sources":["../src/changed.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Names: a name for data a page depends on, so whatever changes it can say so\n * from anywhere - a webhook, a job, another visitor's action - and every open\n * tab showing it refreshes.\n *\n * // a section says what it depends on\n * export default section('repos', Repos, { refreshOn: ({ params }) => [`team:${params.team}:repos`] })\n *\n * // and whatever changes it says so\n * changed(`team:${team}:repos`) // here, from a route.ts or an action\n * rsckit.Changed(ctx, \"team:1:repos\") // or the backend, from its webhook\n * Rsc::changed(\"team:$team:repos\"); // Laravel\n *\n * A name has a version: a number that moves whenever something says the name\n * changed. Nothing else - not what changed, not the data. A page learns the\n * versions at render; one stream per open tab watches them; when one moves,\n * the sections carrying that name ask for themselves again, the way refresh()\n * does. Data is never pushed, which is what keeps this working on a host that\n * cannot hold state: a version is a read.\n *\n * Where versions live is the source's business. Without a backend they are in\n * this process, which is one instance. With one, the backend keeps them - in\n * its cache or a table, shared by every instance it runs - and answers\n * `__rsc.changed`: given the versions a watcher holds, the ones that differ now.\n * A backend that can wait holds the call until one does; one that cannot\n * answers at once, and the watcher asks again on its interval. The protocol\n * is the same either way, and so is the page.\n */\n\nimport { KEEPALIVE_MS, frame } from \"./events.js\";\nimport { HEADER } from \"./headers.js\";\n\n/** The reserved name a backend answers name versions on. */\nexport const CHANGED_FUNCTION = \"__rsc.changed\";\n\n/** What `__rsc.changed` is asked. */\nexport interface ChangedQuery {\n /** The versions the asker holds. A name never changed is 0; -1 asks for every current version. */\n since: Record<string, number>;\n /** How long the backend may hold the call waiting for one to differ, in ms. It may answer at once. */\n wait?: number;\n}\n\n/** What `__rsc.changed` answers: the versions among `since` that differ now. */\nexport interface ChangedAnswer {\n versions: Record<string, number>;\n}\n\n/**\n * Where name versions are read from, and - when they live here - bumped.\n *\n * `changed` answers the versions among `since` that differ now, and may wait\n * up to `wait` ms for one to. Nothing else is required of it, which is why a\n * backend's cache, a table, this process's memory, or a pub/sub that answers\n * the moment a name moves all fit behind it.\n */\nexport interface VersionSource {\n changed(\n since: Record<string, number>,\n wait: number,\n ): Promise<Record<string, number>>;\n /** Say these names changed. Only a source that holds its own versions can. */\n bump(names: string[]): void | Promise<void>;\n}\n\n/** Versions kept in this process: the source without a backend, and the one tests use. */\nexport function memoryVersions(): VersionSource {\n const versions = new Map<string, number>();\n const waiters = new Set<() => void>();\n\n const differing = (since: Record<string, number>): Record<string, number> => {\n const out: Record<string, number> = {};\n\n for (const [name, held] of Object.entries(since)) {\n const current = versions.get(name) ?? 0;\n\n if (current !== held) out[name] = current;\n }\n\n return out;\n };\n\n return {\n async changed(since, wait) {\n const now = differing(since);\n\n if (Object.keys(now).length > 0 || wait <= 0) return now;\n\n await new Promise<void>((resolve) => {\n const done = () => {\n clearTimeout(timer);\n waiters.delete(done);\n resolve();\n };\n const timer = setTimeout(done, wait);\n\n // A test or a build must not be held open by a watcher's wait.\n (timer as { unref?: () => void }).unref?.();\n waiters.add(done);\n });\n\n return differing(since);\n },\n bump(names) {\n for (const name of names)\n versions.set(name, (versions.get(name) ?? 0) + 1);\n for (const wake of [...waiters]) wake();\n },\n };\n}\n\ntype HostFn = (name: string, ...args: unknown[]) => Promise<unknown>;\n\n/**\n * Versions kept by the backend, read through `__rsc.changed`.\n *\n * A backend that has not got the function - an adapter from before names, or\n * a host of plain rpc functions - is answered from this process instead,\n * found out on the first ask and remembered, so a page with names still works\n * on it: one instance, versions bumped by `changed()` here.\n */\nexport function backendVersions(call: HostFn): VersionSource {\n let local: VersionSource | null = null;\n\n // A backend's \"no such function\", or the test host's \"no handler for\".\n const unknown = (error: unknown): boolean =>\n /no host function named|has no handler for/i.test(\n error instanceof Error ? error.message : String(error),\n );\n\n return {\n async changed(since, wait) {\n if (local) return local.changed(since, wait);\n\n try {\n const answer = (await call(CHANGED_FUNCTION, {\n since,\n wait,\n } satisfies ChangedQuery)) as Partial<ChangedAnswer> | null;\n\n return answer?.versions ?? {};\n } catch (error) {\n if (!unknown(error)) throw error;\n\n local = memoryVersions();\n\n return local.changed(since, wait);\n }\n },\n bump(names) {\n if (local) return local.bump(names);\n\n throw new Error(\n \"changed() bumps versions this process keeps, and a backend keeps these: say the name changed there - \" +\n \"rsckit.Changed(ctx, ...) in Go, Rsc::changed(...) in Laravel.\",\n );\n },\n };\n}\n\n/** Runs one statement with positional parameters and resolves to its rows. */\nexport type SqlQuery = (\n text: string,\n params: unknown[],\n) => Promise<readonly Record<string, unknown>[]>;\n\nexport interface SqlVersionsOptions {\n /**\n * Your driver, as a function of SQL text and parameters:\n *\n * query: (text, params) => sql.unsafe(text, params) // postgres.js, Bun.sql\n * query: async (text, params) => (await pool.query(text, params)).rows // node-postgres\n */\n query: SqlQuery;\n /** The table: `CREATE TABLE rsc_versions (name TEXT PRIMARY KEY, version BIGINT NOT NULL)`. */\n table?: string;\n /** How the n-th parameter is written (1-based). Default `?`; Postgres is `(n) => '$' + n`. */\n placeholder?: (n: number) => string;\n /**\n * Called once with a function that wakes every waiting ask, for a database\n * that can say the moment a version moved - Postgres's LISTEN:\n *\n * listen: (wake) => sql.listen('rsc_versions', wake)\n *\n * Without it, a waiting ask reads the table every `poll` ms.\n */\n listen?: (wake: () => void) => unknown;\n /**\n * Run after every bump, to tell the other instances - Postgres's NOTIFY:\n *\n * notify: () => sql.notify('rsc_versions', '')\n */\n notify?: () => unknown;\n /** How often a waiting ask reads the table. Default 1000ms. */\n poll?: number;\n}\n\n/**\n * Versions in a table every instance shares: the web servers, and the worker\n * that finishes a job and says so. The same table and columns Go's\n * `SQLVersions` uses, so a Go service and a JavaScript worker can share one.\n *\n * installVersionSource(sqlVersions({\n * query: (text, params) => sql.unsafe(text, params),\n * placeholder: (n) => '$' + n,\n * listen: (wake) => sql.listen('rsc_versions', wake),\n * notify: () => sql.notify('rsc_versions', ''),\n * }))\n *\n * Written with an UPDATE and, the first time, an INSERT - two statements any\n * SQL dialect has - rather than an upsert each spells differently. Takes the\n * app's own driver, so the engine has no database dependency of its own.\n */\nexport function sqlVersions(options: SqlVersionsOptions): VersionSource {\n const { query } = options;\n const table = options.table ?? \"rsc_versions\";\n const mark = options.placeholder ?? (() => \"?\");\n\n return createVersions(\n {\n async read(names) {\n const rows = await query(\n `SELECT name, version FROM ${table} WHERE name IN (${names.map((_, i) => mark(i + 1)).join(\", \")})`,\n names,\n );\n\n // A BIGINT comes back as a string from most drivers.\n return Object.fromEntries(\n rows.map((row) => [String(row.name), Number(row.version)]),\n );\n },\n async bump(names) {\n const update = `UPDATE ${table} SET version = version + 1 WHERE name = ${mark(1)}`;\n const insert = `INSERT INTO ${table} (name, version) VALUES (${mark(1)}, 1)`;\n\n for (const name of names) {\n // The row count is not something every driver reports the same way,\n // so ask: a name with no row yet gets one.\n const [exists] = await query(\n `SELECT 1 AS found FROM ${table} WHERE name = ${mark(1)}`,\n [name],\n );\n\n if (exists) {\n await query(update, [name]);\n continue;\n }\n\n try {\n await query(insert, [name]);\n } catch {\n // Another instance inserted it first: bump that one.\n await query(update, [name]);\n }\n }\n },\n listen: options.listen,\n notify: options.notify,\n },\n { poll: options.poll },\n );\n}\n\n/**\n * Where versions are kept, as the app writes it for its own database: the\n * three things rsc-kit cannot know about your storage, and nothing else.\n * `createVersions` does the rest - comparing, waiting, waking.\n *\n * // Drizzle\n * read: async (names) => Object.fromEntries(\n * (await db.select().from(rscVersions).where(inArray(rscVersions.name, names)))\n * .map((r) => [r.name, Number(r.version)])),\n * bump: async (names) => { for (const name of names) await db.insert(rscVersions)\n * .values({ name, version: 1 })\n * .onConflictDoUpdate({ target: rscVersions.name, set: { version: sql`${rscVersions.version} + 1` } }) },\n *\n * // Redis\n * read: async (names) => Object.fromEntries((await redis.mget(names.map(k)))\n * .map((v, i) => [names[i], Number(v ?? 0)])),\n * bump: async (names) => { await Promise.all(names.map((n) => redis.incr(k(n)))) },\n */\nexport interface VersionStore {\n /** The current version of each name asked for. A name it has never seen may be left out: it is 0. */\n read(names: string[]): Promise<Record<string, number>>;\n /** Move each name's version - by one, or to anything new: watchers compare, they do not count. */\n bump(names: string[]): Promise<void>;\n /**\n * Called once with a function that wakes every waiting ask, when the store\n * can say the moment a version moved - Postgres LISTEN, Redis SUBSCRIBE.\n * Without it a waiting ask reads again every `poll` ms.\n */\n listen?(wake: () => void): unknown;\n /** Run after a bump, to tell the other instances listening: Postgres NOTIFY, Redis PUBLISH. */\n notify?(): unknown;\n}\n\n/**\n * A version source over any store: SQL through any driver or ORM - Drizzle,\n * Prisma, Kysely, Knex - or Redis, or anything with a read and an increment.\n *\n * installVersionSource(createVersions({ read, bump, listen, notify }))\n *\n * rsc-kit owns the rest: an ask waits up to the time it was given for a\n * version to differ, reading every `poll` ms (1000 by default) or the moment\n * `listen` wakes it, and a bump in this process wakes this process's asks at\n * once whatever the store.\n */\nexport function createVersions(\n store: VersionStore,\n options: { poll?: number } = {},\n): VersionSource {\n const poll = options.poll ?? 1_000;\n const waiters = new Set<() => void>();\n const wakeAll = () => {\n for (const wake of [...waiters]) wake();\n };\n\n if (store.listen) void Promise.resolve(store.listen(wakeAll)).catch(() => {});\n\n const read = async (names: string[]): Promise<Record<string, number>> => {\n if (names.length === 0) return {};\n\n const found = await store.read(names);\n\n return Object.fromEntries(\n names.map((name) => [name, Number(found[name] ?? 0)]),\n );\n };\n\n return {\n async changed(since, wait) {\n const names = Object.keys(since);\n const deadline = Date.now() + Math.max(0, wait);\n\n for (;;) {\n const versions = await read(names);\n const differ: Record<string, number> = {};\n\n for (const name of names)\n if (versions[name] !== since[name])\n differ[name] = versions[name] ?? 0;\n\n const remaining = deadline - Date.now();\n\n if (Object.keys(differ).length > 0 || remaining <= 0) return differ;\n\n await new Promise<void>((resolve) => {\n const done = () => {\n clearTimeout(timer);\n waiters.delete(done);\n resolve();\n };\n const timer = setTimeout(done, Math.min(poll, remaining));\n\n (timer as { unref?: () => void }).unref?.();\n waiters.add(done);\n });\n }\n },\n async bump(names) {\n const unique = [...new Set(names)];\n\n if (unique.length === 0) return;\n\n await store.bump(unique);\n wakeAll();\n if (store.notify) await store.notify();\n },\n };\n}\n\n/** The parts of a Postgres client `postgresVersions` uses: postgres.js and `Bun.sql` both have them. */\nexport interface PostgresClient {\n unsafe(\n text: string,\n params?: unknown[],\n ): PromiseLike<readonly Record<string, unknown>[]>;\n /** postgres.js has it; with it, a waiting ask hears another instance at once. */\n listen?(channel: string, onNotify: () => void): unknown;\n notify?(channel: string, payload: string): unknown;\n}\n\n/**\n * Versions in Postgres, from the client the app already has:\n *\n * installVersionSource(postgresVersions(sql))\n *\n * `sqlVersions` with Postgres's placeholders, and - when the client can\n * LISTEN, as postgres.js can - a change on one instance wakes the waiting asks\n * on every other at once, rather than on their next read of the table.\n * Without it (`Bun.sql`), the table is read once a second while an ask waits.\n *\n * CREATE TABLE rsc_versions (name TEXT PRIMARY KEY, version BIGINT NOT NULL)\n */\nexport function postgresVersions(\n sql: PostgresClient,\n options: { table?: string; channel?: string; poll?: number } = {},\n): VersionSource {\n const channel = options.channel ?? \"rsc_versions\";\n const listen = sql.listen?.bind(sql);\n const notify = sql.notify?.bind(sql);\n\n return sqlVersions({\n query: async (text, params) => await sql.unsafe(text, params),\n placeholder: (n) => \"$\" + n,\n table: options.table,\n poll: options.poll,\n listen: listen ? (wake) => listen(channel, wake) : undefined,\n notify: listen && notify ? () => notify(channel, \"\") : undefined,\n });\n}\n\nconst SOURCE = Symbol.for(\"rsc-kit.version-source\");\nconst BACKEND_SOURCE = Symbol.for(\"rsc-kit.backend-version-source\");\nconst LOCAL_SOURCE = Symbol.for(\"rsc-kit.local-version-source\");\nconst globals = globalThis as Record<symbol, unknown>;\n\n/**\n * Install where versions are read from - a table, Redis, any store - for this\n * process; `null` takes it out again.\n *\n * The app's choice wins over the backend's: with a Go or Laravel backend that\n * writes its versions to a store this process can read too, reading it here\n * means watching costs the backend nothing.\n */\nexport function installVersionSource(source: VersionSource | null): void {\n globals[SOURCE] = source;\n}\n\n/** The engine's, when a backend is installed: its versions, read through it. Not for apps. */\nexport function installBackendVersionSource(\n source: VersionSource | null,\n): void {\n globals[BACKEND_SOURCE] = source;\n}\n\n/** The source in use: the app's, else the backend's, else this process's own. */\nexport function versionSource(): VersionSource {\n return (\n (globals[SOURCE] as VersionSource | null | undefined) ??\n (globals[BACKEND_SOURCE] as VersionSource | null | undefined) ??\n ((globals[LOCAL_SOURCE] as VersionSource | undefined) ??= memoryVersions())\n );\n}\n\n/**\n * Say these names changed, so every open tab showing them refreshes.\n *\n * export async function POST(request: Request) { // a webhook, in a route.ts\n * const { team } = await request.json()\n * changed(`team:${team}:repos`)\n * return new Response(null, { status: 204 })\n * }\n *\n * For an app that keeps its versions here. With a backend the versions are\n * its, and so is the call: `rsckit.Changed`, `Rsc::changed`.\n */\nexport async function changed(...names: string[]): Promise<void> {\n await versionSource().bump(names);\n}\n\n// ── Tokens ───────────────────────────────────────────────────────────────────\n//\n// A tab may watch exactly the names its page was rendered with. Rather than\n// remembering what each tab was given - state, which a Worker cannot keep\n// between requests - every name goes to the client signed, and comes back\n// with its signature. Verified, not looked up, so a watch request is\n// stateless, and a browser cannot make up a name to watch.\n\n/** Thrown when a production server has no key to sign names with. */\nexport class MissingSigningSecret extends Error {\n constructor() {\n super(\n \"refreshOn needs a signing secret in production, the same on every instance: set RSC_SIGNING_SECRET \" +\n \"to a long random string. Without one, each instance would sign with its own random key, and a tab \" +\n \"served by one would be refused by the next.\",\n );\n this.name = \"MissingSigningSecret\";\n }\n}\n\n// On the global rather than in this module: an app imports this file through\n// its own copy of the package, and a key configured there must be the key the\n// engine's copy signs and verifies with.\nconst KEY = Symbol.for(\"rsc-kit.signing-key\");\n\ninterface Keying {\n secret: string | null;\n keyed: Promise<CryptoKey> | null;\n signatures: Map<string, Promise<string>>;\n}\n\nfunction keying(): Keying {\n return ((globals[KEY] as Keying | undefined) ??= {\n secret: null,\n keyed: null,\n signatures: new Map(),\n });\n}\n\nconst env = (name: string): string | undefined =>\n (globalThis as { process?: { env?: Record<string, string | undefined> } })\n .process?.env?.[name] || undefined;\n\nconst production = (): boolean => env(\"NODE_ENV\") === \"production\";\n\n/**\n * The secret names are signed with, when it does not come from the\n * environment. Normally it does: `RSC_SIGNING_SECRET`, the same on every\n * instance, or a tab served by one is refused by another.\n *\n * Its own secret, not the backend's host-call secret: that one authorises\n * calls between the renderer and a backend, and an app with no backend has\n * no reason to have it. Two jobs, two keys - one leaking does not hand out\n * the other.\n */\nexport function configureChanged(options: { secret?: string | null }): void {\n const state = keying();\n\n state.secret = options.secret ?? null;\n state.keyed = null;\n state.signatures.clear();\n}\n\n/** The secret in use: configured, or from the environment; null when there is none. */\nfunction secretInUse(): string | null {\n const state = keying();\n\n return state.secret ?? env(\"RSC_SIGNING_SECRET\") ?? null;\n}\n\n/**\n * Refuse, in production, to sign with a key nobody else has.\n *\n * A random key is right for one process - a dev server - and silently wrong\n * for two: every tab served by one instance is refused by the next, and never\n * refreshes. The server calls this on its first request when the app uses\n * refreshOn, so the misconfiguration is an error at once rather than a page\n * that quietly stops updating.\n */\nexport function assertSigningSecret(): void {\n if (!secretInUse() && production()) throw new MissingSigningSecret();\n}\n\nlet warnedRandom = false;\n\nasync function key(): Promise<CryptoKey> {\n const state = keying();\n\n return (state.keyed ??= (async () => {\n const secret = secretInUse();\n let raw: Uint8Array<ArrayBuffer>;\n\n if (secret) raw = new TextEncoder().encode(secret);\n else if (production()) throw new MissingSigningSecret();\n else {\n if (!warnedRandom) {\n warnedRandom = true;\n console.warn(\n \"[rsc-kit] refreshOn is signing with a random key, which is fine for one dev server. \" +\n \"Set RSC_SIGNING_SECRET before running more than one instance.\",\n );\n }\n\n raw = crypto.getRandomValues(new Uint8Array(32));\n }\n\n return await crypto.subtle.importKey(\n \"raw\",\n raw,\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"],\n );\n })());\n}\n\n/** A name's signature: the first 16 bytes of an HMAC, base64url, which is what rides in a url. */\nexport function sign(name: string): Promise<string> {\n const { signatures } = keying();\n let pending = signatures.get(name);\n\n if (!pending) {\n pending = (async () => {\n const mac = await crypto.subtle.sign(\n \"HMAC\",\n await key(),\n new TextEncoder().encode(name),\n );\n\n return base64url(new Uint8Array(mac).slice(0, 16));\n })();\n\n // Bounded: a name with a visitor's id in it is a new entry per visitor.\n if (signatures.size > 10_000) signatures.clear();\n signatures.set(name, pending);\n }\n\n return pending;\n}\n\nasync function verify(name: string, signature: string): Promise<boolean> {\n const expected = await sign(name);\n\n if (expected.length !== signature.length) return false;\n\n let differ = 0;\n\n for (let i = 0; i < expected.length; i++)\n differ |= expected.charCodeAt(i) ^ signature.charCodeAt(i);\n\n return differ === 0;\n}\n\nfunction base64url(bytes: Uint8Array): string {\n let text = \"\";\n\n for (const byte of bytes) text += String.fromCharCode(byte);\n\n return btoa(text).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n/** What a page hands the client for one name: its version at render, and its signature. */\nexport type SignedName = [version: number, signature: string];\n\n// ── Watching ─────────────────────────────────────────────────────────────────\n//\n// One stream per open tab, and one ask of the source per process: every\n// stream's names are asked about together, on an interval, and a version that\n// moved is written to every stream holding its name. A thousand tabs on a\n// long-lived server cost the backend one call per interval, not a thousand.\n// On a host that answers each request in its own isolate the sharing is\n// whatever the isolate serves at once, and a lone stream asks for itself -\n// the same code, degrading to one small call per tab per interval.\n\n/** How often the source is asked, when it answered at once. */\nexport const CHANGES_INTERVAL_MS = 2_000;\n\n/** How long the source may hold one ask. Bounds how long a new tab waits to join the shared ask. */\nexport const CHANGES_WAIT_MS = 5_000;\n\ninterface Stream {\n names: Set<string>;\n send(name: string, version: number): void;\n}\n\nconst streams = new Set<Stream>();\n\n/** The latest version seen for each name any stream holds. */\nconst known = new Map<string, number>();\n\nlet running = false;\nlet wake: (() => void) | null = null;\nlet warned = false;\n\nfunction observe(name: string, version: number): void {\n if (known.get(name) === version) return;\n\n known.set(name, version);\n\n for (const stream of streams)\n if (stream.names.has(name)) stream.send(name, version);\n}\n\n/** Mapped to this process's set of streams: what to ask about, relative to what is known. */\nfunction sinceAll(): Record<string, number> {\n const since: Record<string, number> = {};\n\n for (const stream of streams)\n for (const name of stream.names) since[name] = known.get(name) ?? 0;\n\n return since;\n}\n\nasync function loop(): Promise<void> {\n running = true;\n\n try {\n while (streams.size > 0) {\n const started = Date.now();\n\n try {\n const moved = await versionSource().changed(\n sinceAll(),\n CHANGES_WAIT_MS,\n );\n\n for (const [name, version] of Object.entries(moved))\n observe(name, version);\n } catch (error) {\n // A backend blip is not the end of watching. Said once, not per interval.\n if (!warned) {\n warned = true;\n console.warn(\n \"[rsc-kit] asking the backend for name versions failed: \" +\n (error instanceof Error ? error.message : String(error)),\n );\n }\n }\n\n // A source that waited used the interval; one that answered at once is\n // asked again after it. A stream opening meanwhile wakes this early.\n const remaining = CHANGES_INTERVAL_MS - (Date.now() - started);\n\n if (remaining > 0 && streams.size > 0) {\n await new Promise<void>((resolve) => {\n const timer = setTimeout(resolve, remaining);\n\n (timer as { unref?: () => void }).unref?.();\n wake = () => {\n clearTimeout(timer);\n resolve();\n };\n });\n wake = null;\n }\n }\n } finally {\n running = false;\n // A stream that opened as the loop was ending starts it again.\n if (streams.size > 0) void loop();\n }\n}\n\nfunction parseWatch(\n url: URL,\n): { name: string; version: number; signature: string }[] | null {\n const raw = url.searchParams.get(\"w\");\n\n if (!raw) return null;\n\n let entries: unknown;\n\n try {\n entries = JSON.parse(raw);\n } catch {\n return null;\n }\n\n if (!Array.isArray(entries) || entries.length === 0 || entries.length > 200)\n return null;\n\n const parsed: { name: string; version: number; signature: string }[] = [];\n\n for (const entry of entries) {\n if (!Array.isArray(entry) || entry.length !== 3) return null;\n\n const [name, version, signature] = entry as unknown[];\n\n if (\n typeof name !== \"string\" ||\n typeof version !== \"number\" ||\n typeof signature !== \"string\"\n )\n return null;\n\n parsed.push({ name, version, signature });\n }\n\n return parsed;\n}\n\n/**\n * The stream a tab watches its names on: `GET /_rsc/changes?w=[[name, version, signature], …]`.\n *\n * One `data:` line per version that moved, `{ name, version }`, from the moment\n * the stream opens - a name that moved between the render and now is reported\n * at once, so nothing that happened in the gap is missed. Keepalives as an\n * events() route sends them; ends when the browser goes away.\n */\nexport async function changes(request: Request): Promise<Response> {\n const entries = parseWatch(new URL(request.url));\n\n if (!entries) return new Response(\"Bad watch request.\", { status: 400 });\n\n for (const { name, signature } of entries) {\n if (!(await verify(name, signature))) {\n return new Response(\"Not a name this page was rendered with.\", {\n status: 403,\n });\n }\n }\n\n const since: Record<string, number> = {};\n\n for (const { name, version } of entries) since[name] = version;\n\n const encoder = new TextEncoder();\n let stream: Stream | null = null;\n\n const body = new ReadableStream<Uint8Array>({\n start(sink) {\n const write = (text: string) => {\n try {\n sink.enqueue(encoder.encode(text));\n } catch {\n // Closed under us; the cancel below ends the rest.\n }\n };\n\n write(\"retry: 2000\\n\\n\");\n\n const keepalive = setInterval(\n () => write(\": keepalive\\n\\n\"),\n KEEPALIVE_MS,\n );\n\n (keepalive as { unref?: () => void }).unref?.();\n\n const me: Stream = {\n names: new Set(Object.keys(since)),\n send: (name, version) => write(frame({ name, version })),\n };\n\n stream = me;\n streams.add(me);\n\n const end = () => {\n clearInterval(keepalive);\n streams.delete(me);\n\n try {\n sink.close();\n } catch {\n // Already closed.\n }\n };\n\n request.signal.addEventListener(\"abort\", end, { once: true });\n\n // What is known already answers at once; what is not is seeded with\n // what this tab holds, so the shared ask compares against it.\n for (const [name, version] of Object.entries(since)) {\n const seen = known.get(name);\n\n if (seen === undefined) known.set(name, version);\n else if (seen !== version) me.send(name, seen);\n }\n\n // This tab's own first ask, holding as long as the source will: the\n // shared ask may be mid-wait on an older set of names, and a name moving\n // in the seconds before this one joins it must not go unnoticed.\n void versionSource()\n .changed(since, CHANGES_WAIT_MS)\n .then((moved) => {\n for (const [name, version] of Object.entries(moved))\n observe(name, version);\n })\n .catch(() => {});\n\n if (!running) void loop();\n else wake?.();\n },\n cancel() {\n if (stream) streams.delete(stream);\n },\n });\n\n return new Response(body, {\n headers: {\n \"Content-Type\": \"text/event-stream; charset=utf-8\",\n \"Cache-Control\": \"no-store\",\n \"X-Accel-Buffering\": \"no\",\n },\n });\n}\n\n/** For tests: forget every stream and version this process has seen. */\nexport function resetChanges(): void {\n streams.clear();\n known.clear();\n warned = false;\n delete globals[LOCAL_SOURCE];\n}\n\n/** The url a tab opens to watch, for the client. */\nexport const CHANGES_PATH = HEADER.changesPath;\n"]}