@rsc-kit/core 0.28.0 → 0.29.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 +39 -6
- package/dist/changed.js +178 -21
- package/dist/changed.js.map +1 -1
- package/dist/host.js +5 -3
- package/dist/host.js.map +1 -1
- package/dist/hostCalls.d.ts +0 -6
- package/dist/hostCalls.js +13 -16
- package/dist/hostCalls.js.map +1 -1
- package/dist/js/refreshOn.d.ts +20 -2
- package/dist/js/refreshOn.js +33 -2
- package/dist/js/refreshOn.js.map +1 -1
- package/dist/js/section.d.ts +11 -0
- package/dist/js/section.js.map +1 -1
- package/dist/testing.d.ts +12 -0
- package/dist/testing.js +29 -10
- package/dist/testing.js.map +1 -1
- package/dist/vite.js +51 -2
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
package/dist/changed.d.ts
CHANGED
|
@@ -123,7 +123,7 @@ export interface SqlVersionsOptions {
|
|
|
123
123
|
*
|
|
124
124
|
* Without it, a waiting ask reads the table every `poll` ms.
|
|
125
125
|
*/
|
|
126
|
-
listen?: (wake: () => void) => unknown;
|
|
126
|
+
listen?: (wake: () => void, health: ListenerHealth) => unknown;
|
|
127
127
|
/**
|
|
128
128
|
* Run after every bump, to tell the other instances - Postgres's NOTIFY:
|
|
129
129
|
*
|
|
@@ -180,8 +180,12 @@ export interface VersionStore {
|
|
|
180
180
|
* Called once with a function that wakes every waiting ask, when the store
|
|
181
181
|
* can say the moment a version moved - Postgres LISTEN, Redis SUBSCRIBE.
|
|
182
182
|
* Without it a waiting ask reads again every `poll` ms.
|
|
183
|
+
*
|
|
184
|
+
* Given `health` too: a store that finds it hears nothing - a Postgres
|
|
185
|
+
* listener behind a pooler in transaction mode - calls `health.deaf(why)`,
|
|
186
|
+
* and rsc-kit says so and goes back to reading every second.
|
|
183
187
|
*/
|
|
184
|
-
listen?(wake: () => void): unknown;
|
|
188
|
+
listen?(wake: () => void, health: ListenerHealth): unknown;
|
|
185
189
|
/** Run after a bump, to tell the other instances listening: Postgres NOTIFY, Redis PUBLISH. */
|
|
186
190
|
notify?(): unknown;
|
|
187
191
|
}
|
|
@@ -202,8 +206,8 @@ export declare function createVersions(store: VersionStore, options?: {
|
|
|
202
206
|
/** The parts of a Postgres client `postgresVersions` uses: postgres.js and `Bun.sql` both have them. */
|
|
203
207
|
export interface PostgresClient {
|
|
204
208
|
unsafe(text: string, params?: unknown[]): PromiseLike<readonly Record<string, unknown>[]>;
|
|
205
|
-
/** postgres.js
|
|
206
|
-
listen?(channel: string, onNotify: () => void): unknown;
|
|
209
|
+
/** postgres.js and Bun.sql have it; with it, a waiting ask hears another instance at once. */
|
|
210
|
+
listen?(channel: string, onNotify: (payload?: string) => void): unknown;
|
|
207
211
|
notify?(channel: string, payload: string): unknown;
|
|
208
212
|
}
|
|
209
213
|
/**
|
|
@@ -223,7 +227,25 @@ export declare function postgresVersions(sql: PostgresClient, options?: {
|
|
|
223
227
|
table?: string;
|
|
224
228
|
channel?: string;
|
|
225
229
|
poll?: number;
|
|
230
|
+
/**
|
|
231
|
+
* A client to listen on, when `sql` goes through a connection pooler in
|
|
232
|
+
* transaction mode - PgBouncer, or DigitalOcean's, Supabase's or Neon's
|
|
233
|
+
* pooled connection - which hands the server connection back after each
|
|
234
|
+
* statement, so a LISTEN on it hears nothing. A direct or session-mode
|
|
235
|
+
* connection; reads and writes stay on `sql`. One connection per process.
|
|
236
|
+
*/
|
|
237
|
+
listenWith?: PostgresClient;
|
|
238
|
+
/**
|
|
239
|
+
* `false`: do not listen - read every second instead, on purpose. For a
|
|
240
|
+
* database whose direct connections are too few to spend one per process
|
|
241
|
+
* on, behind a pooler that cannot listen. No listener, so no probe and no
|
|
242
|
+
* warning about one. A change made in this process still reaches its own
|
|
243
|
+
* tabs at once; one made elsewhere within about a second.
|
|
244
|
+
*/
|
|
245
|
+
listen?: boolean;
|
|
226
246
|
}): PrunableVersions;
|
|
247
|
+
/** How long a listener's probe may take to come back before it counts as deaf. */
|
|
248
|
+
export declare const LISTEN_PROBE_MS = 5000;
|
|
227
249
|
/**
|
|
228
250
|
* Install where versions are read from - a table, Redis, any store - for this
|
|
229
251
|
* process; `null` takes it out again.
|
|
@@ -232,7 +254,7 @@ export declare function postgresVersions(sql: PostgresClient, options?: {
|
|
|
232
254
|
* writes its versions to a store this process can read too, reading it here
|
|
233
255
|
* means watching costs the backend nothing.
|
|
234
256
|
*/
|
|
235
|
-
export declare function installVersionSource(source: VersionSource | null): void;
|
|
257
|
+
export declare function installVersionSource(source: VersionSource | (() => VersionSource) | null): void;
|
|
236
258
|
/** The engine's, when a backend is installed: its versions, read through it. Not for apps. */
|
|
237
259
|
export declare function installBackendVersionSource(source: VersionSource | null): void;
|
|
238
260
|
/** The source in use: the app's, else the backend's, else this process's own. */
|
|
@@ -298,7 +320,12 @@ export declare const CHANGES_WAIT_MS = 5000;
|
|
|
298
320
|
* a backend that answers at once is asked again every 30 seconds as a safety
|
|
299
321
|
* net instead of every two.
|
|
300
322
|
*/
|
|
301
|
-
|
|
323
|
+
/** What a listener is told besides how to wake: how to say it hears nothing. */
|
|
324
|
+
export interface ListenerHealth {
|
|
325
|
+
/** This listener will never wake anything: say why, once, and stop counting on it. */
|
|
326
|
+
deaf(reason: string): void;
|
|
327
|
+
}
|
|
328
|
+
export declare function wakeOn(listen: (wake: () => void, health: ListenerHealth) => unknown): void;
|
|
302
329
|
/**
|
|
303
330
|
* The stream a tab watches its names on: `GET /_rsc/changes?w=[[name, version, signature], …]`.
|
|
304
331
|
*
|
|
@@ -314,3 +341,9 @@ export declare function rememberedNames(): number;
|
|
|
314
341
|
export declare function resetChanges(): void;
|
|
315
342
|
/** The url a tab opens to watch, for the client. */
|
|
316
343
|
export declare const CHANGES_PATH: "/_rsc/changes";
|
|
344
|
+
/**
|
|
345
|
+
* The changes endpoint: a tab's stream here, or - with a hub - in the hub,
|
|
346
|
+
* where every tab's is. The hub itself answers what isolates forward to it:
|
|
347
|
+
* a stream to hold, or a poke to ask now.
|
|
348
|
+
*/
|
|
349
|
+
export declare function serveChanges(request: Request): Promise<Response>;
|
package/dist/changed.js
CHANGED
|
@@ -261,7 +261,8 @@ export function createVersions(store, options = {}) {
|
|
|
261
261
|
// table on a timer is only a safety net - for a listening connection that
|
|
262
262
|
// dropped without a word. Once an ask is enough: about every five seconds
|
|
263
263
|
// per server, where one that cannot listen reads every second.
|
|
264
|
-
|
|
264
|
+
let listening = Boolean(store.listen);
|
|
265
|
+
const poll = () => options.poll ?? (listening ? LISTENING_POLL_MS : 1_000);
|
|
265
266
|
const waiters = new Set();
|
|
266
267
|
const wakeAll = () => {
|
|
267
268
|
for (const wake of [...waiters])
|
|
@@ -270,8 +271,15 @@ export function createVersions(store, options = {}) {
|
|
|
270
271
|
// A store that can listen wakes the renderer the way anything else does -
|
|
271
272
|
// through wakeOn, which starts it with the first watching tab and never in
|
|
272
273
|
// a build. A wake also ends an ask this store is holding.
|
|
273
|
-
if (store.listen)
|
|
274
|
-
|
|
274
|
+
if (store.listen) {
|
|
275
|
+
const listen = store.listen.bind(store);
|
|
276
|
+
wakeOn((wake, health) => listen(wake, {
|
|
277
|
+
deaf: (reason) => {
|
|
278
|
+
listening = false;
|
|
279
|
+
health.deaf(reason);
|
|
280
|
+
},
|
|
281
|
+
}));
|
|
282
|
+
}
|
|
275
283
|
wakers().sleepers.add(wakeAll);
|
|
276
284
|
const read = async (names) => {
|
|
277
285
|
if (names.length === 0)
|
|
@@ -302,7 +310,7 @@ export function createVersions(store, options = {}) {
|
|
|
302
310
|
waiters.delete(done);
|
|
303
311
|
resolve();
|
|
304
312
|
};
|
|
305
|
-
const timer = setTimeout(done, Math.min(poll, remaining));
|
|
313
|
+
const timer = setTimeout(done, Math.min(poll(), remaining));
|
|
306
314
|
timer.unref?.();
|
|
307
315
|
waiters.add(done);
|
|
308
316
|
});
|
|
@@ -334,7 +342,8 @@ export function createVersions(store, options = {}) {
|
|
|
334
342
|
*/
|
|
335
343
|
export function postgresVersions(sql, options = {}) {
|
|
336
344
|
const channel = options.channel ?? "rsc_versions";
|
|
337
|
-
const
|
|
345
|
+
const listener = options.listenWith ?? sql;
|
|
346
|
+
const listenOn = options.listen === false ? undefined : listener.listen?.bind(listener);
|
|
338
347
|
// One statement per change: the upsert of every name, and the NOTIFY that
|
|
339
348
|
// wakes every listening server - this one included - when it commits.
|
|
340
349
|
return sqlVersions({
|
|
@@ -344,9 +353,38 @@ export function postgresVersions(sql, options = {}) {
|
|
|
344
353
|
channel,
|
|
345
354
|
table: options.table,
|
|
346
355
|
poll: options.poll,
|
|
347
|
-
listen:
|
|
356
|
+
listen: listenOn
|
|
357
|
+
? async (wake, health) => {
|
|
358
|
+
// A probe, sent the way a change is - through `sql` - and listened
|
|
359
|
+
// for on the listening connection. Behind a transaction-mode pooler
|
|
360
|
+
// the LISTEN lands on a connection the pooler takes back, sending
|
|
361
|
+
// still works, and nothing says the listener is deaf: changes from
|
|
362
|
+
// other instances would wait for the safety-net read. The probe
|
|
363
|
+
// not arriving is how that is found out - once, as listening starts.
|
|
364
|
+
const probe = "rsc-kit:probe:" + crypto.randomUUID();
|
|
365
|
+
let heard = false;
|
|
366
|
+
await listenOn(channel, (payload) => {
|
|
367
|
+
if (payload === probe)
|
|
368
|
+
heard = true;
|
|
369
|
+
else
|
|
370
|
+
wake();
|
|
371
|
+
});
|
|
372
|
+
await sql.unsafe("SELECT pg_notify($1, $2)", [channel, probe]);
|
|
373
|
+
const timer = setTimeout(() => {
|
|
374
|
+
if (heard)
|
|
375
|
+
return;
|
|
376
|
+
health.deaf("listening on Postgres receives no notifications - is the connection going through a pooler " +
|
|
377
|
+
"in transaction mode (PgBouncer; DigitalOcean, Supabase or Neon pooled connections)? " +
|
|
378
|
+
"Pass a direct or session-mode connection: postgresVersions(sql, { listenWith }). " +
|
|
379
|
+
"Reading every second meanwhile.");
|
|
380
|
+
}, LISTEN_PROBE_MS);
|
|
381
|
+
timer.unref?.();
|
|
382
|
+
}
|
|
383
|
+
: undefined,
|
|
348
384
|
});
|
|
349
385
|
}
|
|
386
|
+
/** How long a listener's probe may take to come back before it counts as deaf. */
|
|
387
|
+
export const LISTEN_PROBE_MS = 5_000;
|
|
350
388
|
const SOURCE = Symbol.for("rsc-kit.version-source");
|
|
351
389
|
const BACKEND_SOURCE = Symbol.for("rsc-kit.backend-version-source");
|
|
352
390
|
const LOCAL_SOURCE = Symbol.for("rsc-kit.local-version-source");
|
|
@@ -368,6 +406,11 @@ export function installBackendVersionSource(source) {
|
|
|
368
406
|
}
|
|
369
407
|
/** The source in use: the app's, else the backend's, else this process's own. */
|
|
370
408
|
export function versionSource() {
|
|
409
|
+
// A factory, made into the store on first use: a store made in register()
|
|
410
|
+
// would make its database client in the build too, which runs register()
|
|
411
|
+
// without the database's settings.
|
|
412
|
+
if (typeof globals[SOURCE] === "function")
|
|
413
|
+
globals[SOURCE] = globals[SOURCE]();
|
|
371
414
|
const chosen = globals[SOURCE] ??
|
|
372
415
|
globals[BACKEND_SOURCE];
|
|
373
416
|
if (chosen)
|
|
@@ -385,7 +428,7 @@ export function versionSource() {
|
|
|
385
428
|
"instance, or in a worker, will not reach the tabs this one serves. Install a store every " +
|
|
386
429
|
"process shares with installVersionSource() in instrumentation.ts - sqlVersions for MySQL " +
|
|
387
430
|
"or SQLite, postgresVersions for Postgres, createVersions for anything else: " +
|
|
388
|
-
"https://docs.rsc-kit.dev/guides/
|
|
431
|
+
"https://docs.rsc-kit.dev/guides/live-data#where-the-versions-live. For a deploy that is one " +
|
|
389
432
|
"instance, installVersionSource(memoryVersions()) says so.");
|
|
390
433
|
}
|
|
391
434
|
}
|
|
@@ -405,6 +448,9 @@ export function versionSource() {
|
|
|
405
448
|
*/
|
|
406
449
|
export async function changed(...names) {
|
|
407
450
|
await versionSource().bump(names);
|
|
451
|
+
// On Workers with a hub, the tabs are held by the hub, not this isolate:
|
|
452
|
+
// tell it, so they hear now rather than at the hub's next read.
|
|
453
|
+
await pokeHub();
|
|
408
454
|
}
|
|
409
455
|
// ── Tokens ───────────────────────────────────────────────────────────────────
|
|
410
456
|
//
|
|
@@ -592,19 +638,6 @@ function wakers() {
|
|
|
592
638
|
sleepers: new Set(),
|
|
593
639
|
});
|
|
594
640
|
}
|
|
595
|
-
/**
|
|
596
|
-
* Wake the renderer when a version may have moved, so it asks then rather
|
|
597
|
-
* than on its next interval.
|
|
598
|
-
*
|
|
599
|
-
* wakeOn(listenToBroadcast({ url, key })) // a broadcast server
|
|
600
|
-
* wakeOn((wake) => subscriber.subscribe('rsc', wake)) // a Redis channel
|
|
601
|
-
*
|
|
602
|
-
* `listen` is given a function to call - it carries no names and no data;
|
|
603
|
-
* the renderer asks wherever the versions live, as always. It is started
|
|
604
|
-
* when the first tab starts watching, never in a build. With one installed,
|
|
605
|
-
* a backend that answers at once is asked again every 30 seconds as a safety
|
|
606
|
-
* net instead of every two.
|
|
607
|
-
*/
|
|
608
641
|
export function wakeOn(listen) {
|
|
609
642
|
const state = wakers();
|
|
610
643
|
state.listens.push(listen);
|
|
@@ -616,8 +649,20 @@ export function wakeOn(listen) {
|
|
|
616
649
|
* again, backing off to thirty seconds. Until it is up the timer stands in.
|
|
617
650
|
*/
|
|
618
651
|
function start(listen, failures = 0) {
|
|
652
|
+
const health = {
|
|
653
|
+
deaf: (reason) => {
|
|
654
|
+
const state = wakers();
|
|
655
|
+
const at = state.listens.indexOf(listen);
|
|
656
|
+
// No longer counted: the renderer goes back to asking on the shorter
|
|
657
|
+
// interval, as it does with nothing to wake it.
|
|
658
|
+
if (at === -1)
|
|
659
|
+
return;
|
|
660
|
+
state.listens.splice(at, 1);
|
|
661
|
+
console.warn("[rsc-kit] " + reason);
|
|
662
|
+
},
|
|
663
|
+
};
|
|
619
664
|
void Promise.resolve()
|
|
620
|
-
.then(() => listen(signal))
|
|
665
|
+
.then(() => listen(signal, health))
|
|
621
666
|
.catch((error) => {
|
|
622
667
|
if (failures === 0) {
|
|
623
668
|
console.warn("[rsc-kit] a wakeOn listener failed to start, trying again: " +
|
|
@@ -820,6 +865,7 @@ export function rememberedNames() {
|
|
|
820
865
|
return known.size;
|
|
821
866
|
}
|
|
822
867
|
export function resetChanges() {
|
|
868
|
+
warnedHub = false;
|
|
823
869
|
delete globals[WAKERS];
|
|
824
870
|
streams.clear();
|
|
825
871
|
known.clear();
|
|
@@ -828,4 +874,115 @@ export function resetChanges() {
|
|
|
828
874
|
}
|
|
829
875
|
/** The url a tab opens to watch, for the client. */
|
|
830
876
|
export const CHANGES_PATH = HEADER.changesPath;
|
|
877
|
+
// ── The hub ──────────────────────────────────────────────────────────────────
|
|
878
|
+
//
|
|
879
|
+
// On Workers each isolate watches for the tabs it happens to hold, which is
|
|
880
|
+
// often one: an ask per tab per interval, a listener per isolate. A Durable
|
|
881
|
+
// Object is one place every isolate can reach. With RSC_CHANGES_HUB naming
|
|
882
|
+
// its binding, every tab's stream is forwarded to one instance of it, which
|
|
883
|
+
// runs the shared ask above for all of them - one ask an interval for the
|
|
884
|
+
// whole deployment, one listener, and a changed() anywhere wakes it at once.
|
|
885
|
+
//
|
|
886
|
+
// Opt-in, by name: the binding is the app's, declared in its wrangler config,
|
|
887
|
+
// and nothing here guesses that a Durable Object is there to use.
|
|
888
|
+
/** Marks a request the hub sent itself; its value is signed, so a browser cannot. */
|
|
889
|
+
const HUB_HEADER = "x-rsc-changes-hub";
|
|
890
|
+
/** The one instance every isolate's streams go to. */
|
|
891
|
+
const HUB_INSTANCE = "rsc-kit-changes";
|
|
892
|
+
/** What the header's value is the signature of. */
|
|
893
|
+
const HUB_PROOF = "rsc-kit:changes-hub";
|
|
894
|
+
let warnedHub = false;
|
|
895
|
+
/** The hub's stub, when RSC_CHANGES_HUB names a Durable Object binding; null otherwise. */
|
|
896
|
+
async function hubStub(request) {
|
|
897
|
+
const binding = env("RSC_CHANGES_HUB");
|
|
898
|
+
if (!binding)
|
|
899
|
+
return null;
|
|
900
|
+
let bindings = request?.runtime?.cloudflare?.env;
|
|
901
|
+
// Nitro's Workers entry keeps the bindings here for code with no request -
|
|
902
|
+
// changed() from a webhook's handler.
|
|
903
|
+
bindings ??= globalThis.__env__;
|
|
904
|
+
if (!bindings) {
|
|
905
|
+
try {
|
|
906
|
+
// Computed, so neither the bundler nor tsc looks for a module only the
|
|
907
|
+
// Workers runtime has.
|
|
908
|
+
const specifier = "cloudflare:workers";
|
|
909
|
+
bindings = (await import(/* @vite-ignore */ specifier)).env;
|
|
910
|
+
}
|
|
911
|
+
catch {
|
|
912
|
+
bindings = undefined;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
const namespace = bindings?.[binding];
|
|
916
|
+
if (!namespace || typeof namespace.idFromName !== "function") {
|
|
917
|
+
if (!warnedHub) {
|
|
918
|
+
warnedHub = true;
|
|
919
|
+
console.warn(`[rsc-kit] RSC_CHANGES_HUB names ${JSON.stringify(binding)}, which is not a Durable Object binding ` +
|
|
920
|
+
"here: each isolate watches for its own tabs. Declare the binding in wrangler config: " +
|
|
921
|
+
"https://docs.rsc-kit.dev/guides/live-data#one-hub-on-workers");
|
|
922
|
+
}
|
|
923
|
+
return null;
|
|
924
|
+
}
|
|
925
|
+
return namespace.get(namespace.idFromName(HUB_INSTANCE));
|
|
926
|
+
}
|
|
927
|
+
/** Whether this request came from an isolate forwarding to the hub. */
|
|
928
|
+
async function fromIsolate(request) {
|
|
929
|
+
const proof = request.headers.get(HUB_HEADER);
|
|
930
|
+
return proof !== null && (await verify(HUB_PROOF, proof));
|
|
931
|
+
}
|
|
932
|
+
/** Tell the hub something changed, so the tabs it holds hear at once. */
|
|
933
|
+
async function pokeHub() {
|
|
934
|
+
let stub;
|
|
935
|
+
try {
|
|
936
|
+
stub = await hubStub();
|
|
937
|
+
}
|
|
938
|
+
catch {
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
if (!stub)
|
|
942
|
+
return;
|
|
943
|
+
try {
|
|
944
|
+
await stub.fetch(new Request("https://hub" + HEADER.changesPath, {
|
|
945
|
+
method: "POST",
|
|
946
|
+
headers: { [HUB_HEADER]: await sign(HUB_PROOF) },
|
|
947
|
+
}));
|
|
948
|
+
}
|
|
949
|
+
catch (error) {
|
|
950
|
+
// The change is in the store; the hub reads it at its next ask.
|
|
951
|
+
if (!warnedHub) {
|
|
952
|
+
warnedHub = true;
|
|
953
|
+
console.warn("[rsc-kit] telling the changes hub failed; its tabs hear at its next read: " +
|
|
954
|
+
(error instanceof Error ? error.message : String(error)));
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* The changes endpoint: a tab's stream here, or - with a hub - in the hub,
|
|
960
|
+
* where every tab's is. The hub itself answers what isolates forward to it:
|
|
961
|
+
* a stream to hold, or a poke to ask now.
|
|
962
|
+
*/
|
|
963
|
+
export async function serveChanges(request) {
|
|
964
|
+
// Forwarded, but not with this key: never forwarded again, which would be
|
|
965
|
+
// a loop - the hub reaching itself - and never served as if from the hub.
|
|
966
|
+
if (request.headers.has(HUB_HEADER) && !(await fromIsolate(request))) {
|
|
967
|
+
return new Response("Not from this deployment's isolates.", { status: 403 });
|
|
968
|
+
}
|
|
969
|
+
if (request.headers.has(HUB_HEADER)) {
|
|
970
|
+
if (request.method === "POST") {
|
|
971
|
+
signal();
|
|
972
|
+
return new Response(null, { status: 204 });
|
|
973
|
+
}
|
|
974
|
+
return await changes(request);
|
|
975
|
+
}
|
|
976
|
+
if (request.method !== "GET")
|
|
977
|
+
return new Response("Method not allowed.", { status: 405 });
|
|
978
|
+
const stub = await hubStub(request);
|
|
979
|
+
if (!stub)
|
|
980
|
+
return await changes(request);
|
|
981
|
+
const headers = new Headers(request.headers);
|
|
982
|
+
headers.set(HUB_HEADER, await sign(HUB_PROOF));
|
|
983
|
+
const held = await stub.fetch(new Request(request.url, { method: "GET", headers, signal: request.signal }));
|
|
984
|
+
// A stub's response has immutable headers, and the response is still the
|
|
985
|
+
// app's to finish - the server adds its own on the way out.
|
|
986
|
+
return new Response(held.body, held);
|
|
987
|
+
}
|
|
831
988
|
//# sourceMappingURL=changed.js.map
|