@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.
Files changed (45) hide show
  1. package/package.json +10 -11
  2. package/src/core/postbuild/analyse.js +1 -3
  3. package/src/core/sync/create_manifest_data/conflict.js +72 -0
  4. package/src/core/sync/create_manifest_data/index.js +1 -65
  5. package/src/core/sync/write_non_ambient.js +2 -2
  6. package/src/core/sync/write_types/index.js +1 -1
  7. package/src/exports/public.d.ts +23 -30
  8. package/src/exports/vite/build/build_server.js +36 -13
  9. package/src/exports/vite/dev/index.js +4 -2
  10. package/src/exports/vite/index.js +18 -16
  11. package/src/runtime/app/server/index.js +1 -2
  12. package/src/runtime/app/server/remote/form.js +10 -0
  13. package/src/runtime/app/server/remote/query.js +111 -44
  14. package/src/runtime/client/client.js +13 -8
  15. package/src/runtime/client/remote-functions/cache.svelte.js +157 -0
  16. package/src/runtime/client/remote-functions/form.svelte.js +235 -196
  17. package/src/runtime/client/remote-functions/index.js +2 -2
  18. package/src/runtime/client/remote-functions/prerender.svelte.js +1 -2
  19. package/src/runtime/client/remote-functions/query/cache.js +4 -0
  20. package/src/runtime/client/remote-functions/query/index.js +48 -0
  21. package/src/runtime/client/remote-functions/query/instance.svelte.js +249 -0
  22. package/src/runtime/client/remote-functions/query/proxy.js +156 -0
  23. package/src/runtime/client/remote-functions/query-batch.svelte.js +1 -1
  24. package/src/runtime/client/remote-functions/query-live/cache.js +4 -0
  25. package/src/runtime/client/remote-functions/query-live/index.js +31 -0
  26. package/src/runtime/client/remote-functions/{query-live.svelte.js → query-live/instance.svelte.js} +61 -310
  27. package/src/runtime/client/remote-functions/query-live/iterator.js +91 -0
  28. package/src/runtime/client/remote-functions/query-live/proxy.js +144 -0
  29. package/src/runtime/client/remote-functions/shared.svelte.js +53 -6
  30. package/src/runtime/client/utils.js +1 -1
  31. package/src/runtime/form-utils.js +7 -16
  32. package/src/runtime/server/index.js +2 -3
  33. package/src/runtime/server/page/actions.js +2 -9
  34. package/src/runtime/server/page/csp.js +3 -4
  35. package/src/runtime/server/page/render.js +13 -14
  36. package/src/runtime/server/respond.js +61 -38
  37. package/src/runtime/server/utils.js +23 -3
  38. package/src/types/global-private.d.ts +5 -0
  39. package/src/types/internal.d.ts +45 -8
  40. package/src/utils/routing.js +3 -1
  41. package/src/utils/shared-iterator.js +213 -0
  42. package/src/version.js +1 -1
  43. package/types/index.d.ts +28 -32
  44. package/types/index.d.ts.map +1 -1
  45. 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.svelte.js';
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,4 @@
1
+ import { live_query_map } from '../../client.js';
2
+ import { CacheController } from '../cache.svelte.js';
3
+
4
+ export const cache = new CacheController(live_query_map, (resource) => resource.destroy());
@@ -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
+ }