@sveltejs/kit 2.27.2 → 2.28.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/package.json +2 -2
- package/src/core/postbuild/prerender.js +5 -0
- package/src/exports/index.js +3 -4
- package/src/exports/public.d.ts +17 -6
- package/src/exports/vite/build/build_remote.js +4 -4
- package/src/runtime/app/server/index.js +3 -2
- package/src/runtime/app/server/remote/command.js +5 -0
- package/src/runtime/app/server/remote/form.js +10 -0
- package/src/runtime/client/client.js +2 -2
- package/src/runtime/client/fetcher.js +3 -2
- package/src/runtime/client/remote-functions/command.svelte.js +91 -0
- package/src/runtime/client/remote-functions/form.svelte.js +29 -2
- package/src/runtime/client/remote-functions/index.js +1 -1
- package/src/runtime/client/remote-functions/shared.svelte.js +11 -14
- package/src/runtime/server/cookie.js +2 -1
- package/src/runtime/server/data/index.js +3 -4
- package/src/runtime/server/page/crypto.js +3 -58
- package/src/runtime/server/page/csp.js +2 -2
- package/src/runtime/server/page/load_data.js +6 -5
- package/src/runtime/server/page/render.js +3 -4
- package/src/runtime/server/remote.js +29 -33
- package/src/runtime/shared.js +8 -12
- package/src/runtime/utils.js +43 -33
- package/src/types/internal.d.ts +1 -1
- package/src/version.js +1 -1
- package/types/index.d.ts +17 -6
- package/types/index.d.ts.map +1 -1
- package/src/runtime/client/remote-functions/command.js +0 -71
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sveltejs/kit",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.28.0",
|
|
4
4
|
"description": "SvelteKit is the fastest way to build Svelte apps",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"framework",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
],
|
|
12
12
|
"repository": {
|
|
13
13
|
"type": "git",
|
|
14
|
-
"url": "https://github.com/sveltejs/kit",
|
|
14
|
+
"url": "git+https://github.com/sveltejs/kit.git",
|
|
15
15
|
"directory": "packages/kit"
|
|
16
16
|
},
|
|
17
17
|
"license": "MIT",
|
|
@@ -242,6 +242,11 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env }) {
|
|
|
242
242
|
const filepath = saved.get(file);
|
|
243
243
|
if (filepath) return readFileSync(filepath);
|
|
244
244
|
|
|
245
|
+
// Static assets emitted during build
|
|
246
|
+
if (file.startsWith(config.appDir)) {
|
|
247
|
+
return readFileSync(`${out}/server/${file}`);
|
|
248
|
+
}
|
|
249
|
+
|
|
245
250
|
// stuff in `static`
|
|
246
251
|
return readFileSync(join(config.files.assets, file));
|
|
247
252
|
},
|
package/src/exports/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
strip_data_suffix,
|
|
9
9
|
strip_resolution_suffix
|
|
10
10
|
} from '../runtime/pathname.js';
|
|
11
|
+
import { text_encoder } from '../runtime/utils.js';
|
|
11
12
|
|
|
12
13
|
export { VERSION } from '../version.js';
|
|
13
14
|
|
|
@@ -142,7 +143,7 @@ export function json(data, init) {
|
|
|
142
143
|
// means less duplicated work
|
|
143
144
|
const headers = new Headers(init?.headers);
|
|
144
145
|
if (!headers.has('content-length')) {
|
|
145
|
-
headers.set('content-length',
|
|
146
|
+
headers.set('content-length', text_encoder.encode(body).byteLength.toString());
|
|
146
147
|
}
|
|
147
148
|
|
|
148
149
|
if (!headers.has('content-type')) {
|
|
@@ -155,8 +156,6 @@ export function json(data, init) {
|
|
|
155
156
|
});
|
|
156
157
|
}
|
|
157
158
|
|
|
158
|
-
const encoder = new TextEncoder();
|
|
159
|
-
|
|
160
159
|
/**
|
|
161
160
|
* Create a `Response` object from the supplied body.
|
|
162
161
|
* @param {string} body The value that will be used as-is.
|
|
@@ -165,7 +164,7 @@ const encoder = new TextEncoder();
|
|
|
165
164
|
export function text(body, init) {
|
|
166
165
|
const headers = new Headers(init?.headers);
|
|
167
166
|
if (!headers.has('content-length')) {
|
|
168
|
-
const encoded =
|
|
167
|
+
const encoded = text_encoder.encode(body);
|
|
169
168
|
headers.set('content-length', encoded.byteLength.toString());
|
|
170
169
|
return new Response(encoded, {
|
|
171
170
|
...init,
|
package/src/exports/public.d.ts
CHANGED
|
@@ -1020,12 +1020,15 @@ export interface NavigationEvent<
|
|
|
1020
1020
|
/**
|
|
1021
1021
|
* Information about the target of a specific navigation.
|
|
1022
1022
|
*/
|
|
1023
|
-
export interface NavigationTarget
|
|
1023
|
+
export interface NavigationTarget<
|
|
1024
|
+
Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>,
|
|
1025
|
+
RouteId extends AppRouteId | null = AppRouteId | null
|
|
1026
|
+
> {
|
|
1024
1027
|
/**
|
|
1025
1028
|
* Parameters of the target page - e.g. for a route like `/blog/[slug]`, a `{ slug: string }` object.
|
|
1026
1029
|
* Is `null` if the target is not part of the SvelteKit app (could not be resolved to a route).
|
|
1027
1030
|
*/
|
|
1028
|
-
params:
|
|
1031
|
+
params: Params | null;
|
|
1029
1032
|
/**
|
|
1030
1033
|
* Info about the target route
|
|
1031
1034
|
*/
|
|
@@ -1033,7 +1036,7 @@ export interface NavigationTarget {
|
|
|
1033
1036
|
/**
|
|
1034
1037
|
* The ID of the current route - e.g. for `src/routes/blog/[slug]`, it would be `/blog/[slug]`. It is `null` when no route is matched.
|
|
1035
1038
|
*/
|
|
1036
|
-
id:
|
|
1039
|
+
id: RouteId | null;
|
|
1037
1040
|
};
|
|
1038
1041
|
/**
|
|
1039
1042
|
* The URL that is navigated to
|
|
@@ -1565,6 +1568,8 @@ export type RemoteForm<Result> = {
|
|
|
1565
1568
|
for(key: string | number | boolean): Omit<RemoteForm<Result>, 'for'>;
|
|
1566
1569
|
/** The result of the form submission */
|
|
1567
1570
|
get result(): Result | undefined;
|
|
1571
|
+
/** The number of pending submissions */
|
|
1572
|
+
get pending(): number;
|
|
1568
1573
|
/** Spread this onto a `<button>` or `<input type="submit">` */
|
|
1569
1574
|
buttonProps: {
|
|
1570
1575
|
type: 'submit';
|
|
@@ -1586,14 +1591,20 @@ export type RemoteForm<Result> = {
|
|
|
1586
1591
|
formaction: string;
|
|
1587
1592
|
onclick: (event: Event) => void;
|
|
1588
1593
|
};
|
|
1594
|
+
/** The number of pending submissions */
|
|
1595
|
+
get pending(): number;
|
|
1589
1596
|
};
|
|
1590
1597
|
};
|
|
1591
1598
|
|
|
1592
1599
|
/**
|
|
1593
1600
|
* The return value of a remote `command` function. See [Remote functions](https://svelte.dev/docs/kit/remote-functions#command) for full documentation.
|
|
1594
1601
|
*/
|
|
1595
|
-
export type RemoteCommand<Input, Output> =
|
|
1596
|
-
|
|
1602
|
+
export type RemoteCommand<Input, Output> = {
|
|
1603
|
+
(arg: Input): Promise<Awaited<Output>> & {
|
|
1604
|
+
updates(...queries: Array<RemoteQuery<any> | RemoteQueryOverride>): Promise<Awaited<Output>>;
|
|
1605
|
+
};
|
|
1606
|
+
/** The number of pending command executions */
|
|
1607
|
+
get pending(): number;
|
|
1597
1608
|
};
|
|
1598
1609
|
|
|
1599
1610
|
export type RemoteResource<T> = Promise<Awaited<T>> & {
|
|
@@ -1623,7 +1634,7 @@ export type RemoteQuery<T> = RemoteResource<T> & {
|
|
|
1623
1634
|
*/
|
|
1624
1635
|
refresh(): Promise<void>;
|
|
1625
1636
|
/**
|
|
1626
|
-
* Temporarily override the value of a query. This is used with the `updates` method of a [command](https://svelte.dev/docs/kit/remote-functions#command-
|
|
1637
|
+
* Temporarily override the value of a query. This is used with the `updates` method of a [command](https://svelte.dev/docs/kit/remote-functions#command-Updating-queries) or [enhanced form submission](https://svelte.dev/docs/kit/remote-functions#form-enhance) to provide optimistic updates.
|
|
1627
1638
|
*
|
|
1628
1639
|
* ```svelte
|
|
1629
1640
|
* <script>
|
|
@@ -102,9 +102,10 @@ export async function treeshake_prerendered_remotes(out, manifest_data, metadata
|
|
|
102
102
|
input[prefix + remote.hash] = remote_file;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
const bundle = await vite.build({
|
|
105
|
+
const bundle = /** @type {import('vite').Rollup.RollupOutput} */ (await vite.build({
|
|
106
106
|
configFile: false,
|
|
107
107
|
build: {
|
|
108
|
+
write: false,
|
|
108
109
|
ssr: true,
|
|
109
110
|
rollupOptions: {
|
|
110
111
|
external: (id) => {
|
|
@@ -114,11 +115,10 @@ export async function treeshake_prerendered_remotes(out, manifest_data, metadata
|
|
|
114
115
|
input
|
|
115
116
|
}
|
|
116
117
|
}
|
|
117
|
-
});
|
|
118
|
+
}));
|
|
118
119
|
|
|
119
|
-
// @ts-expect-error TypeScript doesn't know what type `bundle` is
|
|
120
120
|
for (const chunk of bundle.output) {
|
|
121
|
-
if (chunk.name.startsWith(prefix)) {
|
|
121
|
+
if (chunk.type === 'chunk' && chunk.name.startsWith(prefix)) {
|
|
122
122
|
fs.writeFileSync(`${dir}/${chunk.fileName.slice(prefix.length)}`, chunk.code);
|
|
123
123
|
}
|
|
124
124
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { read_implementation, manifest } from '__sveltekit/server';
|
|
2
2
|
import { base } from '__sveltekit/paths';
|
|
3
3
|
import { DEV } from 'esm-env';
|
|
4
|
-
import {
|
|
4
|
+
import { base64_decode } from '../../utils.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Read the contents of an imported asset from the filesystem
|
|
@@ -33,8 +33,9 @@ export function read(asset) {
|
|
|
33
33
|
const data = asset.slice(match[0].length);
|
|
34
34
|
|
|
35
35
|
if (match[2] !== undefined) {
|
|
36
|
-
const decoded =
|
|
36
|
+
const decoded = base64_decode(data);
|
|
37
37
|
|
|
38
|
+
// @ts-ignore passing a Uint8Array to `new Response(...)` is fine
|
|
38
39
|
return new Response(decoded, {
|
|
39
40
|
headers: {
|
|
40
41
|
'Content-Length': decoded.byteLength.toString(),
|
|
@@ -97,6 +97,16 @@ export function form(fn) {
|
|
|
97
97
|
}
|
|
98
98
|
});
|
|
99
99
|
|
|
100
|
+
// On the server, pending is always 0
|
|
101
|
+
Object.defineProperty(instance, 'pending', {
|
|
102
|
+
get: () => 0
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// On the server, buttonProps.pending is always 0
|
|
106
|
+
Object.defineProperty(button_props, 'pending', {
|
|
107
|
+
get: () => 0
|
|
108
|
+
});
|
|
109
|
+
|
|
100
110
|
if (key == undefined) {
|
|
101
111
|
Object.defineProperty(instance, 'for', {
|
|
102
112
|
/** @type {RemoteForm<any>['for']} */
|
|
@@ -44,6 +44,7 @@ import { get_message, get_status } from '../../utils/error.js';
|
|
|
44
44
|
import { writable } from 'svelte/store';
|
|
45
45
|
import { page, update, navigating } from './state.svelte.js';
|
|
46
46
|
import { add_data_suffix, add_resolution_suffix } from '../pathname.js';
|
|
47
|
+
import { text_decoder } from '../utils.js';
|
|
47
48
|
|
|
48
49
|
export { load_css };
|
|
49
50
|
const ICON_REL_ATTRIBUTES = new Set(['icon', 'shortcut icon', 'apple-touch-icon']);
|
|
@@ -2781,7 +2782,6 @@ async function load_data(url, invalid) {
|
|
|
2781
2782
|
*/
|
|
2782
2783
|
const deferreds = new Map();
|
|
2783
2784
|
const reader = /** @type {ReadableStream<Uint8Array>} */ (res.body).getReader();
|
|
2784
|
-
const decoder = new TextDecoder();
|
|
2785
2785
|
|
|
2786
2786
|
/**
|
|
2787
2787
|
* @param {any} data
|
|
@@ -2804,7 +2804,7 @@ async function load_data(url, invalid) {
|
|
|
2804
2804
|
const { done, value } = await reader.read();
|
|
2805
2805
|
if (done && !text) break;
|
|
2806
2806
|
|
|
2807
|
-
text += !value && text ? '\n' :
|
|
2807
|
+
text += !value && text ? '\n' : text_decoder.decode(value, { stream: true }); // no value -> final chunk -> add a new line to trigger the last parse
|
|
2808
2808
|
|
|
2809
2809
|
while (true) {
|
|
2810
2810
|
const split = text.indexOf('\n');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BROWSER, DEV } from 'esm-env';
|
|
2
2
|
import { hash } from '../../utils/hash.js';
|
|
3
|
-
import {
|
|
3
|
+
import { base64_decode } from '../utils.js';
|
|
4
4
|
|
|
5
5
|
let loading = 0;
|
|
6
6
|
|
|
@@ -90,6 +90,7 @@ export function initial_fetch(resource, opts) {
|
|
|
90
90
|
|
|
91
91
|
const script = document.querySelector(selector);
|
|
92
92
|
if (script?.textContent) {
|
|
93
|
+
script.remove(); // In case multiple script tags match the same selector
|
|
93
94
|
let { body, ...init } = JSON.parse(script.textContent);
|
|
94
95
|
|
|
95
96
|
const ttl = script.getAttribute('data-ttl');
|
|
@@ -98,7 +99,7 @@ export function initial_fetch(resource, opts) {
|
|
|
98
99
|
if (b64 !== null) {
|
|
99
100
|
// Can't use native_fetch('data:...;base64,${body}')
|
|
100
101
|
// csp can block the request
|
|
101
|
-
body =
|
|
102
|
+
body = base64_decode(body);
|
|
102
103
|
}
|
|
103
104
|
|
|
104
105
|
return Promise.resolve(new Response(body, init));
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/** @import { RemoteCommand, RemoteQueryOverride } from '@sveltejs/kit' */
|
|
2
|
+
/** @import { RemoteFunctionResponse } from 'types' */
|
|
3
|
+
/** @import { Query } from './query.svelte.js' */
|
|
4
|
+
import { app_dir, base } from '__sveltekit/paths';
|
|
5
|
+
import * as devalue from 'devalue';
|
|
6
|
+
import { HttpError } from '@sveltejs/kit/internal';
|
|
7
|
+
import { app } from '../client.js';
|
|
8
|
+
import { stringify_remote_arg } from '../../shared.js';
|
|
9
|
+
import { refresh_queries, release_overrides } from './shared.svelte.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Client-version of the `command` function from `$app/server`.
|
|
13
|
+
* @param {string} id
|
|
14
|
+
* @returns {RemoteCommand<any, any>}
|
|
15
|
+
*/
|
|
16
|
+
export function command(id) {
|
|
17
|
+
/** @type {number} */
|
|
18
|
+
let pending_count = $state(0);
|
|
19
|
+
|
|
20
|
+
// Careful: This function MUST be synchronous (can't use the async keyword) because the return type has to be a promise with an updates() method.
|
|
21
|
+
// If we make it async, the return type will be a promise that resolves to a promise with an updates() method, which is not what we want.
|
|
22
|
+
/** @type {RemoteCommand<any, any>} */
|
|
23
|
+
const command_function = (arg) => {
|
|
24
|
+
/** @type {Array<Query<any> | RemoteQueryOverride>} */
|
|
25
|
+
let updates = [];
|
|
26
|
+
|
|
27
|
+
// Increment pending count when command starts
|
|
28
|
+
pending_count++;
|
|
29
|
+
|
|
30
|
+
/** @type {Promise<any> & { updates: (...args: any[]) => any }} */
|
|
31
|
+
const promise = (async () => {
|
|
32
|
+
try {
|
|
33
|
+
// Wait a tick to give room for the `updates` method to be called
|
|
34
|
+
await Promise.resolve();
|
|
35
|
+
|
|
36
|
+
const response = await fetch(`${base}/${app_dir}/remote/${id}`, {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
body: JSON.stringify({
|
|
39
|
+
payload: stringify_remote_arg(arg, app.hooks.transport),
|
|
40
|
+
refreshes: updates.map((u) => u._key)
|
|
41
|
+
}),
|
|
42
|
+
headers: {
|
|
43
|
+
'Content-Type': 'application/json'
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
release_overrides(updates);
|
|
49
|
+
// We only end up here in case of a network error or if the server has an internal error
|
|
50
|
+
// (which shouldn't happen because we handle errors on the server and always send a 200 response)
|
|
51
|
+
throw new Error('Failed to execute remote function');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const result = /** @type {RemoteFunctionResponse} */ (await response.json());
|
|
55
|
+
if (result.type === 'redirect') {
|
|
56
|
+
release_overrides(updates);
|
|
57
|
+
throw new Error(
|
|
58
|
+
'Redirects are not allowed in commands. Return a result instead and use goto on the client'
|
|
59
|
+
);
|
|
60
|
+
} else if (result.type === 'error') {
|
|
61
|
+
release_overrides(updates);
|
|
62
|
+
throw new HttpError(result.status ?? 500, result.error);
|
|
63
|
+
} else {
|
|
64
|
+
if (result.refreshes) {
|
|
65
|
+
refresh_queries(result.refreshes, updates);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return devalue.parse(result.result, app.decoders);
|
|
69
|
+
}
|
|
70
|
+
} finally {
|
|
71
|
+
// Decrement pending count when command completes
|
|
72
|
+
pending_count--;
|
|
73
|
+
}
|
|
74
|
+
})();
|
|
75
|
+
|
|
76
|
+
promise.updates = (/** @type {any} */ ...args) => {
|
|
77
|
+
updates = args;
|
|
78
|
+
// @ts-expect-error Don't allow updates to be called multiple times
|
|
79
|
+
delete promise.updates;
|
|
80
|
+
return promise;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
return promise;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
Object.defineProperty(command_function, 'pending', {
|
|
87
|
+
get: () => pending_count
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return command_function;
|
|
91
|
+
}
|
|
@@ -5,7 +5,14 @@ import { app_dir, base } from '__sveltekit/paths';
|
|
|
5
5
|
import * as devalue from 'devalue';
|
|
6
6
|
import { DEV } from 'esm-env';
|
|
7
7
|
import { HttpError } from '@sveltejs/kit/internal';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
app,
|
|
10
|
+
remote_responses,
|
|
11
|
+
started,
|
|
12
|
+
goto,
|
|
13
|
+
set_nearest_error_page,
|
|
14
|
+
invalidateAll
|
|
15
|
+
} from '../client.js';
|
|
9
16
|
import { tick } from 'svelte';
|
|
10
17
|
import { refresh_queries, release_overrides } from './shared.svelte.js';
|
|
11
18
|
|
|
@@ -27,6 +34,9 @@ export function form(id) {
|
|
|
27
34
|
/** @type {any} */
|
|
28
35
|
let result = $state(started ? undefined : remote_responses[action_id]);
|
|
29
36
|
|
|
37
|
+
/** @type {number} */
|
|
38
|
+
let pending_count = $state(0);
|
|
39
|
+
|
|
30
40
|
/**
|
|
31
41
|
* @param {FormData} data
|
|
32
42
|
* @returns {Promise<any> & { updates: (...args: any[]) => any }}
|
|
@@ -42,6 +52,9 @@ export function form(id) {
|
|
|
42
52
|
entry.count++;
|
|
43
53
|
}
|
|
44
54
|
|
|
55
|
+
// Increment pending count when submission starts
|
|
56
|
+
pending_count++;
|
|
57
|
+
|
|
45
58
|
/** @type {Array<Query<any> | RemoteQueryOverride>} */
|
|
46
59
|
let updates = [];
|
|
47
60
|
|
|
@@ -78,7 +91,11 @@ export function form(id) {
|
|
|
78
91
|
if (form_result.type === 'result') {
|
|
79
92
|
result = devalue.parse(form_result.result, app.decoders);
|
|
80
93
|
|
|
81
|
-
|
|
94
|
+
if (form_result.refreshes) {
|
|
95
|
+
refresh_queries(form_result.refreshes, updates);
|
|
96
|
+
} else {
|
|
97
|
+
void invalidateAll();
|
|
98
|
+
}
|
|
82
99
|
} else if (form_result.type === 'redirect') {
|
|
83
100
|
const refreshes = form_result.refreshes ?? '';
|
|
84
101
|
const invalidateAll = !refreshes && updates.length === 0;
|
|
@@ -94,6 +111,9 @@ export function form(id) {
|
|
|
94
111
|
release_overrides(updates);
|
|
95
112
|
throw e;
|
|
96
113
|
} finally {
|
|
114
|
+
// Decrement pending count when submission completes
|
|
115
|
+
pending_count--;
|
|
116
|
+
|
|
97
117
|
void tick().then(() => {
|
|
98
118
|
if (entry) {
|
|
99
119
|
entry.count--;
|
|
@@ -242,6 +262,10 @@ export function form(id) {
|
|
|
242
262
|
}
|
|
243
263
|
});
|
|
244
264
|
|
|
265
|
+
Object.defineProperty(button_props, 'pending', {
|
|
266
|
+
get: () => pending_count
|
|
267
|
+
});
|
|
268
|
+
|
|
245
269
|
Object.defineProperties(instance, {
|
|
246
270
|
buttonProps: {
|
|
247
271
|
value: button_props
|
|
@@ -249,6 +273,9 @@ export function form(id) {
|
|
|
249
273
|
result: {
|
|
250
274
|
get: () => result
|
|
251
275
|
},
|
|
276
|
+
pending: {
|
|
277
|
+
get: () => pending_count
|
|
278
|
+
},
|
|
252
279
|
enhance: {
|
|
253
280
|
/** @type {RemoteForm<any>['enhance']} */
|
|
254
281
|
value: (callback) => {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/** @import { RemoteFunctionResponse } from 'types' */
|
|
3
3
|
/** @import { Query } from './query.svelte.js' */
|
|
4
4
|
import * as devalue from 'devalue';
|
|
5
|
-
import { app, goto,
|
|
5
|
+
import { app, goto, query_map } from '../client.js';
|
|
6
6
|
import { HttpError, Redirect } from '@sveltejs/kit/internal';
|
|
7
7
|
import { tick } from 'svelte';
|
|
8
8
|
import { create_remote_cache_key, stringify_remote_arg } from '../../shared.js';
|
|
@@ -125,19 +125,16 @@ export function release_overrides(updates) {
|
|
|
125
125
|
*/
|
|
126
126
|
export function refresh_queries(stringified_refreshes, updates = []) {
|
|
127
127
|
const refreshes = Object.entries(devalue.parse(stringified_refreshes, app.decoders));
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
// Update the query with the new value
|
|
137
|
-
const entry = query_map.get(key);
|
|
138
|
-
entry?.resource.set(value);
|
|
128
|
+
|
|
129
|
+
// `refreshes` is a superset of `updates`
|
|
130
|
+
for (const [key, value] of refreshes) {
|
|
131
|
+
// If there was an optimistic update, release it right before we update the query
|
|
132
|
+
const update = updates.find((u) => u._key === key);
|
|
133
|
+
if (update && 'release' in update) {
|
|
134
|
+
update.release();
|
|
139
135
|
}
|
|
140
|
-
|
|
141
|
-
|
|
136
|
+
// Update the query with the new value
|
|
137
|
+
const entry = query_map.get(key);
|
|
138
|
+
entry?.resource.set(value);
|
|
142
139
|
}
|
|
143
140
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { parse, serialize } from 'cookie';
|
|
2
2
|
import { normalize_path, resolve } from '../../utils/url.js';
|
|
3
3
|
import { add_data_suffix } from '../pathname.js';
|
|
4
|
+
import { text_encoder } from '../utils.js';
|
|
4
5
|
|
|
5
6
|
// eslint-disable-next-line no-control-regex -- control characters are invalid in cookie names
|
|
6
7
|
const INVALID_COOKIE_CHARACTER_REGEX = /[\x00-\x1F\x7F()<>@,;:"/[\]?={} \t]/;
|
|
@@ -217,7 +218,7 @@ export function get_cookies(request, url) {
|
|
|
217
218
|
|
|
218
219
|
if (__SVELTEKIT_DEV__) {
|
|
219
220
|
const serialized = serialize(name, value, new_cookies[name].options);
|
|
220
|
-
if (
|
|
221
|
+
if (text_encoder.encode(serialized).byteLength > MAX_COOKIE_SIZE) {
|
|
221
222
|
throw new Error(`Cookie "${name}" is too large, and will be discarded by the browser`);
|
|
222
223
|
}
|
|
223
224
|
|
|
@@ -7,8 +7,7 @@ import { clarify_devalue_error, handle_error_and_jsonify, serialize_uses } from
|
|
|
7
7
|
import { normalize_path } from '../../../utils/url.js';
|
|
8
8
|
import * as devalue from 'devalue';
|
|
9
9
|
import { create_async_iterator } from '../../../utils/streaming.js';
|
|
10
|
-
|
|
11
|
-
const encoder = new TextEncoder();
|
|
10
|
+
import { text_encoder } from '../../utils.js';
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
13
|
* @param {import('@sveltejs/kit').RequestEvent} event
|
|
@@ -129,9 +128,9 @@ export async function render_data(
|
|
|
129
128
|
return new Response(
|
|
130
129
|
new ReadableStream({
|
|
131
130
|
async start(controller) {
|
|
132
|
-
controller.enqueue(
|
|
131
|
+
controller.enqueue(text_encoder.encode(data));
|
|
133
132
|
for await (const chunk of chunks) {
|
|
134
|
-
controller.enqueue(
|
|
133
|
+
controller.enqueue(text_encoder.encode(chunk));
|
|
135
134
|
}
|
|
136
135
|
controller.close();
|
|
137
136
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { text_encoder } from '../../utils.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* SHA-256 hashing function adapted from https://bitwiseshiftleft.github.io/sjcl
|
|
@@ -102,7 +102,7 @@ export function sha256(data) {
|
|
|
102
102
|
const bytes = new Uint8Array(out.buffer);
|
|
103
103
|
reverse_endianness(bytes);
|
|
104
104
|
|
|
105
|
-
return
|
|
105
|
+
return btoa(String.fromCharCode(...bytes));
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
/** The SHA-256 initialization vector */
|
|
@@ -160,7 +160,7 @@ function reverse_endianness(bytes) {
|
|
|
160
160
|
|
|
161
161
|
/** @param {string} str */
|
|
162
162
|
function encode(str) {
|
|
163
|
-
const encoded =
|
|
163
|
+
const encoded = text_encoder.encode(str);
|
|
164
164
|
const length = encoded.length * 8;
|
|
165
165
|
|
|
166
166
|
// result should be a multiple of 512 bits in length,
|
|
@@ -182,58 +182,3 @@ function encode(str) {
|
|
|
182
182
|
|
|
183
183
|
return words;
|
|
184
184
|
}
|
|
185
|
-
|
|
186
|
-
/*
|
|
187
|
-
Based on https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727
|
|
188
|
-
|
|
189
|
-
MIT License
|
|
190
|
-
Copyright (c) 2020 Egor Nepomnyaschih
|
|
191
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
192
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
193
|
-
in the Software without restriction, including without limitation the rights
|
|
194
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
195
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
196
|
-
furnished to do so, subject to the following conditions:
|
|
197
|
-
The above copyright notice and this permission notice shall be included in all
|
|
198
|
-
copies or substantial portions of the Software.
|
|
199
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
200
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
201
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
202
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
203
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
204
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
205
|
-
SOFTWARE.
|
|
206
|
-
*/
|
|
207
|
-
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('');
|
|
208
|
-
|
|
209
|
-
/** @param {Uint8Array} bytes */
|
|
210
|
-
export function base64(bytes) {
|
|
211
|
-
const l = bytes.length;
|
|
212
|
-
|
|
213
|
-
let result = '';
|
|
214
|
-
let i;
|
|
215
|
-
|
|
216
|
-
for (i = 2; i < l; i += 3) {
|
|
217
|
-
result += chars[bytes[i - 2] >> 2];
|
|
218
|
-
result += chars[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)];
|
|
219
|
-
result += chars[((bytes[i - 1] & 0x0f) << 2) | (bytes[i] >> 6)];
|
|
220
|
-
result += chars[bytes[i] & 0x3f];
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
if (i === l + 1) {
|
|
224
|
-
// 1 octet yet to write
|
|
225
|
-
result += chars[bytes[i - 2] >> 2];
|
|
226
|
-
result += chars[(bytes[i - 2] & 0x03) << 4];
|
|
227
|
-
result += '==';
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
if (i === l) {
|
|
231
|
-
// 2 octets yet to write
|
|
232
|
-
result += chars[bytes[i - 2] >> 2];
|
|
233
|
-
result += chars[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)];
|
|
234
|
-
result += chars[(bytes[i - 1] & 0x0f) << 2];
|
|
235
|
-
result += '=';
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
return result;
|
|
239
|
-
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { escape_html } from '../../../utils/escape.js';
|
|
2
|
-
import {
|
|
2
|
+
import { sha256 } from './crypto.js';
|
|
3
3
|
|
|
4
4
|
const array = new Uint8Array(16);
|
|
5
5
|
|
|
6
6
|
function generate_nonce() {
|
|
7
7
|
crypto.getRandomValues(array);
|
|
8
|
-
return
|
|
8
|
+
return btoa(String.fromCharCode(...array));
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
const quoted = new Set([
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DEV } from 'esm-env';
|
|
2
2
|
import { disable_search, make_trackable } from '../../../utils/url.js';
|
|
3
3
|
import { validate_depends } from '../../shared.js';
|
|
4
|
-
import {
|
|
4
|
+
import { base64_encode, text_decoder } from '../../utils.js';
|
|
5
5
|
import { with_event } from '../../app/server/event.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -316,12 +316,14 @@ export function create_universal_fetch(event, state, fetched, csr, resolve_opts)
|
|
|
316
316
|
return async () => {
|
|
317
317
|
const buffer = await response.arrayBuffer();
|
|
318
318
|
|
|
319
|
+
const bytes = new Uint8Array(buffer);
|
|
320
|
+
|
|
319
321
|
if (dependency) {
|
|
320
|
-
dependency.body =
|
|
322
|
+
dependency.body = bytes;
|
|
321
323
|
}
|
|
322
324
|
|
|
323
325
|
if (buffer instanceof ArrayBuffer) {
|
|
324
|
-
await push_fetched(
|
|
326
|
+
await push_fetched(base64_encode(bytes), true);
|
|
325
327
|
}
|
|
326
328
|
|
|
327
329
|
return buffer;
|
|
@@ -394,13 +396,12 @@ export function create_universal_fetch(event, state, fetched, csr, resolve_opts)
|
|
|
394
396
|
async function stream_to_string(stream) {
|
|
395
397
|
let result = '';
|
|
396
398
|
const reader = stream.getReader();
|
|
397
|
-
const decoder = new TextDecoder();
|
|
398
399
|
while (true) {
|
|
399
400
|
const { done, value } = await reader.read();
|
|
400
401
|
if (done) {
|
|
401
402
|
break;
|
|
402
403
|
}
|
|
403
|
-
result +=
|
|
404
|
+
result += text_decoder.decode(value);
|
|
404
405
|
}
|
|
405
406
|
return result;
|
|
406
407
|
}
|
|
@@ -17,6 +17,7 @@ import { create_server_routing_response, generate_route_object } from './server_
|
|
|
17
17
|
import { add_resolution_suffix } from '../../pathname.js';
|
|
18
18
|
import { with_event } from '../../app/server/event.js';
|
|
19
19
|
import { get_event_state } from '../event-state.js';
|
|
20
|
+
import { text_encoder } from '../../utils.js';
|
|
20
21
|
|
|
21
22
|
// TODO rename this function/module
|
|
22
23
|
|
|
@@ -25,8 +26,6 @@ const updated = {
|
|
|
25
26
|
check: () => false
|
|
26
27
|
};
|
|
27
28
|
|
|
28
|
-
const encoder = new TextEncoder();
|
|
29
|
-
|
|
30
29
|
/**
|
|
31
30
|
* Creates the HTML response.
|
|
32
31
|
* @param {{
|
|
@@ -586,9 +585,9 @@ export async function render_response({
|
|
|
586
585
|
: new Response(
|
|
587
586
|
new ReadableStream({
|
|
588
587
|
async start(controller) {
|
|
589
|
-
controller.enqueue(
|
|
588
|
+
controller.enqueue(text_encoder.encode(transformed + '\n'));
|
|
590
589
|
for await (const chunk of chunks) {
|
|
591
|
-
controller.enqueue(
|
|
590
|
+
controller.enqueue(text_encoder.encode(chunk));
|
|
592
591
|
}
|
|
593
592
|
controller.close();
|
|
594
593
|
},
|
|
@@ -62,13 +62,7 @@ export async function handle_remote_call(event, options, manifest, id) {
|
|
|
62
62
|
/** @type {RemoteFunctionResponse} */ ({
|
|
63
63
|
type: 'result',
|
|
64
64
|
result: stringify(data, transport),
|
|
65
|
-
refreshes:
|
|
66
|
-
{
|
|
67
|
-
...get_event_state(event).refreshes,
|
|
68
|
-
...(await apply_client_refreshes(/** @type {string[]} */ (form_client_refreshes)))
|
|
69
|
-
},
|
|
70
|
-
transport
|
|
71
|
-
)
|
|
65
|
+
refreshes: await serialize_refreshes(/** @type {string[]} */ (form_client_refreshes))
|
|
72
66
|
})
|
|
73
67
|
);
|
|
74
68
|
}
|
|
@@ -78,13 +72,12 @@ export async function handle_remote_call(event, options, manifest, id) {
|
|
|
78
72
|
const { payload, refreshes } = await event.request.json();
|
|
79
73
|
const arg = parse_remote_arg(payload, transport);
|
|
80
74
|
const data = await with_event(event, () => fn(arg));
|
|
81
|
-
const refreshed = await apply_client_refreshes(refreshes);
|
|
82
75
|
|
|
83
76
|
return json(
|
|
84
77
|
/** @type {RemoteFunctionResponse} */ ({
|
|
85
78
|
type: 'result',
|
|
86
79
|
result: stringify(data, transport),
|
|
87
|
-
refreshes:
|
|
80
|
+
refreshes: await serialize_refreshes(refreshes)
|
|
88
81
|
})
|
|
89
82
|
);
|
|
90
83
|
}
|
|
@@ -107,14 +100,10 @@ export async function handle_remote_call(event, options, manifest, id) {
|
|
|
107
100
|
);
|
|
108
101
|
} catch (error) {
|
|
109
102
|
if (error instanceof Redirect) {
|
|
110
|
-
const refreshes = {
|
|
111
|
-
...(get_event_state(event).refreshes ?? {}), // could be set by form actions
|
|
112
|
-
...(await apply_client_refreshes(form_client_refreshes ?? []))
|
|
113
|
-
};
|
|
114
103
|
return json({
|
|
115
104
|
type: 'redirect',
|
|
116
105
|
location: error.location,
|
|
117
|
-
refreshes:
|
|
106
|
+
refreshes: await serialize_refreshes(form_client_refreshes ?? [])
|
|
118
107
|
});
|
|
119
108
|
}
|
|
120
109
|
|
|
@@ -132,26 +121,33 @@ export async function handle_remote_call(event, options, manifest, id) {
|
|
|
132
121
|
);
|
|
133
122
|
}
|
|
134
123
|
|
|
135
|
-
/**
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
124
|
+
/**
|
|
125
|
+
* @param {string[]} client_refreshes
|
|
126
|
+
*/
|
|
127
|
+
async function serialize_refreshes(client_refreshes) {
|
|
128
|
+
const refreshes = {
|
|
129
|
+
...get_event_state(event).refreshes,
|
|
130
|
+
...Object.fromEntries(
|
|
131
|
+
await Promise.all(
|
|
132
|
+
client_refreshes.map(async (key) => {
|
|
133
|
+
const [hash, name, payload] = key.split('/');
|
|
134
|
+
const loader = manifest._.remotes[hash];
|
|
135
|
+
|
|
136
|
+
// TODO what do we do in this case? erroring after the mutation has happened is not great
|
|
137
|
+
if (!loader) error(400, 'Bad Request');
|
|
138
|
+
|
|
139
|
+
const module = await loader();
|
|
140
|
+
const fn = module[name];
|
|
141
|
+
|
|
142
|
+
if (!fn) error(400, 'Bad Request');
|
|
143
|
+
|
|
144
|
+
return [key, await with_event(event, () => fn(parse_remote_arg(payload, transport)))];
|
|
145
|
+
})
|
|
146
|
+
)
|
|
153
147
|
)
|
|
154
|
-
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
return Object.keys(refreshes).length > 0 ? stringify(refreshes, transport) : undefined;
|
|
155
151
|
}
|
|
156
152
|
}
|
|
157
153
|
|
package/src/runtime/shared.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** @import { Transport } from '@sveltejs/kit' */
|
|
2
2
|
import * as devalue from 'devalue';
|
|
3
|
+
import { base64_decode, base64_encode, text_decoder } from './utils.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* @param {string} route_id
|
|
@@ -41,12 +42,8 @@ export function stringify_remote_arg(value, transport) {
|
|
|
41
42
|
// If people hit file/url size limits, we can look into using something like compress_and_encode_text from svelte.dev beyond a certain size
|
|
42
43
|
const json_string = stringify(value, transport);
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return btoa(String.fromCharCode(...utf8_bytes))
|
|
47
|
-
.replace(/=/g, '')
|
|
48
|
-
.replace(/\+/g, '-')
|
|
49
|
-
.replace(/\//g, '_');
|
|
45
|
+
const bytes = new TextEncoder().encode(json_string);
|
|
46
|
+
return base64_encode(bytes).replaceAll('=', '').replaceAll('+', '-').replaceAll('/', '_');
|
|
50
47
|
}
|
|
51
48
|
|
|
52
49
|
/**
|
|
@@ -57,13 +54,12 @@ export function stringify_remote_arg(value, transport) {
|
|
|
57
54
|
export function parse_remote_arg(string, transport) {
|
|
58
55
|
if (!string) return undefined;
|
|
59
56
|
|
|
60
|
-
const
|
|
57
|
+
const json_string = text_decoder.decode(
|
|
58
|
+
// no need to add back `=` characters, atob can handle it
|
|
59
|
+
base64_decode(string.replaceAll('-', '+').replaceAll('_', '/'))
|
|
60
|
+
);
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
const base64_restored = string.replace(/-/g, '+').replace(/_/g, '/');
|
|
64
|
-
const binary_string = atob(base64_restored);
|
|
65
|
-
const utf8_bytes = new Uint8Array([...binary_string].map((char) => char.charCodeAt(0)));
|
|
66
|
-
const json_string = new TextDecoder().decode(utf8_bytes);
|
|
62
|
+
const decoders = Object.fromEntries(Object.entries(transport).map(([k, v]) => [k, v.decode]));
|
|
67
63
|
|
|
68
64
|
return devalue.parse(json_string, decoders);
|
|
69
65
|
}
|
package/src/runtime/utils.js
CHANGED
|
@@ -1,37 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
* @param {string} text
|
|
3
|
-
* @returns {ArrayBufferLike}
|
|
4
|
-
*/
|
|
5
|
-
export function b64_decode(text) {
|
|
6
|
-
const d = atob(text);
|
|
1
|
+
import { BROWSER } from 'esm-env';
|
|
7
2
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
for (let i = 0; i < d.length; i++) {
|
|
11
|
-
u8[i] = d.charCodeAt(i);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
return u8.buffer;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* @param {ArrayBuffer} buffer
|
|
19
|
-
* @returns {string}
|
|
20
|
-
*/
|
|
21
|
-
export function b64_encode(buffer) {
|
|
22
|
-
if (globalThis.Buffer) {
|
|
23
|
-
return Buffer.from(buffer).toString('base64');
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const little_endian = new Uint8Array(new Uint16Array([1]).buffer)[0] > 0;
|
|
27
|
-
|
|
28
|
-
// The Uint16Array(Uint8Array(...)) ensures the code points are padded with 0's
|
|
29
|
-
return btoa(
|
|
30
|
-
new TextDecoder(little_endian ? 'utf-16le' : 'utf-16be').decode(
|
|
31
|
-
new Uint16Array(new Uint8Array(buffer))
|
|
32
|
-
)
|
|
33
|
-
);
|
|
34
|
-
}
|
|
3
|
+
export const text_encoder = new TextEncoder();
|
|
4
|
+
export const text_decoder = new TextDecoder();
|
|
35
5
|
|
|
36
6
|
/**
|
|
37
7
|
* Like node's path.relative, but without using node
|
|
@@ -53,3 +23,43 @@ export function get_relative_path(from, to) {
|
|
|
53
23
|
|
|
54
24
|
return from_parts.concat(to_parts).join('/');
|
|
55
25
|
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {Uint8Array} bytes
|
|
29
|
+
* @returns {string}
|
|
30
|
+
*/
|
|
31
|
+
export function base64_encode(bytes) {
|
|
32
|
+
// Using `Buffer` is faster than iterating
|
|
33
|
+
if (!BROWSER && globalThis.Buffer) {
|
|
34
|
+
return globalThis.Buffer.from(bytes).toString('base64');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let binary = '';
|
|
38
|
+
|
|
39
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
40
|
+
binary += String.fromCharCode(bytes[i]);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return btoa(binary);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @param {string} encoded
|
|
48
|
+
* @returns {Uint8Array}
|
|
49
|
+
*/
|
|
50
|
+
export function base64_decode(encoded) {
|
|
51
|
+
// Using `Buffer` is faster than iterating
|
|
52
|
+
if (!BROWSER && globalThis.Buffer) {
|
|
53
|
+
const buffer = globalThis.Buffer.from(encoded, 'base64');
|
|
54
|
+
return new Uint8Array(buffer);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const binary = atob(encoded);
|
|
58
|
+
const bytes = new Uint8Array(binary.length);
|
|
59
|
+
|
|
60
|
+
for (let i = 0; i < binary.length; i++) {
|
|
61
|
+
bytes[i] = binary.charCodeAt(i);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return bytes;
|
|
65
|
+
}
|
package/src/types/internal.d.ts
CHANGED
package/src/version.js
CHANGED
package/types/index.d.ts
CHANGED
|
@@ -997,12 +997,15 @@ declare module '@sveltejs/kit' {
|
|
|
997
997
|
/**
|
|
998
998
|
* Information about the target of a specific navigation.
|
|
999
999
|
*/
|
|
1000
|
-
export interface NavigationTarget
|
|
1000
|
+
export interface NavigationTarget<
|
|
1001
|
+
Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>,
|
|
1002
|
+
RouteId extends AppRouteId | null = AppRouteId | null
|
|
1003
|
+
> {
|
|
1001
1004
|
/**
|
|
1002
1005
|
* Parameters of the target page - e.g. for a route like `/blog/[slug]`, a `{ slug: string }` object.
|
|
1003
1006
|
* Is `null` if the target is not part of the SvelteKit app (could not be resolved to a route).
|
|
1004
1007
|
*/
|
|
1005
|
-
params:
|
|
1008
|
+
params: Params | null;
|
|
1006
1009
|
/**
|
|
1007
1010
|
* Info about the target route
|
|
1008
1011
|
*/
|
|
@@ -1010,7 +1013,7 @@ declare module '@sveltejs/kit' {
|
|
|
1010
1013
|
/**
|
|
1011
1014
|
* The ID of the current route - e.g. for `src/routes/blog/[slug]`, it would be `/blog/[slug]`. It is `null` when no route is matched.
|
|
1012
1015
|
*/
|
|
1013
|
-
id:
|
|
1016
|
+
id: RouteId | null;
|
|
1014
1017
|
};
|
|
1015
1018
|
/**
|
|
1016
1019
|
* The URL that is navigated to
|
|
@@ -1542,6 +1545,8 @@ declare module '@sveltejs/kit' {
|
|
|
1542
1545
|
for(key: string | number | boolean): Omit<RemoteForm<Result>, 'for'>;
|
|
1543
1546
|
/** The result of the form submission */
|
|
1544
1547
|
get result(): Result | undefined;
|
|
1548
|
+
/** The number of pending submissions */
|
|
1549
|
+
get pending(): number;
|
|
1545
1550
|
/** Spread this onto a `<button>` or `<input type="submit">` */
|
|
1546
1551
|
buttonProps: {
|
|
1547
1552
|
type: 'submit';
|
|
@@ -1563,14 +1568,20 @@ declare module '@sveltejs/kit' {
|
|
|
1563
1568
|
formaction: string;
|
|
1564
1569
|
onclick: (event: Event) => void;
|
|
1565
1570
|
};
|
|
1571
|
+
/** The number of pending submissions */
|
|
1572
|
+
get pending(): number;
|
|
1566
1573
|
};
|
|
1567
1574
|
};
|
|
1568
1575
|
|
|
1569
1576
|
/**
|
|
1570
1577
|
* The return value of a remote `command` function. See [Remote functions](https://svelte.dev/docs/kit/remote-functions#command) for full documentation.
|
|
1571
1578
|
*/
|
|
1572
|
-
export type RemoteCommand<Input, Output> =
|
|
1573
|
-
|
|
1579
|
+
export type RemoteCommand<Input, Output> = {
|
|
1580
|
+
(arg: Input): Promise<Awaited<Output>> & {
|
|
1581
|
+
updates(...queries: Array<RemoteQuery<any> | RemoteQueryOverride>): Promise<Awaited<Output>>;
|
|
1582
|
+
};
|
|
1583
|
+
/** The number of pending command executions */
|
|
1584
|
+
get pending(): number;
|
|
1574
1585
|
};
|
|
1575
1586
|
|
|
1576
1587
|
export type RemoteResource<T> = Promise<Awaited<T>> & {
|
|
@@ -1600,7 +1611,7 @@ declare module '@sveltejs/kit' {
|
|
|
1600
1611
|
*/
|
|
1601
1612
|
refresh(): Promise<void>;
|
|
1602
1613
|
/**
|
|
1603
|
-
* Temporarily override the value of a query. This is used with the `updates` method of a [command](https://svelte.dev/docs/kit/remote-functions#command-
|
|
1614
|
+
* Temporarily override the value of a query. This is used with the `updates` method of a [command](https://svelte.dev/docs/kit/remote-functions#command-Updating-queries) or [enhanced form submission](https://svelte.dev/docs/kit/remote-functions#form-enhance) to provide optimistic updates.
|
|
1604
1615
|
*
|
|
1605
1616
|
* ```svelte
|
|
1606
1617
|
* <script>
|
package/types/index.d.ts.map
CHANGED
|
@@ -185,6 +185,6 @@
|
|
|
185
185
|
null,
|
|
186
186
|
null
|
|
187
187
|
],
|
|
188
|
-
"mappings": ";;;;;;;;;;kBAiCiBA,OAAOA;;;;;;;;;;;;;;;;;;;;;;;;;;;aA2BZC,cAAcA;;;;;;aAMdC,cAAcA;;;;;;;;MAQrBC,aAAaA;;;;;OAKJC,YAAYA;;kBAETC,aAAaA;;;;;;MAMzBC,qBAAqBA;;;;;;;;;;;kBAWTC,OAAOA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAqGPC,MAAMA;;;;;;;;;;;kBAWNC,OAAOA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA4DPC,QAAQA;;;;;;;;kBAQRC,SAASA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aA2edC,MAAMA;;;;;;;;;;;aAWNC,iBAAiBA;;;;;;;;;;;;aAYjBC,qBAAqBA;;;;;;;;;aASrBC,iBAAiBA;;;;;;;;;;aAUjBC,WAAWA;;;;;;;;;;aAUXC,UAAUA;;;;;;aAMVC,UAAUA;;;;;;aAMVC,OAAOA;;;;;;;;;;;;;;;;;;;;;;;;;;aA0BPC,SAASA;;;;;kBAKJC,WAAWA;;;;;;;;;;;;aAYhBC,IAAIA;;;;;;;;;;;;kBAYCC,SAASA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA4GTC,eAAeA;;;;;;;;;;;;;;;;;;;;;;;;;;kBA0BfC,gBAAgBA
|
|
188
|
+
"mappings": "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",
|
|
189
189
|
"ignoreList": []
|
|
190
190
|
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
/** @import { RemoteCommand, RemoteQueryOverride } from '@sveltejs/kit' */
|
|
2
|
-
/** @import { RemoteFunctionResponse } from 'types' */
|
|
3
|
-
/** @import { Query } from './query.svelte.js' */
|
|
4
|
-
import { app_dir, base } from '__sveltekit/paths';
|
|
5
|
-
import * as devalue from 'devalue';
|
|
6
|
-
import { HttpError } from '@sveltejs/kit/internal';
|
|
7
|
-
import { app } from '../client.js';
|
|
8
|
-
import { stringify_remote_arg } from '../../shared.js';
|
|
9
|
-
import { refresh_queries, release_overrides } from './shared.svelte.js';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Client-version of the `command` function from `$app/server`.
|
|
13
|
-
* @param {string} id
|
|
14
|
-
* @returns {RemoteCommand<any, any>}
|
|
15
|
-
*/
|
|
16
|
-
export function command(id) {
|
|
17
|
-
// Careful: This function MUST be synchronous (can't use the async keyword) because the return type has to be a promise with an updates() method.
|
|
18
|
-
// If we make it async, the return type will be a promise that resolves to a promise with an updates() method, which is not what we want.
|
|
19
|
-
return (arg) => {
|
|
20
|
-
/** @type {Array<Query<any> | RemoteQueryOverride>} */
|
|
21
|
-
let updates = [];
|
|
22
|
-
|
|
23
|
-
/** @type {Promise<any> & { updates: (...args: any[]) => any }} */
|
|
24
|
-
const promise = (async () => {
|
|
25
|
-
// Wait a tick to give room for the `updates` method to be called
|
|
26
|
-
await Promise.resolve();
|
|
27
|
-
|
|
28
|
-
const response = await fetch(`${base}/${app_dir}/remote/${id}`, {
|
|
29
|
-
method: 'POST',
|
|
30
|
-
body: JSON.stringify({
|
|
31
|
-
payload: stringify_remote_arg(arg, app.hooks.transport),
|
|
32
|
-
refreshes: updates.map((u) => u._key)
|
|
33
|
-
}),
|
|
34
|
-
headers: {
|
|
35
|
-
'Content-Type': 'application/json'
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
if (!response.ok) {
|
|
40
|
-
release_overrides(updates);
|
|
41
|
-
// We only end up here in case of a network error or if the server has an internal error
|
|
42
|
-
// (which shouldn't happen because we handle errors on the server and always send a 200 response)
|
|
43
|
-
throw new Error('Failed to execute remote function');
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const result = /** @type {RemoteFunctionResponse} */ (await response.json());
|
|
47
|
-
if (result.type === 'redirect') {
|
|
48
|
-
release_overrides(updates);
|
|
49
|
-
throw new Error(
|
|
50
|
-
'Redirects are not allowed in commands. Return a result instead and use goto on the client'
|
|
51
|
-
);
|
|
52
|
-
} else if (result.type === 'error') {
|
|
53
|
-
release_overrides(updates);
|
|
54
|
-
throw new HttpError(result.status ?? 500, result.error);
|
|
55
|
-
} else {
|
|
56
|
-
refresh_queries(result.refreshes, updates);
|
|
57
|
-
|
|
58
|
-
return devalue.parse(result.result, app.decoders);
|
|
59
|
-
}
|
|
60
|
-
})();
|
|
61
|
-
|
|
62
|
-
promise.updates = (/** @type {any} */ ...args) => {
|
|
63
|
-
updates = args;
|
|
64
|
-
// @ts-expect-error Don't allow updates to be called multiple times
|
|
65
|
-
delete promise.updates;
|
|
66
|
-
return promise;
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
return promise;
|
|
70
|
-
};
|
|
71
|
-
}
|