@sveltejs/kit 2.60.0 → 2.61.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 +10 -11
- package/src/core/postbuild/analyse.js +1 -3
- package/src/core/sync/create_manifest_data/conflict.js +72 -0
- package/src/core/sync/create_manifest_data/index.js +1 -65
- package/src/core/sync/write_non_ambient.js +2 -2
- package/src/core/sync/write_types/index.js +1 -1
- package/src/exports/public.d.ts +23 -30
- package/src/exports/vite/build/build_server.js +36 -13
- package/src/exports/vite/dev/index.js +4 -2
- package/src/exports/vite/index.js +18 -16
- package/src/runtime/app/server/index.js +1 -2
- package/src/runtime/app/server/remote/form.js +10 -0
- package/src/runtime/app/server/remote/query.js +111 -44
- package/src/runtime/client/client.js +13 -8
- package/src/runtime/client/remote-functions/cache.svelte.js +157 -0
- package/src/runtime/client/remote-functions/form.svelte.js +235 -196
- package/src/runtime/client/remote-functions/index.js +2 -2
- package/src/runtime/client/remote-functions/prerender.svelte.js +1 -2
- package/src/runtime/client/remote-functions/query/cache.js +4 -0
- package/src/runtime/client/remote-functions/query/index.js +48 -0
- package/src/runtime/client/remote-functions/query/instance.svelte.js +249 -0
- package/src/runtime/client/remote-functions/query/proxy.js +156 -0
- package/src/runtime/client/remote-functions/query-batch.svelte.js +1 -1
- package/src/runtime/client/remote-functions/query-live/cache.js +4 -0
- package/src/runtime/client/remote-functions/query-live/index.js +31 -0
- package/src/runtime/client/remote-functions/{query-live.svelte.js → query-live/instance.svelte.js} +61 -310
- package/src/runtime/client/remote-functions/query-live/iterator.js +91 -0
- package/src/runtime/client/remote-functions/query-live/proxy.js +144 -0
- package/src/runtime/client/remote-functions/shared.svelte.js +53 -6
- package/src/runtime/client/utils.js +1 -1
- package/src/runtime/form-utils.js +7 -16
- package/src/runtime/server/index.js +2 -3
- package/src/runtime/server/page/actions.js +2 -9
- package/src/runtime/server/page/csp.js +3 -4
- package/src/runtime/server/page/render.js +13 -14
- package/src/runtime/server/respond.js +61 -38
- package/src/runtime/server/utils.js +23 -3
- package/src/types/global-private.d.ts +5 -0
- package/src/types/internal.d.ts +45 -8
- package/src/utils/routing.js +3 -1
- package/src/utils/shared-iterator.js +213 -0
- package/src/version.js +1 -1
- package/types/index.d.ts +28 -32
- package/types/index.d.ts.map +1 -1
- package/src/runtime/client/remote-functions/query.svelte.js +0 -512
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { query_responses } from '../../client.js';
|
|
2
|
+
import { QUERY_OVERRIDE_KEY } from '../shared.svelte.js';
|
|
3
|
+
import { noop } from '../../../../utils/functions.js';
|
|
4
|
+
import { with_resolvers } from '../../../../utils/promise.js';
|
|
5
|
+
import { tick, untrack } from 'svelte';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The actual query instance. There should only ever be one active query instance per key.
|
|
9
|
+
*
|
|
10
|
+
* @template T
|
|
11
|
+
* @implements {Promise<T>}
|
|
12
|
+
*/
|
|
13
|
+
export class Query {
|
|
14
|
+
/** @type {string} */
|
|
15
|
+
#key;
|
|
16
|
+
|
|
17
|
+
/** @type {() => Promise<T>} */
|
|
18
|
+
#fn;
|
|
19
|
+
#loading = $state(true);
|
|
20
|
+
/** @type {Array<(value: undefined) => void>} */
|
|
21
|
+
#latest = [];
|
|
22
|
+
|
|
23
|
+
/** @type {boolean} */
|
|
24
|
+
#ready = $state(false);
|
|
25
|
+
/** @type {T | undefined} */
|
|
26
|
+
#raw = $state.raw();
|
|
27
|
+
/** @type {Promise<void> | null} */
|
|
28
|
+
#promise = $state.raw(null);
|
|
29
|
+
/** @type {Array<(old: T) => T>} */
|
|
30
|
+
#overrides = $state([]);
|
|
31
|
+
|
|
32
|
+
/** @type {T | undefined} */
|
|
33
|
+
#current = $derived.by(() => {
|
|
34
|
+
// don't reduce undefined value
|
|
35
|
+
if (!this.#ready) return undefined;
|
|
36
|
+
|
|
37
|
+
return this.#overrides.reduce((v, r) => r(v), /** @type {T} */ (this.#raw));
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
/** @type {any} */
|
|
41
|
+
#error = $state.raw(undefined);
|
|
42
|
+
|
|
43
|
+
/** @type {Promise<T>['then']} */
|
|
44
|
+
// @ts-expect-error TS doesn't understand that the promise returns something
|
|
45
|
+
#then = $derived.by(() => {
|
|
46
|
+
const p = this.#get_promise();
|
|
47
|
+
this.#overrides.length;
|
|
48
|
+
|
|
49
|
+
return (resolve, reject) => {
|
|
50
|
+
const result = p.then(tick).then(() => /** @type {T} */ (this.#current));
|
|
51
|
+
|
|
52
|
+
if (resolve || reject) {
|
|
53
|
+
return result.then(resolve, reject);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return result;
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @param {string} key
|
|
62
|
+
* @param {() => Promise<T>} fn
|
|
63
|
+
*/
|
|
64
|
+
constructor(key, fn) {
|
|
65
|
+
this.#key = key;
|
|
66
|
+
this.#fn = fn;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
#get_promise() {
|
|
70
|
+
void untrack(() => (this.#promise ??= this.#run()));
|
|
71
|
+
return /** @type {Promise<T>} */ (this.#promise);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
#start() {
|
|
75
|
+
// there is a really weird bug with untrack and writes and initializations
|
|
76
|
+
// every time you see this comment, try removing the `tick.then` here and see
|
|
77
|
+
// if all the tests still pass with the latest svelte version
|
|
78
|
+
// if they do, congrats, you can remove tick.then
|
|
79
|
+
void tick().then(() => this.#get_promise());
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
#clear_pending() {
|
|
83
|
+
this.#latest.forEach((r) => r(undefined));
|
|
84
|
+
this.#latest.length = 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
#run() {
|
|
88
|
+
this.#loading = true;
|
|
89
|
+
|
|
90
|
+
const { promise, resolve, reject } = with_resolvers();
|
|
91
|
+
|
|
92
|
+
this.#latest.push(resolve);
|
|
93
|
+
|
|
94
|
+
Promise.resolve(this.#fn())
|
|
95
|
+
.then((value) => {
|
|
96
|
+
// Skip the response if resource was refreshed with a later promise while we were waiting for this one to resolve
|
|
97
|
+
const idx = this.#latest.indexOf(resolve);
|
|
98
|
+
if (idx === -1) return;
|
|
99
|
+
|
|
100
|
+
// Untrack this to not trigger mutation validation errors which can occur if you do e.g. $derived({ a: await queryA(), b: await queryB() })
|
|
101
|
+
untrack(() => {
|
|
102
|
+
this.#latest.splice(0, idx).forEach((r) => r(undefined));
|
|
103
|
+
this.#ready = true;
|
|
104
|
+
this.#loading = false;
|
|
105
|
+
this.#raw = value;
|
|
106
|
+
this.#error = undefined;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
resolve(undefined);
|
|
110
|
+
})
|
|
111
|
+
.catch((e) => {
|
|
112
|
+
// TODO: Our behavior here could be better:
|
|
113
|
+
// - We should not reject on redirects, but should hook into the router
|
|
114
|
+
// to ensure the query is properly refreshed before the navigation completes
|
|
115
|
+
// - Instead of failing on transport-level errors, we should probably do what
|
|
116
|
+
// LiveQuery does and preserve the last known good value and retry the connection
|
|
117
|
+
const idx = this.#latest.indexOf(resolve);
|
|
118
|
+
if (idx === -1) return;
|
|
119
|
+
|
|
120
|
+
untrack(() => {
|
|
121
|
+
this.#latest.splice(0, idx).forEach((r) => r(undefined));
|
|
122
|
+
this.#error = e;
|
|
123
|
+
this.#loading = false;
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
reject(e);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return promise;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
get then() {
|
|
133
|
+
// TODO this should be unnecessary but due to the bug described
|
|
134
|
+
// in #start, we need to do this in some circumstances
|
|
135
|
+
this.#start();
|
|
136
|
+
return this.#then;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
get catch() {
|
|
140
|
+
this.#start();
|
|
141
|
+
this.#then;
|
|
142
|
+
return (/** @type {any} */ reject) => {
|
|
143
|
+
return this.#then(undefined, reject);
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
get finally() {
|
|
148
|
+
this.#start();
|
|
149
|
+
this.#then;
|
|
150
|
+
return (/** @type {any} */ fn) => {
|
|
151
|
+
return this.#then(
|
|
152
|
+
(value) => {
|
|
153
|
+
fn();
|
|
154
|
+
return value;
|
|
155
|
+
},
|
|
156
|
+
(error) => {
|
|
157
|
+
fn();
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
get current() {
|
|
165
|
+
this.#start();
|
|
166
|
+
return this.#current;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
get error() {
|
|
170
|
+
this.#start();
|
|
171
|
+
return this.#error;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Returns true if the resource is loading or reloading.
|
|
176
|
+
*/
|
|
177
|
+
get loading() {
|
|
178
|
+
this.#start();
|
|
179
|
+
return this.#loading;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Returns true once the resource has been loaded for the first time.
|
|
184
|
+
*/
|
|
185
|
+
get ready() {
|
|
186
|
+
this.#start();
|
|
187
|
+
return this.#ready;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* @returns {Promise<void>}
|
|
192
|
+
*/
|
|
193
|
+
refresh() {
|
|
194
|
+
delete query_responses[this.#key];
|
|
195
|
+
return (this.#promise = this.#run());
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* @param {T} value
|
|
200
|
+
*/
|
|
201
|
+
set(value) {
|
|
202
|
+
this.#clear_pending();
|
|
203
|
+
this.#ready = true;
|
|
204
|
+
this.#loading = false;
|
|
205
|
+
this.#error = undefined;
|
|
206
|
+
this.#raw = value;
|
|
207
|
+
this.#promise = Promise.resolve();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* @param {unknown} error
|
|
212
|
+
*/
|
|
213
|
+
fail(error) {
|
|
214
|
+
this.#clear_pending();
|
|
215
|
+
this.#loading = false;
|
|
216
|
+
this.#error = error;
|
|
217
|
+
|
|
218
|
+
const promise = Promise.reject(error);
|
|
219
|
+
|
|
220
|
+
promise.catch(noop);
|
|
221
|
+
this.#promise = promise;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* @param {(old: T) => T} fn
|
|
226
|
+
* @returns {(() => void) & { [QUERY_OVERRIDE_KEY]: string }}
|
|
227
|
+
*/
|
|
228
|
+
withOverride(fn) {
|
|
229
|
+
this.#overrides.push(fn);
|
|
230
|
+
|
|
231
|
+
const release = /** @type {(() => void) & { [QUERY_OVERRIDE_KEY]: string }} */ (
|
|
232
|
+
() => {
|
|
233
|
+
const i = this.#overrides.indexOf(fn);
|
|
234
|
+
|
|
235
|
+
if (i !== -1) {
|
|
236
|
+
this.#overrides.splice(i, 1);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
Object.defineProperty(release, QUERY_OVERRIDE_KEY, { value: this.#key });
|
|
242
|
+
|
|
243
|
+
return release;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
get [Symbol.toStringTag]() {
|
|
247
|
+
return 'Query';
|
|
248
|
+
}
|
|
249
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { app, query_map } from '../../client.js';
|
|
2
|
+
import {
|
|
3
|
+
pin_in_effect,
|
|
4
|
+
pin_while_resolving,
|
|
5
|
+
QUERY_OVERRIDE_KEY,
|
|
6
|
+
QUERY_RESOURCE_KEY
|
|
7
|
+
} from '../shared.svelte.js';
|
|
8
|
+
import { create_remote_key, stringify_remote_arg } from '../../../shared.js';
|
|
9
|
+
import { Query } from './instance.svelte.js';
|
|
10
|
+
import { cache } from './cache.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Manages the caching layer between the user and the actual {@link Query} instance. This is the thing
|
|
14
|
+
* the developer actually gets to interact with in their application code.
|
|
15
|
+
*
|
|
16
|
+
* @template T
|
|
17
|
+
* @implements {Promise<T>}
|
|
18
|
+
*/
|
|
19
|
+
export class QueryProxy {
|
|
20
|
+
#id;
|
|
21
|
+
#key;
|
|
22
|
+
#payload;
|
|
23
|
+
#fn;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {string} id
|
|
27
|
+
* @param {any} arg
|
|
28
|
+
* @param {(key: string, payload: string) => Promise<T>} fn
|
|
29
|
+
*/
|
|
30
|
+
constructor(id, arg, fn) {
|
|
31
|
+
this.#id = id;
|
|
32
|
+
this.#payload = stringify_remote_arg(arg, app.hooks.transport);
|
|
33
|
+
this.#key = create_remote_key(id, this.#payload);
|
|
34
|
+
Object.defineProperty(this, QUERY_RESOURCE_KEY, { value: this.#key });
|
|
35
|
+
this.#fn = fn;
|
|
36
|
+
|
|
37
|
+
const key = this.#key;
|
|
38
|
+
const payload = this.#payload;
|
|
39
|
+
const entry = cache.ensure_entry(
|
|
40
|
+
this.#id,
|
|
41
|
+
this.#payload,
|
|
42
|
+
// IMPORTANT: This cannot close over `this` or it becomes impossible to
|
|
43
|
+
// garbage collect the QueryProxy and thus impossible to evict cache entries.
|
|
44
|
+
() => new Query(key, () => fn(key, payload))
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
cache.ref(this, entry, this.#id, this.#payload);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
#get_cached_query() {
|
|
51
|
+
const cached = query_map.get(this.#id)?.get(this.#payload);
|
|
52
|
+
|
|
53
|
+
if (!cached) {
|
|
54
|
+
// Sanity check: a live proxy should always keep its cache entry alive via
|
|
55
|
+
// `proxy_count`, and the invalidation paths never locally evict entries.
|
|
56
|
+
throw new Error(
|
|
57
|
+
'No cached query found. This should be impossible. Please file a bug report.'
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return cached.resource;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
get current() {
|
|
65
|
+
return this.#get_cached_query().current;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
get error() {
|
|
69
|
+
return this.#get_cached_query().error;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
get loading() {
|
|
73
|
+
return this.#get_cached_query().loading;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
get ready() {
|
|
77
|
+
return this.#get_cached_query().ready;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
refresh() {
|
|
81
|
+
return this.#get_cached_query().refresh();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** @type {Query<T>['set']} */
|
|
85
|
+
set(value) {
|
|
86
|
+
this.#get_cached_query().set(value);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** @type {Query<T>['withOverride']} */
|
|
90
|
+
withOverride(fn) {
|
|
91
|
+
const fn_ref = this.#fn;
|
|
92
|
+
const key_ref = this.#key;
|
|
93
|
+
const payload_ref = this.#payload;
|
|
94
|
+
// The override increments `proxy_count` to keep the cache entry alive until the
|
|
95
|
+
// release function is called.
|
|
96
|
+
const entry = cache.ensure_entry(
|
|
97
|
+
this.#id,
|
|
98
|
+
this.#payload,
|
|
99
|
+
// IMPORTANT: This cannot close over `this` or it becomes impossible to
|
|
100
|
+
// garbage collect the QueryProxy and thus impossible to evict cache entries.
|
|
101
|
+
() => new Query(key_ref, () => fn_ref(key_ref, payload_ref))
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const deref = cache.manual_ref(entry, this.#id, this.#payload);
|
|
105
|
+
|
|
106
|
+
const override = entry.resource.withOverride(fn);
|
|
107
|
+
|
|
108
|
+
const release = /** @type {(() => void) & { [QUERY_OVERRIDE_KEY]: string }} */ (
|
|
109
|
+
() => {
|
|
110
|
+
override();
|
|
111
|
+
deref();
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
Object.defineProperty(release, QUERY_OVERRIDE_KEY, { value: override[QUERY_OVERRIDE_KEY] });
|
|
116
|
+
|
|
117
|
+
return release;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** @type {Query<T>['then']} */
|
|
121
|
+
get then() {
|
|
122
|
+
pin_in_effect(query_map, cache, this.#id, this.#payload);
|
|
123
|
+
const cached = this.#get_cached_query();
|
|
124
|
+
return pin_while_resolving(query_map, cache, this.#id, this.#payload, cached.then.bind(cached));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** @type {Query<T>['catch']} */
|
|
128
|
+
get catch() {
|
|
129
|
+
pin_in_effect(query_map, cache, this.#id, this.#payload);
|
|
130
|
+
const cached = this.#get_cached_query();
|
|
131
|
+
return pin_while_resolving(
|
|
132
|
+
query_map,
|
|
133
|
+
cache,
|
|
134
|
+
this.#id,
|
|
135
|
+
this.#payload,
|
|
136
|
+
cached.catch.bind(cached)
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** @type {Query<T>['finally']} */
|
|
141
|
+
get finally() {
|
|
142
|
+
pin_in_effect(query_map, cache, this.#id, this.#payload);
|
|
143
|
+
const cached = this.#get_cached_query();
|
|
144
|
+
return pin_while_resolving(
|
|
145
|
+
query_map,
|
|
146
|
+
cache,
|
|
147
|
+
this.#id,
|
|
148
|
+
this.#payload,
|
|
149
|
+
cached.finally.bind(cached)
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
get [Symbol.toStringTag]() {
|
|
154
|
+
return 'QueryProxy';
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { app_dir, base } from '$app/paths/internal/client';
|
|
4
4
|
import { app, goto } from '../client.js';
|
|
5
5
|
import { get_remote_request_headers, QUERY_FUNCTION_ID } from './shared.svelte.js';
|
|
6
|
-
import { QueryProxy } from './query.
|
|
6
|
+
import { QueryProxy } from './query/proxy.js';
|
|
7
7
|
import * as devalue from 'devalue';
|
|
8
8
|
import { HttpError, Redirect } from '@sveltejs/kit/internal';
|
|
9
9
|
import { unfriendly_hydratable } from '../../shared.js';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** @import { RemoteLiveQuery, RemoteLiveQueryFunction } from '@sveltejs/kit' */
|
|
2
|
+
import { live_query_map } from '../../client.js';
|
|
3
|
+
import { QUERY_FUNCTION_ID } from '../shared.svelte.js';
|
|
4
|
+
import { DEV } from 'esm-env';
|
|
5
|
+
import { LiveQueryProxy } from './proxy.js';
|
|
6
|
+
|
|
7
|
+
export { LiveQueryProxy };
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {string} id
|
|
11
|
+
* @returns {RemoteLiveQueryFunction<any, any>}
|
|
12
|
+
*/
|
|
13
|
+
export function query_live(id) {
|
|
14
|
+
if (DEV) {
|
|
15
|
+
// If this reruns as part of HMR, reconnect all live entries.
|
|
16
|
+
const entries = live_query_map.get(id);
|
|
17
|
+
|
|
18
|
+
if (entries) {
|
|
19
|
+
for (const { resource } of entries.values()) {
|
|
20
|
+
void resource.reconnect();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** @type {RemoteLiveQueryFunction<any, any>} */
|
|
26
|
+
const wrapper = (arg) => /** @type {RemoteLiveQuery<any>} */ (new LiveQueryProxy(id, arg));
|
|
27
|
+
|
|
28
|
+
Object.defineProperty(wrapper, QUERY_FUNCTION_ID, { value: id });
|
|
29
|
+
|
|
30
|
+
return wrapper;
|
|
31
|
+
}
|