@sveltejs/kit 3.0.0-next.11 → 3.0.0-next.12
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 +1 -1
- package/src/cli.js +8 -8
- package/src/constants.js +1 -1
- package/src/core/config/options.js +43 -35
- package/src/core/env.js +25 -11
- package/src/core/sync/sync.js +11 -14
- package/src/core/sync/utils.js +21 -1
- package/src/core/sync/{write_non_ambient.js → write_app_types.js} +23 -21
- package/src/core/sync/write_client_manifest.js +4 -12
- package/src/core/sync/write_env.js +6 -4
- package/src/core/sync/write_server.js +10 -15
- package/src/core/sync/write_tsconfig/index.js +245 -0
- package/src/core/sync/write_tsconfig/utils.js +162 -0
- package/src/core/sync/write_types/index.js +80 -88
- package/src/exports/internal/server/index.js +3 -1
- package/src/exports/public.d.ts +28 -51
- package/src/exports/vite/dev/index.js +88 -76
- package/src/exports/vite/index.js +364 -144
- package/src/exports/vite/module_ids.js +3 -1
- package/src/exports/vite/utils.js +1 -10
- package/src/runner.js +13 -0
- package/src/runtime/app/forms.js +4 -0
- package/src/runtime/app/manifest/index.js +1 -0
- package/src/runtime/app/paths/client.js +30 -30
- package/src/runtime/app/paths/internal/client.js +26 -0
- package/src/runtime/app/paths/server.js +22 -9
- package/src/runtime/app/paths/types.d.ts +11 -19
- package/src/runtime/app/server/remote/command.js +12 -7
- package/src/runtime/app/server/remote/form.js +29 -26
- package/src/runtime/app/server/remote/prerender.js +9 -2
- package/src/runtime/app/server/remote/query.js +5 -4
- package/src/runtime/app/server/remote/shared.js +48 -30
- package/src/runtime/app/service-worker/index.js +24 -0
- package/src/runtime/app/state/index.js +1 -1
- package/src/runtime/client/client.js +46 -9
- package/src/runtime/client/remote-functions/form.svelte.js +43 -51
- package/src/runtime/client/remote-functions/query/index.js +2 -2
- package/src/runtime/client/remote-functions/query-batch.svelte.js +2 -3
- package/src/runtime/client/remote-functions/query-live/iterator.js +5 -2
- package/src/runtime/client/remote-functions/shared.svelte.js +4 -1
- package/src/runtime/client/state.svelte.js +54 -21
- package/src/runtime/form-utils.js +89 -54
- package/src/runtime/server/cookie.js +1 -1
- package/src/runtime/server/data/index.js +31 -28
- package/src/runtime/server/errors.js +1 -1
- package/src/runtime/server/page/actions.js +3 -3
- package/src/runtime/server/page/render.js +19 -28
- package/src/runtime/server/remote-functions.js +66 -35
- package/src/runtime/server/respond.js +56 -14
- package/src/runtime/server/utils.js +10 -0
- package/src/types/ambient-private.d.ts +8 -0
- package/src/types/ambient.d.ts +22 -27
- package/src/types/global-private.d.ts +12 -0
- package/src/types/internal.d.ts +13 -3
- package/src/utils/url.js +12 -0
- package/src/version.js +1 -1
- package/types/index.d.ts +80 -98
- package/types/index.d.ts.map +4 -1
- package/src/core/sync/write_ambient.js +0 -18
- package/src/core/sync/write_tsconfig.js +0 -258
- /package/src/core/sync/{write_tsconfig_test → write_tsconfig/test-app}/package.json +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** @import { RouteId } from '$app/types' */
|
|
1
2
|
/** @import { RemoteFunctionDataNode, ServerNodesResponse, ServerRedirectNode } from 'types' */
|
|
2
3
|
/** @import { NavigationIntent } from './types.js' */
|
|
3
4
|
/** @import { RenderNode } from '../types.js' */
|
|
@@ -21,7 +22,7 @@ import {
|
|
|
21
22
|
scroll_state,
|
|
22
23
|
load_css
|
|
23
24
|
} from './utils.js';
|
|
24
|
-
import { base } from '$app/paths/internal/client';
|
|
25
|
+
import { base, set_match_implementation } from '$app/paths/internal/client';
|
|
25
26
|
import * as devalue from 'devalue';
|
|
26
27
|
import {
|
|
27
28
|
HISTORY_INDEX,
|
|
@@ -42,7 +43,7 @@ import {
|
|
|
42
43
|
validate_load_response
|
|
43
44
|
} from '../shared.js';
|
|
44
45
|
import { get_message, get_status } from '../../utils/error.js';
|
|
45
|
-
import { page, update, navigating, updated } from './state.svelte.js';
|
|
46
|
+
import { page, update, navigating, updated, notify_version } from './state.svelte.js';
|
|
46
47
|
import { payload } from './payload.js';
|
|
47
48
|
import { add_data_suffix, add_resolution_suffix } from '../pathname.js';
|
|
48
49
|
import { noop_span } from '../telemetry/noop.js';
|
|
@@ -332,6 +333,23 @@ export const query_map = new Map();
|
|
|
332
333
|
*/
|
|
333
334
|
export const live_query_map = new Map();
|
|
334
335
|
|
|
336
|
+
set_match_implementation(async (url) => {
|
|
337
|
+
if (typeof url === 'string') {
|
|
338
|
+
url = new URL(url, location.href);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const intent = await get_navigation_intent(url, false);
|
|
342
|
+
|
|
343
|
+
if (intent) {
|
|
344
|
+
return {
|
|
345
|
+
id: /** @type {RouteId} */ (intent.route.id),
|
|
346
|
+
params: intent.params
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return null;
|
|
351
|
+
});
|
|
352
|
+
|
|
335
353
|
/**
|
|
336
354
|
* @param {import('./types.js').SvelteKitApp} _app
|
|
337
355
|
* @param {HTMLElement} _target
|
|
@@ -1197,7 +1215,8 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
|
|
|
1197
1215
|
/** @type {import('types').ServerNodesResponse | import('types').ServerRedirectNode | null} */
|
|
1198
1216
|
let server_data = null;
|
|
1199
1217
|
const url_changed = current.url ? id !== get_page_key(current.url) : false;
|
|
1200
|
-
|
|
1218
|
+
// current.route is null after an error-page render, so a missing route counts as changed
|
|
1219
|
+
const route_changed = !current.route || route.id !== current.route.id;
|
|
1201
1220
|
const search_params_changed = diff_search_params(current.url, url);
|
|
1202
1221
|
|
|
1203
1222
|
let parent_invalid = false;
|
|
@@ -1986,14 +2005,14 @@ async function navigate({
|
|
|
1986
2005
|
}
|
|
1987
2006
|
|
|
1988
2007
|
// we reset scroll before dealing with focus, to avoid a flash of unscrolled content
|
|
1989
|
-
/** @type {Element | null
|
|
2008
|
+
/** @type {Element | null} */
|
|
1990
2009
|
let deep_linked = null;
|
|
1991
2010
|
|
|
1992
2011
|
if (autoscroll) {
|
|
1993
2012
|
const scroll = popped ? popped.scroll : noscroll ? scroll_state() : null;
|
|
1994
2013
|
if (scroll) {
|
|
1995
2014
|
scrollTo(scroll.x, scroll.y);
|
|
1996
|
-
} else if ((deep_linked =
|
|
2015
|
+
} else if ((deep_linked = get_hash_element(url))) {
|
|
1997
2016
|
// Here we use `scrollIntoView` on the element instead of `scrollTo`
|
|
1998
2017
|
// because it natively supports the `scroll-margin` and `scroll-behavior`
|
|
1999
2018
|
// CSS properties.
|
|
@@ -2744,13 +2763,19 @@ function _start_router() {
|
|
|
2744
2763
|
history.scrollRestoration = 'auto';
|
|
2745
2764
|
}
|
|
2746
2765
|
});
|
|
2747
|
-
|
|
2748
2766
|
addEventListener('visibilitychange', () => {
|
|
2749
2767
|
if (document.visibilityState === 'hidden') {
|
|
2750
2768
|
persist_state();
|
|
2769
|
+
} else {
|
|
2770
|
+
// the tab just became visible — a good time to check for a new deployment
|
|
2771
|
+
void updated.check();
|
|
2751
2772
|
}
|
|
2752
2773
|
});
|
|
2753
2774
|
|
|
2775
|
+
addEventListener('focus', () => {
|
|
2776
|
+
void updated.check();
|
|
2777
|
+
});
|
|
2778
|
+
|
|
2754
2779
|
// @ts-expect-error this isn't supported everywhere yet
|
|
2755
2780
|
if (!navigator.connection?.saveData) {
|
|
2756
2781
|
setup_preload();
|
|
@@ -3177,6 +3202,9 @@ async function load_data(url, invalid) {
|
|
|
3177
3202
|
const fetcher = DEV ? dev_fetch : window.fetch;
|
|
3178
3203
|
const res = await fetcher(data_url.href, {});
|
|
3179
3204
|
|
|
3205
|
+
// detect new deployments from the response header
|
|
3206
|
+
notify_version(res.headers.get('x-sveltekit-version'));
|
|
3207
|
+
|
|
3180
3208
|
if (!res.ok) {
|
|
3181
3209
|
// error message is a JSON-stringified string which devalue can't handle at the top level
|
|
3182
3210
|
// turn it into a HttpError to not call handleError on the client again (was already handled on the server)
|
|
@@ -3294,8 +3322,8 @@ function reset_focus(url, scroll = true) {
|
|
|
3294
3322
|
|
|
3295
3323
|
// Mimic the browsers' behaviour and set the sequential focus navigation
|
|
3296
3324
|
// starting point to the fragment identifier.
|
|
3297
|
-
const
|
|
3298
|
-
if (
|
|
3325
|
+
const element = get_hash_element(url);
|
|
3326
|
+
if (element) {
|
|
3299
3327
|
const { x, y } = scroll_state();
|
|
3300
3328
|
|
|
3301
3329
|
// `element.focus()` doesn't work on Safari and Firefox Ubuntu so we need
|
|
@@ -3304,7 +3332,7 @@ function reset_focus(url, scroll = true) {
|
|
|
3304
3332
|
const history_state = history.state;
|
|
3305
3333
|
|
|
3306
3334
|
resetting_focus = true;
|
|
3307
|
-
location.replace(new URL(`#${id}`, location.href));
|
|
3335
|
+
location.replace(new URL(`#${element.id}`, location.href));
|
|
3308
3336
|
|
|
3309
3337
|
// Firefox has a bug that sets the history state to `null` so we need to
|
|
3310
3338
|
// restore it after. See https://bugzilla.mozilla.org/show_bug.cgi?id=1199924
|
|
@@ -3456,6 +3484,15 @@ function get_id(url) {
|
|
|
3456
3484
|
return decodeURIComponent(id);
|
|
3457
3485
|
}
|
|
3458
3486
|
|
|
3487
|
+
/**
|
|
3488
|
+
* @param {URL} url
|
|
3489
|
+
* @returns {Element | null}
|
|
3490
|
+
*/
|
|
3491
|
+
function get_hash_element(url) {
|
|
3492
|
+
const id = get_id(url);
|
|
3493
|
+
return id ? document.getElementById(id) : null;
|
|
3494
|
+
}
|
|
3495
|
+
|
|
3459
3496
|
if (DEV) {
|
|
3460
3497
|
// Nasty hack to silence harmless warnings the user can do nothing about
|
|
3461
3498
|
const console_warn = console.warn;
|
|
@@ -25,7 +25,9 @@ import {
|
|
|
25
25
|
serialize_binary_form,
|
|
26
26
|
deep_get,
|
|
27
27
|
DELETE_KEY,
|
|
28
|
-
BINARY_FORM_CONTENT_TYPE
|
|
28
|
+
BINARY_FORM_CONTENT_TYPE,
|
|
29
|
+
parse_form_key,
|
|
30
|
+
coerce_form_value
|
|
29
31
|
} from '../../form-utils.js';
|
|
30
32
|
|
|
31
33
|
/**
|
|
@@ -123,8 +125,8 @@ export function form(id) {
|
|
|
123
125
|
/** @type {InternalRemoteFormIssue[] | null} */
|
|
124
126
|
let unread_issues = null;
|
|
125
127
|
|
|
126
|
-
/** @type {string | null} */
|
|
127
|
-
let
|
|
128
|
+
/** @type {{ name: string; type: 'number' | 'boolean' | null; is_array: boolean } | null} */
|
|
129
|
+
let previous_submitter = null;
|
|
128
130
|
|
|
129
131
|
/**
|
|
130
132
|
* In dev, warn if there are validation issues going unread
|
|
@@ -160,8 +162,8 @@ export function form(id) {
|
|
|
160
162
|
* @returns {Record<string, any>}
|
|
161
163
|
*/
|
|
162
164
|
function convert(form_data) {
|
|
163
|
-
const data = convert_formdata(form_data);
|
|
164
|
-
if (key !== undefined && !
|
|
165
|
+
const data = convert_formdata(action_id_without_key, form_data);
|
|
166
|
+
if (key !== undefined && !('id' in data)) {
|
|
165
167
|
data.id = key;
|
|
166
168
|
}
|
|
167
169
|
return data;
|
|
@@ -405,26 +407,30 @@ export function form(id) {
|
|
|
405
407
|
const form_data = new FormData(form, event.submitter);
|
|
406
408
|
|
|
407
409
|
if (
|
|
408
|
-
|
|
409
|
-
!Array.from(form_data.keys())
|
|
410
|
+
previous_submitter !== null &&
|
|
411
|
+
!Array.from(form_data.keys())
|
|
412
|
+
.map((k) => parse_form_key(action_id_without_key, k).name)
|
|
413
|
+
.includes(previous_submitter.name)
|
|
410
414
|
) {
|
|
411
|
-
|
|
412
|
-
// `set_nested_value` would coerce `undefined` to `NaN`/`false`
|
|
413
|
-
// instead of clearing the previously-submitted value.
|
|
414
|
-
set_nested_value(input, previous_submitter_name, undefined);
|
|
415
|
+
set_nested_value(input, previous_submitter, undefined);
|
|
415
416
|
}
|
|
416
417
|
|
|
417
418
|
if (event.submitter) {
|
|
418
419
|
const name = event.submitter.getAttribute('name');
|
|
420
|
+
|
|
421
|
+
/** @type {null | ReturnType<typeof parse_form_key>} */
|
|
422
|
+
let submitter = null;
|
|
423
|
+
|
|
419
424
|
const value = /** @type {any} */ (event.submitter).value;
|
|
420
425
|
|
|
421
426
|
if (name !== null && value !== undefined) {
|
|
422
|
-
|
|
427
|
+
submitter = parse_form_key(action_id_without_key, name);
|
|
428
|
+
set_nested_value(input, submitter, coerce_form_value(submitter.type, value));
|
|
423
429
|
}
|
|
424
430
|
|
|
425
|
-
|
|
431
|
+
previous_submitter = submitter;
|
|
426
432
|
} else {
|
|
427
|
-
|
|
433
|
+
previous_submitter = null;
|
|
428
434
|
}
|
|
429
435
|
|
|
430
436
|
if (DEV) {
|
|
@@ -463,15 +469,14 @@ export function form(id) {
|
|
|
463
469
|
// but that makes the types unnecessarily awkward
|
|
464
470
|
const element = /** @type {HTMLInputElement} */ (e.target);
|
|
465
471
|
|
|
466
|
-
|
|
472
|
+
const name = element.name;
|
|
467
473
|
if (!name) return;
|
|
468
474
|
|
|
469
|
-
const
|
|
470
|
-
if (is_array) name = name.slice(0, -2);
|
|
475
|
+
const field = parse_form_key(action_id_without_key, name);
|
|
471
476
|
|
|
472
477
|
const is_file = element.type === 'file';
|
|
473
478
|
|
|
474
|
-
if (is_array) {
|
|
479
|
+
if (field.is_array) {
|
|
475
480
|
let value;
|
|
476
481
|
|
|
477
482
|
if (element.tagName === 'SELECT') {
|
|
@@ -481,7 +486,7 @@ export function form(id) {
|
|
|
481
486
|
);
|
|
482
487
|
} else {
|
|
483
488
|
const elements = /** @type {HTMLInputElement[]} */ (
|
|
484
|
-
Array.from(form.querySelectorAll(`[name="${name}
|
|
489
|
+
Array.from(form.querySelectorAll(`[name="${name}"]`))
|
|
485
490
|
);
|
|
486
491
|
|
|
487
492
|
if (DEV) {
|
|
@@ -502,7 +507,7 @@ export function form(id) {
|
|
|
502
507
|
}
|
|
503
508
|
}
|
|
504
509
|
|
|
505
|
-
set_nested_value(input,
|
|
510
|
+
set_nested_value(input, field, value);
|
|
506
511
|
} else if (is_file) {
|
|
507
512
|
if (DEV && element.multiple) {
|
|
508
513
|
throw new Error(
|
|
@@ -513,21 +518,19 @@ export function form(id) {
|
|
|
513
518
|
const file = /** @type {HTMLInputElement & { files: FileList }} */ (element).files[0];
|
|
514
519
|
|
|
515
520
|
if (file) {
|
|
516
|
-
set_nested_value(input,
|
|
521
|
+
set_nested_value(input, field, file);
|
|
517
522
|
} else {
|
|
518
|
-
set_nested_value(input,
|
|
523
|
+
set_nested_value(input, field, DELETE_KEY);
|
|
519
524
|
}
|
|
520
525
|
} else {
|
|
521
526
|
set_nested_value(
|
|
522
527
|
input,
|
|
523
|
-
|
|
528
|
+
field,
|
|
524
529
|
element.type === 'checkbox' && !element.checked ? null : element.value
|
|
525
530
|
);
|
|
526
531
|
}
|
|
527
532
|
|
|
528
|
-
name =
|
|
529
|
-
|
|
530
|
-
dirty[name] = true;
|
|
533
|
+
dirty[field.name] = true;
|
|
531
534
|
};
|
|
532
535
|
|
|
533
536
|
const handle_reset = async () => {
|
|
@@ -535,7 +538,7 @@ export function form(id) {
|
|
|
535
538
|
// the inputs are actually updated (so that it can be cancelled)
|
|
536
539
|
await tick();
|
|
537
540
|
|
|
538
|
-
input = convert_formdata(new FormData(form));
|
|
541
|
+
input = convert_formdata(action_id_without_key, new FormData(form));
|
|
539
542
|
raw_issues = [];
|
|
540
543
|
touched = {};
|
|
541
544
|
dirty = {};
|
|
@@ -545,15 +548,15 @@ export function form(id) {
|
|
|
545
548
|
|
|
546
549
|
/** @param {Event} e */
|
|
547
550
|
const handle_focusout = (e) => {
|
|
548
|
-
|
|
551
|
+
const name = /** @type {HTMLInputElement} */ (e.target).name;
|
|
549
552
|
if (!name) return;
|
|
550
553
|
|
|
551
|
-
|
|
554
|
+
const field = parse_form_key(action_id_without_key, name);
|
|
552
555
|
|
|
553
|
-
touched[name] = true;
|
|
556
|
+
touched[field.name] = true;
|
|
554
557
|
|
|
555
|
-
if (Object.hasOwn(dirty, name)) {
|
|
556
|
-
can_validate[name] = true;
|
|
558
|
+
if (Object.hasOwn(dirty, field.name)) {
|
|
559
|
+
can_validate[field.name] = true;
|
|
557
560
|
}
|
|
558
561
|
};
|
|
559
562
|
|
|
@@ -608,10 +611,10 @@ export function form(id) {
|
|
|
608
611
|
},
|
|
609
612
|
fields: {
|
|
610
613
|
get: () =>
|
|
611
|
-
create_field_proxy(
|
|
612
|
-
|
|
613
|
-
() => input,
|
|
614
|
-
(path, value) => {
|
|
614
|
+
create_field_proxy({
|
|
615
|
+
form_id: action_id_without_key,
|
|
616
|
+
get: () => input,
|
|
617
|
+
set: (path, value) => {
|
|
615
618
|
if (path.length === 0) {
|
|
616
619
|
input = value;
|
|
617
620
|
} else if (value !== deep_get(input, path)) {
|
|
@@ -626,7 +629,7 @@ export function form(id) {
|
|
|
626
629
|
}
|
|
627
630
|
}
|
|
628
631
|
},
|
|
629
|
-
(path, all) => {
|
|
632
|
+
get_issues: (path, all) => {
|
|
630
633
|
if (DEV && unread_issues !== null && path !== undefined) {
|
|
631
634
|
unread_issues = unread_issues.filter((issue) => {
|
|
632
635
|
return (
|
|
@@ -638,10 +641,9 @@ export function form(id) {
|
|
|
638
641
|
|
|
639
642
|
return issues;
|
|
640
643
|
},
|
|
641
|
-
() => touched,
|
|
642
|
-
() => dirty
|
|
643
|
-
|
|
644
|
-
)
|
|
644
|
+
get_touched: () => touched,
|
|
645
|
+
get_dirty: () => dirty
|
|
646
|
+
})
|
|
645
647
|
},
|
|
646
648
|
result: {
|
|
647
649
|
get: () => result
|
|
@@ -809,13 +811,3 @@ function validate_form_data(form_data, enctype) {
|
|
|
809
811
|
}
|
|
810
812
|
}
|
|
811
813
|
}
|
|
812
|
-
|
|
813
|
-
/**
|
|
814
|
-
* Remove the `n:` or `b:` prefix from a field name
|
|
815
|
-
* @template {string | null} T
|
|
816
|
-
* @param {T} name
|
|
817
|
-
* @returns {T}
|
|
818
|
-
*/
|
|
819
|
-
function strip_prefix(name) {
|
|
820
|
-
return /** @type {T} */ (name && name.replace(/^[nb]:/, ''));
|
|
821
|
-
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** @import { RemoteQueryFunction } from '@sveltejs/kit' */
|
|
2
2
|
import { app_dir, base } from '$app/paths/internal/client';
|
|
3
3
|
import { _goto, query_map } from '../../client.js';
|
|
4
|
-
import {
|
|
4
|
+
import { QUERY_FUNCTION_ID, remote_request } from '../shared.svelte.js';
|
|
5
5
|
import { DEV } from 'esm-env';
|
|
6
6
|
import { QueryProxy } from './proxy.js';
|
|
7
7
|
|
|
@@ -26,7 +26,7 @@ export function query(id) {
|
|
|
26
26
|
return new QueryProxy(id, arg, async (payload) => {
|
|
27
27
|
const url = `${base}/${app_dir}/remote/${id}${payload ? `?payload=${payload}` : ''}`;
|
|
28
28
|
|
|
29
|
-
const result = await remote_request(url
|
|
29
|
+
const result = await remote_request(url);
|
|
30
30
|
|
|
31
31
|
if (result.redirect) {
|
|
32
32
|
// Use internal version to allow redirects to external URLs
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** @import { RemoteQueryFunction } from '@sveltejs/kit' */
|
|
2
2
|
import { app_dir, base } from '$app/paths/internal/client';
|
|
3
3
|
import { _goto } from '../client.js';
|
|
4
|
-
import {
|
|
4
|
+
import { QUERY_FUNCTION_ID, remote_request } from './shared.svelte.js';
|
|
5
5
|
import { QueryProxy } from './query/proxy.js';
|
|
6
6
|
import { HttpError } from '@sveltejs/kit/internal';
|
|
7
7
|
|
|
@@ -30,8 +30,7 @@ export function query_batch(id) {
|
|
|
30
30
|
// but in different forks/async contexts and in the same macrotask?
|
|
31
31
|
// If so this would potentially be buggy
|
|
32
32
|
const headers = {
|
|
33
|
-
'Content-Type': 'application/json'
|
|
34
|
-
...get_remote_request_headers()
|
|
33
|
+
'Content-Type': 'application/json'
|
|
35
34
|
};
|
|
36
35
|
|
|
37
36
|
// Wait for the next macrotask - don't use microtask as Svelte runtime uses these to collect changes and flush them,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { app_dir, base } from '$app/paths/internal/client';
|
|
2
2
|
import { app } from '../../client.js';
|
|
3
|
-
import {
|
|
3
|
+
import { notify_version } from '../../state.svelte.js';
|
|
4
|
+
import { handle_side_channel_response } from '../shared.svelte.js';
|
|
4
5
|
import * as devalue from 'devalue';
|
|
5
6
|
import { HttpError } from '@sveltejs/kit/internal';
|
|
6
7
|
import { noop } from '../../../../utils/functions.js';
|
|
@@ -23,10 +24,12 @@ export async function* create_live_iterator(
|
|
|
23
24
|
const url = `${base}/${app_dir}/remote/${id}${payload ? `?payload=${payload}` : ''}`;
|
|
24
25
|
|
|
25
26
|
const response = await fetch(url, {
|
|
26
|
-
headers: get_remote_request_headers(),
|
|
27
27
|
signal: controller.signal
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
+
// detect new deployments from the response header
|
|
31
|
+
notify_version(response.headers.get('x-sveltekit-version'));
|
|
32
|
+
|
|
30
33
|
if (!response.ok) {
|
|
31
34
|
const result = await response.json().catch(() => ({
|
|
32
35
|
type: 'error',
|
|
@@ -6,7 +6,7 @@ import { app, _goto, live_query_map, query_map, query_responses } from '../clien
|
|
|
6
6
|
import { HttpError, Redirect } from '@sveltejs/kit/internal';
|
|
7
7
|
import { untrack } from 'svelte';
|
|
8
8
|
import { create_remote_key, split_remote_key } from '../../shared.js';
|
|
9
|
-
import { navigating, page } from '../state.svelte.js';
|
|
9
|
+
import { navigating, page, notify_version } from '../state.svelte.js';
|
|
10
10
|
|
|
11
11
|
/** Indicates a query function, as opposed to a query instance */
|
|
12
12
|
export const QUERY_FUNCTION_ID = Symbol('sveltekit.query_function_id');
|
|
@@ -113,6 +113,9 @@ export function get_remote_request_headers() {
|
|
|
113
113
|
export async function remote_request(url, init) {
|
|
114
114
|
const response = await fetch(url, init);
|
|
115
115
|
|
|
116
|
+
// detect new deployments from the response header
|
|
117
|
+
notify_version(response.headers.get('x-sveltekit-version'));
|
|
118
|
+
|
|
116
119
|
if (!response.ok) {
|
|
117
120
|
const result = await response.json().catch(() => ({
|
|
118
121
|
type: 'error',
|
|
@@ -25,41 +25,74 @@ export const updated = new (class Updated {
|
|
|
25
25
|
check = async () => false;
|
|
26
26
|
})();
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Internal: mark `updated.current` as `true` if the given version differs.
|
|
30
|
+
* Called from the server response header path. No-op unless version checks
|
|
31
|
+
* are enabled (assigned below). Not exported on the public `updated` object.
|
|
32
|
+
* @type {(new_version: string | null) => void}
|
|
33
|
+
*/
|
|
34
|
+
export let notify_version = () => {};
|
|
35
|
+
|
|
28
36
|
if (!DEV && BROWSER) {
|
|
29
37
|
const interval = __SVELTEKIT_APP_VERSION_POLL_INTERVAL__;
|
|
30
38
|
|
|
31
|
-
/** @type {number} */
|
|
39
|
+
/** @type {number | undefined} */
|
|
32
40
|
let timeout;
|
|
33
41
|
|
|
42
|
+
/** @type {Promise<boolean> | undefined} */
|
|
43
|
+
let checking;
|
|
44
|
+
|
|
45
|
+
if (__SVELTEKIT_APP_VERSION_CHECKS_ENABLED__) {
|
|
46
|
+
/**
|
|
47
|
+
* Mark `updated.current` as `true` if the given version differs from the one
|
|
48
|
+
* the app was hydrated with. Called from the server response header path.
|
|
49
|
+
* Does NOT reset the poll timer — unlike `check()`, this is a passive observation
|
|
50
|
+
* from a single server instance's response, not an explicit version check. The
|
|
51
|
+
* poll timer continues on its original schedule as a backstop. This is important
|
|
52
|
+
* for platforms that implement skew protection, where `x-sveltekit-version`
|
|
53
|
+
* may be out of date — in this case we still need to poll for `version.json`.
|
|
54
|
+
* @param {string | null} new_version
|
|
55
|
+
*/
|
|
56
|
+
notify_version = (new_version) => {
|
|
57
|
+
if (new_version && new_version !== version) {
|
|
58
|
+
updated.current = true;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
34
63
|
/** @type {() => Promise<boolean>} */
|
|
35
|
-
|
|
64
|
+
function check() {
|
|
65
|
+
if (checking) return checking;
|
|
66
|
+
|
|
36
67
|
window.clearTimeout(timeout);
|
|
37
68
|
|
|
38
|
-
|
|
69
|
+
return (checking = (async () => {
|
|
70
|
+
try {
|
|
71
|
+
const res = await fetch(`${assets}/${__SVELTEKIT_APP_VERSION_FILE__}`, {
|
|
72
|
+
headers: {
|
|
73
|
+
'cache-control': 'no-cache'
|
|
74
|
+
}
|
|
75
|
+
});
|
|
39
76
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
headers: {
|
|
43
|
-
'cache-control': 'no-cache'
|
|
77
|
+
if (!res.ok) {
|
|
78
|
+
return false;
|
|
44
79
|
}
|
|
45
|
-
});
|
|
46
80
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
81
|
+
const data = await res.json();
|
|
82
|
+
const new_update = data.version !== version;
|
|
50
83
|
|
|
51
|
-
|
|
52
|
-
|
|
84
|
+
if (new_update) {
|
|
85
|
+
updated.current = true;
|
|
86
|
+
}
|
|
53
87
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
88
|
+
return new_update;
|
|
89
|
+
} catch {
|
|
90
|
+
return false;
|
|
91
|
+
} finally {
|
|
92
|
+
checking = undefined;
|
|
93
|
+
if (interval && !updated.current) timeout = window.setTimeout(check, interval);
|
|
57
94
|
}
|
|
58
|
-
|
|
59
|
-
return new_update;
|
|
60
|
-
} catch {
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
95
|
+
})());
|
|
63
96
|
}
|
|
64
97
|
|
|
65
98
|
if (interval) timeout = window.setTimeout(check, interval);
|