@sveltejs/kit 3.0.0-next.2 → 3.0.0-next.21
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/package.json +62 -36
- package/src/cli.js +15 -12
- package/src/constants.js +6 -1
- package/src/core/adapt/builder.js +66 -72
- package/src/core/adapt/index.js +17 -6
- package/src/core/config/index.js +134 -80
- package/src/core/config/options.js +295 -273
- package/src/core/config/types.d.ts +1 -1
- package/src/core/env.js +146 -16
- package/src/core/generate_manifest/index.js +18 -15
- package/src/core/postbuild/analyse.js +8 -20
- package/src/core/postbuild/crawl.js +22 -6
- package/src/core/postbuild/entities.js +8 -2
- package/src/core/postbuild/fallback.js +4 -2
- package/src/core/postbuild/prerender.js +222 -70
- package/src/core/postbuild/queue.js +2 -1
- package/src/core/sync/create_manifest_data/conflict.js +1 -1
- package/src/core/sync/create_manifest_data/index.js +82 -62
- package/src/core/sync/sync.js +40 -31
- package/src/core/sync/ts.js +1 -1
- package/src/core/sync/utils.js +22 -2
- package/src/core/sync/{write_non_ambient.js → write_app_types.js} +107 -58
- package/src/core/sync/write_client_manifest.js +14 -29
- package/src/core/sync/write_env.js +7 -5
- package/src/core/sync/write_server.js +37 -45
- package/src/core/sync/write_tsconfig/index.js +274 -0
- package/src/core/sync/write_tsconfig/test-app/package.json +7 -0
- package/src/core/sync/write_tsconfig/utils.js +77 -0
- package/src/core/sync/write_tsconfig/validate.js +128 -0
- package/src/core/sync/write_types/index.js +127 -123
- package/src/core/utils.js +30 -5
- package/src/exports/env/index.js +77 -0
- package/src/exports/env/public.d.ts +55 -0
- package/src/exports/hooks/index.js +3 -9
- package/src/exports/hooks/public.d.ts +195 -0
- package/src/exports/hooks/sequence.js +9 -6
- package/src/exports/index.js +52 -29
- package/src/exports/internal/client.js +5 -0
- package/src/exports/internal/env.js +8 -5
- package/src/exports/internal/index.js +1 -90
- package/src/exports/internal/{event.js → server/event.js} +2 -3
- package/src/exports/internal/server/index.js +37 -0
- package/src/exports/internal/server/telemetry.js +95 -0
- package/src/exports/internal/shared.js +90 -0
- package/src/exports/node/index.js +64 -22
- package/src/exports/params/index.js +70 -0
- package/src/exports/params/public.d.ts +63 -0
- package/src/exports/public.d.ts +50 -1733
- package/src/exports/remote/index.js +11 -0
- package/src/exports/remote/public.d.ts +519 -0
- package/src/exports/url.js +86 -0
- package/src/exports/vite/build/build_server.js +54 -65
- package/src/exports/vite/build/remote.js +24 -19
- package/src/exports/vite/build/utils.js +0 -8
- package/src/exports/vite/dev/index.js +218 -146
- package/src/exports/vite/index.js +1590 -858
- package/src/exports/vite/module_ids.js +2 -2
- package/src/exports/vite/preview/index.js +40 -32
- package/src/exports/vite/public.d.ts +588 -0
- package/src/exports/vite/utils.js +84 -46
- package/src/pathname.js +55 -0
- package/src/runner.js +15 -0
- package/src/runtime/app/env/internal.js +4 -4
- package/src/runtime/app/env/types.d.ts +1 -1
- package/src/runtime/app/environment/index.js +3 -3
- package/src/runtime/app/{forms.js → forms/index.js} +73 -42
- package/src/runtime/app/forms/public.d.ts +2 -0
- package/src/runtime/app/forms/types.d.ts +56 -0
- package/src/runtime/app/internal/transport.js +53 -0
- package/src/runtime/app/manifest/index.js +1 -0
- package/src/runtime/app/{navigation.js → navigation/index.js} +2 -1
- package/src/runtime/app/navigation/public.d.ts +237 -0
- package/src/runtime/app/paths/client.js +37 -37
- package/src/runtime/app/paths/index.js +1 -1
- package/src/runtime/app/paths/internal/client.js +34 -2
- package/src/runtime/app/paths/internal/server.js +6 -23
- package/src/runtime/app/paths/internal.d.ts +3 -0
- package/src/runtime/app/paths/public.d.ts +1 -29
- package/src/runtime/app/paths/server.js +36 -17
- package/src/runtime/app/paths/types.d.ts +11 -19
- package/src/runtime/app/server/index.js +2 -2
- package/src/runtime/app/server/public.d.ts +201 -0
- package/src/runtime/app/server/remote/command.js +13 -11
- package/src/runtime/app/server/remote/form.js +61 -39
- package/src/runtime/app/server/remote/prerender.js +45 -45
- package/src/runtime/app/server/remote/query.js +106 -108
- package/src/runtime/app/server/remote/requested.js +27 -19
- package/src/runtime/app/server/remote/shared.js +76 -76
- package/src/runtime/app/service-worker/index.js +24 -0
- package/src/runtime/app/state/client.js +4 -2
- package/src/runtime/app/state/index.js +7 -5
- package/src/runtime/app/state/public.d.ts +72 -0
- package/src/runtime/app/state/server.js +3 -0
- package/src/runtime/app/stores.js +15 -78
- package/src/runtime/client/bundle.js +1 -1
- package/src/runtime/client/client-entry.js +3 -0
- package/src/runtime/client/client.js +1336 -648
- package/src/runtime/client/constants.js +3 -6
- package/src/runtime/client/entry.js +24 -3
- package/src/runtime/client/fetcher.js +25 -25
- package/src/runtime/client/ndjson.js +1 -1
- package/src/runtime/client/parse.js +1 -1
- package/src/runtime/client/payload.js +17 -0
- package/src/runtime/client/remote-functions/cache.svelte.js +3 -1
- package/src/runtime/client/remote-functions/command.svelte.js +37 -61
- package/src/runtime/client/remote-functions/form.svelte.js +267 -178
- package/src/runtime/client/remote-functions/prerender.svelte.js +32 -11
- package/src/runtime/client/remote-functions/query/index.js +10 -17
- package/src/runtime/client/remote-functions/query/instance.svelte.js +64 -18
- package/src/runtime/client/remote-functions/query/proxy.js +5 -5
- package/src/runtime/client/remote-functions/query-batch.svelte.js +61 -70
- package/src/runtime/client/remote-functions/query-live/index.js +1 -1
- package/src/runtime/client/remote-functions/query-live/instance.svelte.js +63 -21
- package/src/runtime/client/remote-functions/query-live/iterator.js +15 -12
- package/src/runtime/client/remote-functions/query-live/proxy.js +2 -12
- package/src/runtime/client/remote-functions/shared.svelte.js +90 -65
- package/src/runtime/client/snapshots.js +147 -0
- package/src/runtime/client/state.svelte.js +94 -55
- package/src/runtime/client/stream.js +3 -2
- package/src/runtime/client/types.d.ts +13 -9
- package/src/runtime/client/utils.js +22 -110
- package/src/runtime/components/root.svelte +56 -0
- package/src/runtime/env/dynamic/private.js +7 -0
- package/src/runtime/env/dynamic/public.js +7 -0
- package/src/runtime/env/static/private.js +6 -0
- package/src/runtime/env/static/public.js +6 -0
- package/src/runtime/form-utils.js +172 -61
- package/src/runtime/pathname.js +20 -32
- package/src/runtime/props.svelte.js +72 -0
- package/src/runtime/server/constants.js +0 -3
- package/src/runtime/server/cookie.js +72 -55
- package/src/runtime/server/csrf.js +65 -0
- package/src/runtime/server/data/index.js +49 -53
- package/src/runtime/server/dev.js +22 -0
- package/src/runtime/server/endpoint.js +6 -7
- package/src/runtime/server/env_module.js +0 -5
- package/src/runtime/server/errors.js +160 -0
- package/src/runtime/server/fetch.js +32 -39
- package/src/runtime/server/index.js +90 -64
- package/src/runtime/server/internal.js +71 -0
- package/src/runtime/server/page/actions.js +85 -64
- package/src/runtime/server/page/crypto.js +2 -2
- package/src/runtime/server/page/csp.js +88 -104
- package/src/runtime/server/page/data_serializer.js +24 -25
- package/src/runtime/server/page/index.js +36 -53
- package/src/runtime/server/page/load_data.js +50 -57
- package/src/runtime/server/page/render.js +173 -239
- package/src/runtime/server/page/respond_with_error.js +17 -31
- package/src/runtime/server/page/serialize_data.js +2 -13
- package/src/runtime/server/page/server_routing.js +85 -26
- package/src/runtime/server/remote-functions.js +640 -0
- package/src/runtime/server/respond.js +190 -131
- package/src/runtime/server/sourcemaps.js +183 -0
- package/src/runtime/server/state.js +53 -0
- package/src/runtime/server/utils.js +13 -155
- package/src/runtime/shared.js +20 -40
- package/src/runtime/utils.js +3 -0
- package/src/types/ambient-private.d.ts +11 -1
- package/src/types/ambient.d.ts +87 -36
- package/src/types/global-private.d.ts +25 -25
- package/src/types/internal.d.ts +163 -157
- package/src/types/private.d.ts +41 -1
- package/src/utils/error.js +28 -4
- package/src/utils/escape.js +9 -25
- package/src/utils/features.js +1 -1
- package/src/utils/filesystem.js +1 -23
- package/src/utils/fork.js +7 -2
- package/src/utils/hash.js +21 -0
- package/src/utils/http.js +8 -7
- package/src/utils/import.js +9 -2
- package/src/utils/imports.js +83 -0
- package/src/utils/mime.js +9 -0
- package/src/utils/page_nodes.js +6 -7
- package/src/utils/params.js +67 -0
- package/src/utils/regex.js +9 -0
- package/src/utils/routing.js +145 -73
- package/src/utils/shared-iterator.js +5 -0
- package/src/utils/streaming.js +14 -4
- package/src/utils/url.js +33 -2
- package/src/version.js +1 -1
- package/types/index.d.ts +2616 -2433
- package/types/index.d.ts.map +120 -106
- package/src/core/sync/write_ambient.js +0 -18
- package/src/core/sync/write_root.js +0 -148
- package/src/core/sync/write_tsconfig.js +0 -250
- package/src/exports/internal/server.js +0 -22
- package/src/exports/node/polyfills.js +0 -30
- package/src/runtime/server/app.js +0 -9
- package/src/runtime/server/remote.js +0 -457
- package/src/runtime/shared-server.js +0 -7
- package/src/runtime/telemetry/otel.js +0 -21
- package/src/runtime/telemetry/record_span.js +0 -65
- package/src/types/synthetic/$lib.md +0 -5
- /package/src/exports/internal/{remote-functions.js → server/remote-functions.js} +0 -0
- /package/src/{runtime/telemetry/noop.js → telemetry.js} +0 -0
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import { HttpError, Redirect } from '@sveltejs/kit/internal';
|
|
1
|
+
import { query_responses, handle_error } from '../../client.js';
|
|
2
|
+
import { HttpError, Redirect, HandledHttpError } from '@sveltejs/kit/internal';
|
|
4
3
|
import { noop, once } from '../../../../utils/functions.js';
|
|
5
4
|
import { SharedIterator } from '../../../../utils/shared-iterator.js';
|
|
6
|
-
import {
|
|
5
|
+
import { tick } from 'svelte';
|
|
7
6
|
import { create_live_iterator } from './iterator.js';
|
|
8
7
|
|
|
9
8
|
/**
|
|
@@ -25,7 +24,7 @@ export class LiveQuery {
|
|
|
25
24
|
#done = $state(false);
|
|
26
25
|
/** @type {T | undefined} */
|
|
27
26
|
#raw = $state.raw();
|
|
28
|
-
/** @type {
|
|
27
|
+
/** @type {App.Error | undefined} */
|
|
29
28
|
#error = $state.raw(undefined);
|
|
30
29
|
/** @type {Promise<void>} */
|
|
31
30
|
#promise;
|
|
@@ -84,9 +83,25 @@ export class LiveQuery {
|
|
|
84
83
|
this.#resolve_first = resolve;
|
|
85
84
|
this.#reject_first = reject;
|
|
86
85
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
if (Object.hasOwn(query_responses, key)) {
|
|
87
|
+
const node = query_responses[key];
|
|
88
|
+
delete query_responses[key];
|
|
89
|
+
|
|
90
|
+
if (node.e) {
|
|
91
|
+
// the query failed during SSR — seed the failed state (mirroring `fail()`,
|
|
92
|
+
// minus its terminal `#done`), so the main loop still connects as usual
|
|
93
|
+
// and the query can recover
|
|
94
|
+
const error = new HandledHttpError(node.e);
|
|
95
|
+
this.#loading = false;
|
|
96
|
+
this.#error = error.body;
|
|
97
|
+
|
|
98
|
+
promise.catch(noop);
|
|
99
|
+
this.#reject_first?.(error);
|
|
100
|
+
this.#resolve_first = null;
|
|
101
|
+
this.#reject_first = null;
|
|
102
|
+
} else {
|
|
103
|
+
this.set(node.v);
|
|
104
|
+
}
|
|
90
105
|
}
|
|
91
106
|
}
|
|
92
107
|
|
|
@@ -107,6 +122,7 @@ export class LiveQuery {
|
|
|
107
122
|
|
|
108
123
|
/** @type {PromiseWithResolvers<void>} */
|
|
109
124
|
const { promise: stopped, resolve: on_stop } = Promise.withResolvers();
|
|
125
|
+
let connected = false;
|
|
110
126
|
|
|
111
127
|
while (!this.#done) {
|
|
112
128
|
const controller = new AbortController();
|
|
@@ -119,16 +135,22 @@ export class LiveQuery {
|
|
|
119
135
|
const generator = create_live_iterator(this.#id, this.#payload, controller, () => {
|
|
120
136
|
this.#connected = true;
|
|
121
137
|
this.#attempt = 0;
|
|
138
|
+
connected = true;
|
|
122
139
|
on_connect();
|
|
123
140
|
});
|
|
124
141
|
|
|
125
142
|
try {
|
|
126
143
|
const { done, value } = await generator.next();
|
|
127
144
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
145
|
+
if (done) {
|
|
146
|
+
if (!this.#ready) {
|
|
147
|
+
throw new Error('Live query completed before yielding a value');
|
|
148
|
+
}
|
|
149
|
+
// stream completed without yielding (e.g. the generator returned
|
|
150
|
+
// immediately on reconnect) — keep the last good value
|
|
151
|
+
this.#done = true;
|
|
152
|
+
this.#fan_out.done();
|
|
153
|
+
break;
|
|
132
154
|
}
|
|
133
155
|
|
|
134
156
|
this.set(value);
|
|
@@ -153,14 +175,14 @@ export class LiveQuery {
|
|
|
153
175
|
|
|
154
176
|
if (!this.#ready) {
|
|
155
177
|
// If we haven't successfully connected and received a value yet, surface the error
|
|
156
|
-
this
|
|
178
|
+
await this.#fail(error);
|
|
157
179
|
on_connect_failed(error);
|
|
158
180
|
break;
|
|
159
181
|
}
|
|
160
182
|
|
|
161
183
|
if (error instanceof HttpError) {
|
|
162
|
-
// Server intentionally
|
|
163
|
-
this
|
|
184
|
+
// Server or client intentionally produced an error. Surface it and stop.
|
|
185
|
+
await this.#fail(error);
|
|
164
186
|
break;
|
|
165
187
|
}
|
|
166
188
|
|
|
@@ -185,6 +207,12 @@ export class LiveQuery {
|
|
|
185
207
|
}
|
|
186
208
|
|
|
187
209
|
this.#interrupt = null;
|
|
210
|
+
// If the loop exited without ever successfully connecting, settle the
|
|
211
|
+
// reconnect handshake so callers (e.g. `invalidateAll()`) never await
|
|
212
|
+
// a forever-pending promise.
|
|
213
|
+
if (!connected) {
|
|
214
|
+
on_connect_failed(this.#error ?? new Error('Live query connection was interrupted'));
|
|
215
|
+
}
|
|
188
216
|
on_stop();
|
|
189
217
|
}
|
|
190
218
|
|
|
@@ -328,10 +356,13 @@ export class LiveQuery {
|
|
|
328
356
|
promise.catch(noop);
|
|
329
357
|
this.#done = false;
|
|
330
358
|
this.#attempt = 0;
|
|
331
|
-
//
|
|
332
|
-
//
|
|
333
|
-
//
|
|
334
|
-
|
|
359
|
+
// Keep the existing fan-out open so active `for await` consumers
|
|
360
|
+
// continue receiving values from the new connection without interruption.
|
|
361
|
+
// Only replace it if it was already closed by a prior `done()`/`fail()`
|
|
362
|
+
// (e.g. reconnecting after a finite or hard-failed stream)
|
|
363
|
+
if (this.#fan_out.closed) {
|
|
364
|
+
this.#fan_out = new SharedIterator();
|
|
365
|
+
}
|
|
335
366
|
this.#main({ on_connect, on_connect_failed }).catch(noop);
|
|
336
367
|
await promise;
|
|
337
368
|
}
|
|
@@ -354,10 +385,10 @@ export class LiveQuery {
|
|
|
354
385
|
this.#fan_out.push(value);
|
|
355
386
|
}
|
|
356
387
|
|
|
357
|
-
/** @param {
|
|
388
|
+
/** @param {HttpError} error */
|
|
358
389
|
fail(error) {
|
|
359
390
|
this.#loading = false;
|
|
360
|
-
this.#error = error;
|
|
391
|
+
this.#error = error.body;
|
|
361
392
|
// `fail` is terminal — once a live query has hard-failed, the only way to start
|
|
362
393
|
// streaming again is via `reconnect()`. Mark it done and abort any in-flight
|
|
363
394
|
// request so that callers from outside the main loop (e.g. `apply_reconnections`)
|
|
@@ -366,6 +397,7 @@ export class LiveQuery {
|
|
|
366
397
|
void this.#interrupt?.();
|
|
367
398
|
|
|
368
399
|
if (this.#reject_first) {
|
|
400
|
+
this.#promise.catch(noop);
|
|
369
401
|
this.#reject_first(error);
|
|
370
402
|
this.#resolve_first = null;
|
|
371
403
|
this.#reject_first = null;
|
|
@@ -378,6 +410,16 @@ export class LiveQuery {
|
|
|
378
410
|
this.#fan_out.fail(error);
|
|
379
411
|
}
|
|
380
412
|
|
|
413
|
+
/** @param {unknown} e */
|
|
414
|
+
async #fail(e) {
|
|
415
|
+
const error = await handle_error(e, {
|
|
416
|
+
params: {},
|
|
417
|
+
route: { id: null },
|
|
418
|
+
url: new URL(location.href)
|
|
419
|
+
});
|
|
420
|
+
this.fail(new HandledHttpError(error));
|
|
421
|
+
}
|
|
422
|
+
|
|
381
423
|
get [Symbol.toStringTag]() {
|
|
382
424
|
return 'LiveQuery';
|
|
383
425
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
/** @import { RemoteFunctionResponse } from 'types' */
|
|
2
|
+
import { app_dir, base } from '#app/paths';
|
|
2
3
|
import { app } from '../../client.js';
|
|
3
|
-
import {
|
|
4
|
+
import { notify_version } from '../../state.svelte.js';
|
|
5
|
+
import { handle_side_channel_response } from '../shared.svelte.js';
|
|
4
6
|
import * as devalue from 'devalue';
|
|
5
|
-
import { HttpError } from '@sveltejs/kit/internal';
|
|
7
|
+
import { HttpError, HandledHttpError } from '@sveltejs/kit/internal';
|
|
6
8
|
import { noop } from '../../../../utils/functions.js';
|
|
7
9
|
import { read_sse } from '../../sse.js';
|
|
8
10
|
|
|
@@ -23,25 +25,26 @@ export async function* create_live_iterator(
|
|
|
23
25
|
const url = `${base}/${app_dir}/remote/${id}${payload ? `?payload=${payload}` : ''}`;
|
|
24
26
|
|
|
25
27
|
const response = await fetch(url, {
|
|
26
|
-
headers: get_remote_request_headers(),
|
|
27
28
|
signal: controller.signal
|
|
28
29
|
});
|
|
29
30
|
|
|
31
|
+
// detect new deployments from the response header
|
|
32
|
+
notify_version(response.headers.get('x-sveltekit-version'));
|
|
33
|
+
|
|
30
34
|
if (!response.ok) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
status: response.status,
|
|
34
|
-
error: response.statusText
|
|
35
|
-
}));
|
|
35
|
+
/** @type {RemoteFunctionResponse | undefined} */
|
|
36
|
+
const result = await response.json().catch(() => undefined);
|
|
36
37
|
|
|
37
|
-
throw
|
|
38
|
+
throw result?.type === 'error'
|
|
39
|
+
? new HandledHttpError(result.error)
|
|
40
|
+
: new HttpError({ status: response.status, message: response.statusText });
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
if (response.headers.get('content-type')?.includes('application/json')) {
|
|
41
44
|
// we can end up here if we e.g. redirect in `handle`
|
|
42
45
|
const result = await response.json();
|
|
43
46
|
await handle_side_channel_response(result);
|
|
44
|
-
throw new HttpError(500, 'Invalid query.live response');
|
|
47
|
+
throw new HttpError({ status: 500, message: 'Invalid query.live response' });
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
if (!response.body) {
|
|
@@ -60,7 +63,7 @@ export async function* create_live_iterator(
|
|
|
60
63
|
}
|
|
61
64
|
|
|
62
65
|
await handle_side_channel_response(node);
|
|
63
|
-
throw new HttpError(500, 'Invalid query.live response');
|
|
66
|
+
throw new HttpError({ status: 500, message: 'Invalid query.live response' });
|
|
64
67
|
}
|
|
65
68
|
} finally {
|
|
66
69
|
try {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { live_query_map } from '../../client.js';
|
|
2
2
|
import { pin_in_effect, pin_while_resolving, QUERY_RESOURCE_KEY } from '../shared.svelte.js';
|
|
3
3
|
import { create_remote_key, stringify_remote_arg } from '../../../shared.js';
|
|
4
4
|
import { LiveQuery } from './instance.svelte.js';
|
|
@@ -20,7 +20,7 @@ export class LiveQueryProxy {
|
|
|
20
20
|
*/
|
|
21
21
|
constructor(id, arg) {
|
|
22
22
|
this.#id = id;
|
|
23
|
-
this.#payload = stringify_remote_arg(arg
|
|
23
|
+
this.#payload = stringify_remote_arg(arg);
|
|
24
24
|
this.#key = create_remote_key(id, this.#payload);
|
|
25
25
|
Object.defineProperty(this, QUERY_RESOURCE_KEY, { value: this.#key });
|
|
26
26
|
|
|
@@ -74,16 +74,6 @@ export class LiveQueryProxy {
|
|
|
74
74
|
return this.#get_cached_query().done;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
/**
|
|
78
|
-
* @deprecated Use `for await (const value of liveQuery())` instead.
|
|
79
|
-
* @returns {AsyncGenerator<T>}
|
|
80
|
-
*/
|
|
81
|
-
run() {
|
|
82
|
-
throw new Error(
|
|
83
|
-
'`.run()` has been removed from live queries. Use `for await (const value of liveQuery())` instead.'
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
77
|
reconnect() {
|
|
88
78
|
return this.#get_cached_query().reconnect();
|
|
89
79
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
/** @import { RemoteFunctionResponse,
|
|
2
|
-
/** @import { RemoteQueryUpdate } from '@sveltejs/kit' */
|
|
1
|
+
/** @import { RemoteFunctionResponse, RemoteFunctionData, RemoteFunctionDataNode } from 'types' */
|
|
2
|
+
/** @import { RemoteQueryUpdate } from '@sveltejs/kit/remote' */
|
|
3
|
+
/** @import { CacheEntry } from './cache.svelte.js' */
|
|
3
4
|
import * as devalue from 'devalue';
|
|
4
|
-
import { app,
|
|
5
|
-
import { HttpError, Redirect } from '@sveltejs/kit/internal';
|
|
5
|
+
import { app, _goto, live_query_map, query_map, query_responses } from '../client.js';
|
|
6
|
+
import { HttpError, Redirect, HandledHttpError } from '@sveltejs/kit/internal';
|
|
6
7
|
import { untrack } from 'svelte';
|
|
7
8
|
import { create_remote_key, split_remote_key } from '../../shared.js';
|
|
8
|
-
import { navigating, page } from '../state.svelte.js';
|
|
9
|
+
import { navigating, page, notify_version } from '../state.svelte.js';
|
|
9
10
|
|
|
10
11
|
/** Indicates a query function, as opposed to a query instance */
|
|
11
12
|
export const QUERY_FUNCTION_ID = Symbol('sveltekit.query_function_id');
|
|
@@ -77,6 +78,20 @@ export function pin_while_resolving(cache_map, cache, id, payload, then) {
|
|
|
77
78
|
/**
|
|
78
79
|
* @returns {{ 'x-sveltekit-pathname': string, 'x-sveltekit-search': string }}
|
|
79
80
|
*/
|
|
81
|
+
/**
|
|
82
|
+
* Unwraps a `RemoteFunctionDataNode` that was serialized during SSR,
|
|
83
|
+
* rethrowing serialized errors so the consuming resource ends up
|
|
84
|
+
* in the same failed state it had on the server
|
|
85
|
+
* @param {RemoteFunctionDataNode} node
|
|
86
|
+
*/
|
|
87
|
+
export function unwrap_node(node) {
|
|
88
|
+
if (node.e) {
|
|
89
|
+
throw new HandledHttpError(node.e);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return node.v;
|
|
93
|
+
}
|
|
94
|
+
|
|
80
95
|
export function get_remote_request_headers() {
|
|
81
96
|
// This will be the correct value of the current or soon-current url,
|
|
82
97
|
// even in forks because it's state-based - therefore not using window.location.
|
|
@@ -93,25 +108,81 @@ export function get_remote_request_headers() {
|
|
|
93
108
|
|
|
94
109
|
/**
|
|
95
110
|
* @param {string} url
|
|
96
|
-
* @param {
|
|
111
|
+
* @param {RequestInit} [init]
|
|
97
112
|
*/
|
|
98
|
-
export async function remote_request(url,
|
|
99
|
-
const response = await fetch(url,
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
});
|
|
113
|
+
export async function remote_request(url, init) {
|
|
114
|
+
const response = await fetch(url, init);
|
|
115
|
+
const status = response.status;
|
|
116
|
+
|
|
117
|
+
// detect new deployments from the response header
|
|
118
|
+
notify_version(response.headers.get('x-sveltekit-version'));
|
|
105
119
|
|
|
106
120
|
if (!response.ok) {
|
|
107
|
-
|
|
121
|
+
const result = await response.json().catch(() => undefined);
|
|
122
|
+
|
|
123
|
+
throw result?.type === 'error'
|
|
124
|
+
? new HandledHttpError({ status, ...result.error })
|
|
125
|
+
: new HttpError({ status, message: response.statusText });
|
|
108
126
|
}
|
|
109
127
|
|
|
110
128
|
const result = /** @type {RemoteFunctionResponse} */ (await response.json());
|
|
111
129
|
|
|
112
|
-
|
|
130
|
+
if (result.type === 'error') {
|
|
131
|
+
throw new HandledHttpError(result.error);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const data = /** @type {RemoteFunctionData} */ (
|
|
135
|
+
result.data ? devalue.parse(result.data, app.decoders) : {}
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* @param {string} key
|
|
140
|
+
* @param {CacheEntry | undefined} entry
|
|
141
|
+
* @param {any} result
|
|
142
|
+
*/
|
|
143
|
+
function refresh(key, entry, result) {
|
|
144
|
+
if (entry?.resource) {
|
|
145
|
+
if (result.e) {
|
|
146
|
+
entry.resource.fail(new HandledHttpError(result.e));
|
|
147
|
+
} else {
|
|
148
|
+
entry.resource.set(result.v);
|
|
149
|
+
}
|
|
150
|
+
} else if (!result.e) {
|
|
151
|
+
// `query_responses` stores `{ v }`/`{ e }` nodes, not raw values.
|
|
152
|
+
// Errors are deliberately dropped here: they are responses to a specific
|
|
153
|
+
// refresh, not durable state a future resource should initialize with
|
|
154
|
+
query_responses[key] = result;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// update queries with refreshed data
|
|
159
|
+
if (data.q) {
|
|
160
|
+
for (const key in data.q) {
|
|
161
|
+
const parts = split_remote_key(key);
|
|
162
|
+
const entry = query_map.get(parts.id)?.get(parts.payload);
|
|
163
|
+
|
|
164
|
+
refresh(key, entry, data.q[key]);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// reconnect live queries
|
|
169
|
+
if (data.l) {
|
|
170
|
+
for (const key in data.l) {
|
|
171
|
+
const parts = split_remote_key(key);
|
|
172
|
+
const entry = live_query_map.get(parts.id)?.get(parts.payload);
|
|
113
173
|
|
|
114
|
-
|
|
174
|
+
refresh(key, entry, data.l[key]);
|
|
175
|
+
|
|
176
|
+
// `fail()` is terminal, so only reconnect on the success path —
|
|
177
|
+
// reconnecting after a hard failure would wipe the error state and
|
|
178
|
+
// restart the stream (see commit 63a3e83 regression).
|
|
179
|
+
if (!data.l[key].e) {
|
|
180
|
+
void entry?.resource.reconnect();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return data;
|
|
115
186
|
}
|
|
116
187
|
|
|
117
188
|
/**
|
|
@@ -120,12 +191,13 @@ export async function remote_request(url, headers) {
|
|
|
120
191
|
*/
|
|
121
192
|
export async function handle_side_channel_response(response) {
|
|
122
193
|
if (response.type === 'redirect') {
|
|
123
|
-
|
|
194
|
+
// Use internal version to allow redirects to external URLs
|
|
195
|
+
await _goto(response.location);
|
|
124
196
|
throw new Redirect(307, response.location);
|
|
125
197
|
}
|
|
126
198
|
|
|
127
199
|
if (response.type === 'error') {
|
|
128
|
-
throw new
|
|
200
|
+
throw new HandledHttpError(response.error);
|
|
129
201
|
}
|
|
130
202
|
|
|
131
203
|
return response;
|
|
@@ -206,50 +278,3 @@ export function categorize_updates(updates) {
|
|
|
206
278
|
|
|
207
279
|
return { overrides, refreshes };
|
|
208
280
|
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* @template TResource
|
|
212
|
-
* @param {string} stringified_singleflight
|
|
213
|
-
* @param {Map<string, Map<string, { resource: TResource }>>} map
|
|
214
|
-
* @param {(resource: TResource, value: RemoteSingleflightEntry) => void} callback
|
|
215
|
-
*/
|
|
216
|
-
function apply_singleflight(stringified_singleflight, map, callback) {
|
|
217
|
-
const singleflight = /** @type {RemoteSingleflightMap} */ (
|
|
218
|
-
devalue.parse(stringified_singleflight, app.decoders)
|
|
219
|
-
);
|
|
220
|
-
|
|
221
|
-
for (const [key, value] of Object.entries(singleflight)) {
|
|
222
|
-
const parts = split_remote_key(key);
|
|
223
|
-
const entry = map.get(parts.id)?.get(parts.payload);
|
|
224
|
-
if (entry?.resource) {
|
|
225
|
-
callback(entry.resource, value);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Apply refresh data from the server to the relevant queries
|
|
232
|
-
*
|
|
233
|
-
* @param {string} stringified_refreshes
|
|
234
|
-
*/
|
|
235
|
-
export const apply_refreshes = (stringified_refreshes) => {
|
|
236
|
-
apply_singleflight(stringified_refreshes, query_map, (resource, value) => {
|
|
237
|
-
if (value.type === 'result') {
|
|
238
|
-
resource?.set(value.data);
|
|
239
|
-
} else {
|
|
240
|
-
resource?.fail(new HttpError(value.status ?? 500, value.error));
|
|
241
|
-
}
|
|
242
|
-
});
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
/** @param {string} stringified_reconnects */
|
|
246
|
-
export const apply_reconnections = (stringified_reconnects) => {
|
|
247
|
-
apply_singleflight(stringified_reconnects, live_query_map, (resource, value) => {
|
|
248
|
-
if (value.type === 'result') {
|
|
249
|
-
resource?.set(value.data);
|
|
250
|
-
void resource?.reconnect();
|
|
251
|
-
} else {
|
|
252
|
-
resource?.fail(new HttpError(value.status ?? 500, value.error));
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { BROWSER, DEV } from 'esm-env';
|
|
2
|
+
import { onMount } from 'svelte';
|
|
3
|
+
import * as storage from './session-storage.js';
|
|
4
|
+
import { NAVIGATION_SNAPSHOT_KEY } from './constants.js';
|
|
5
|
+
import { hash } from '../../utils/hash.js';
|
|
6
|
+
import { parse, stringify } from '#app/internal/transport';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {{ id: string; capture: () => any; restore: (value: any) => void; reset?: () => void }} SnapshotRegistration
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// Function snapshots use history indexes so shallow entries have distinct state.
|
|
13
|
+
/** @type {Record<string, Record<string, string>>} */
|
|
14
|
+
const navigation_snapshots = storage.get(NAVIGATION_SNAPSHOT_KEY) ?? {};
|
|
15
|
+
|
|
16
|
+
/** @type {Set<SnapshotRegistration>} */
|
|
17
|
+
const snapshot_registrations = new Set();
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The registrations mounted right now. Take before navigating and pass to
|
|
21
|
+
* `restore_navigation_snapshot` — only these are eligible for `reset`.
|
|
22
|
+
* @returns {Set<SnapshotRegistration>}
|
|
23
|
+
*/
|
|
24
|
+
export function current_registrations() {
|
|
25
|
+
return new Set(snapshot_registrations);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// injected by client.js: the live history index while the router is idle, null mid-navigation
|
|
29
|
+
/** @type {() => number | null} */
|
|
30
|
+
let get_idle_history_index = () => null;
|
|
31
|
+
|
|
32
|
+
/** @param {() => number | null} fn */
|
|
33
|
+
export function init_snapshots(fn) {
|
|
34
|
+
get_idle_history_index = fn;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** @param {number} index */
|
|
38
|
+
export function capture_navigation_snapshot(index) {
|
|
39
|
+
if (snapshot_registrations.size === 0) {
|
|
40
|
+
delete navigation_snapshots[index];
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
navigation_snapshots[index] = Object.fromEntries(
|
|
45
|
+
Array.from(snapshot_registrations, ({ id, capture }) => [id, stringify(capture())])
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {number} index
|
|
51
|
+
* @param {Set<SnapshotRegistration>} [reset_registrations]
|
|
52
|
+
*/
|
|
53
|
+
export function restore_navigation_snapshot(index, reset_registrations) {
|
|
54
|
+
const values = navigation_snapshots[index];
|
|
55
|
+
|
|
56
|
+
for (const registration of snapshot_registrations) {
|
|
57
|
+
if (values && Object.hasOwn(values, registration.id)) {
|
|
58
|
+
registration.restore(parse(values[registration.id]));
|
|
59
|
+
} else if (reset_registrations?.has(registration)) {
|
|
60
|
+
registration.reset?.();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** @param {number} index */
|
|
66
|
+
export function delete_navigation_snapshot(index) {
|
|
67
|
+
delete navigation_snapshots[index];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** @param {number} index */
|
|
71
|
+
export function persist_navigation_snapshots(index) {
|
|
72
|
+
capture_navigation_snapshot(index);
|
|
73
|
+
storage.set(NAVIGATION_SNAPSHOT_KEY, navigation_snapshots);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @param {string | undefined} stack
|
|
78
|
+
* @returns {string}
|
|
79
|
+
*/
|
|
80
|
+
function callsite_id(stack) {
|
|
81
|
+
let frames = stack?.split('\n') ?? [];
|
|
82
|
+
if (frames[0]?.trim() === 'Error') frames = frames.slice(1);
|
|
83
|
+
|
|
84
|
+
// only the callsite frame is stable: frames above it differ between hydration and
|
|
85
|
+
// re-mounting, and Vite query strings (stripped here) change on module invalidation
|
|
86
|
+
const frame = frames[1]?.replace(/\?[^)\s]*(?=:\d+:\d+\)?$)/, '');
|
|
87
|
+
|
|
88
|
+
if (!frame) {
|
|
89
|
+
throw new Error('Could not generate a snapshot id from the stack trace. Pass an `id` instead.');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return hash(frame);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* A lifecycle function that captures state before navigating and restores it when traversing history.
|
|
97
|
+
*
|
|
98
|
+
* By default, the snapshot `id` is generated from the call site. Pass an explicit `id` to keep snapshots stable across deployments or distinguish multiple uses of a shared helper.
|
|
99
|
+
*
|
|
100
|
+
* The optional `reset` callback runs on navigations where there is no captured value to restore, such as when a new history entry is created. Captured values are serialized with the app's transport hook.
|
|
101
|
+
*
|
|
102
|
+
* `snapshot` must be called during a component initialization. It remains active as long as the component is mounted.
|
|
103
|
+
* @template T
|
|
104
|
+
* @param {{ id?: string; capture: () => T; restore: (value: T) => void; reset?: () => void }} options
|
|
105
|
+
* @returns {void}
|
|
106
|
+
*/
|
|
107
|
+
export function snapshot(options) {
|
|
108
|
+
if (!BROWSER) return;
|
|
109
|
+
|
|
110
|
+
let id = options.id;
|
|
111
|
+
if (id === undefined) {
|
|
112
|
+
// restore any lowered third-party limit, else every callsite collapses to one id
|
|
113
|
+
const limit = Error.stackTraceLimit;
|
|
114
|
+
const lowered = typeof limit === 'number' && limit < 3;
|
|
115
|
+
if (lowered) Error.stackTraceLimit = 3;
|
|
116
|
+
const stack = new Error().stack;
|
|
117
|
+
if (lowered) Error.stackTraceLimit = limit;
|
|
118
|
+
|
|
119
|
+
id = callsite_id(stack);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const registration = { ...options, id };
|
|
123
|
+
|
|
124
|
+
onMount(() => {
|
|
125
|
+
if (DEV && Array.from(snapshot_registrations).some((existing) => existing.id === id)) {
|
|
126
|
+
throw new Error(
|
|
127
|
+
options.id === undefined
|
|
128
|
+
? 'snapshot() was called multiple times from the same call site. Pass a unique `id` to distinguish the instances.'
|
|
129
|
+
: `A snapshot with id "${options.id}" is already registered. Pass a unique \`id\`.`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const index = get_idle_history_index();
|
|
134
|
+
if (index !== null) {
|
|
135
|
+
const values = navigation_snapshots[index];
|
|
136
|
+
if (values && Object.hasOwn(values, id)) {
|
|
137
|
+
registration.restore(parse(values[id]));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
snapshot_registrations.add(registration);
|
|
142
|
+
|
|
143
|
+
return () => {
|
|
144
|
+
snapshot_registrations.delete(registration);
|
|
145
|
+
};
|
|
146
|
+
});
|
|
147
|
+
}
|