@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
@@ -1,512 +0,0 @@
1
- /** @import { RemoteQueryFunction } from '@sveltejs/kit' */
2
- import { app_dir, base } from '$app/paths/internal/client';
3
- import { app, query_map, query_responses } from '../client.js';
4
- import {
5
- get_remote_request_headers,
6
- is_in_effect,
7
- QUERY_FUNCTION_ID,
8
- QUERY_OVERRIDE_KEY,
9
- QUERY_RESOURCE_KEY,
10
- remote_request
11
- } from './shared.svelte.js';
12
- import * as devalue from 'devalue';
13
- import { DEV } from 'esm-env';
14
- import { noop } from '../../../utils/functions.js';
15
- import { with_resolvers } from '../../../utils/promise.js';
16
- import { tick, untrack } from 'svelte';
17
- import { create_remote_key, stringify_remote_arg, unfriendly_hydratable } from '../../shared.js';
18
-
19
- /**
20
- * @template T
21
- * @typedef {{
22
- * count: number;
23
- * resource: Query<T>;
24
- * cleanup: () => void;
25
- * }} RemoteQueryCacheEntry
26
- */
27
-
28
- /**
29
- * @param {string} id
30
- * @returns {RemoteQueryFunction<any, any>}
31
- */
32
- export function query(id) {
33
- if (DEV) {
34
- // If this reruns as part of HMR, refresh the query
35
- const entries = query_map.get(id);
36
-
37
- if (entries) {
38
- for (const entry of entries.values()) {
39
- void entry.resource.refresh();
40
- }
41
- }
42
- }
43
-
44
- /** @type {RemoteQueryFunction<any, any>} */
45
- const wrapper = (arg) => {
46
- return new QueryProxy(id, arg, async (key, payload) => {
47
- const url = `${base}/${app_dir}/remote/${id}${payload ? `?payload=${payload}` : ''}`;
48
-
49
- const serialized = await unfriendly_hydratable(key, () =>
50
- remote_request(url, get_remote_request_headers())
51
- );
52
-
53
- return devalue.parse(serialized, app.decoders);
54
- });
55
- };
56
-
57
- Object.defineProperty(wrapper, QUERY_FUNCTION_ID, { value: id });
58
-
59
- return wrapper;
60
- }
61
-
62
- /**
63
- * The actual query instance. There should only ever be one active query instance per key.
64
- *
65
- * @template T
66
- * @implements {Promise<T>}
67
- */
68
- export class Query {
69
- /** @type {string} */
70
- #key;
71
-
72
- /** @type {() => Promise<T>} */
73
- #fn;
74
- #loading = $state(true);
75
- /** @type {Array<(value: undefined) => void>} */
76
- #latest = [];
77
-
78
- /** @type {boolean} */
79
- #ready = $state(false);
80
- /** @type {T | undefined} */
81
- #raw = $state.raw();
82
- /** @type {Promise<void> | null} */
83
- #promise = $state.raw(null);
84
- /** @type {Array<(old: T) => T>} */
85
- #overrides = $state([]);
86
-
87
- /** @type {T | undefined} */
88
- #current = $derived.by(() => {
89
- // don't reduce undefined value
90
- if (!this.#ready) return undefined;
91
-
92
- return this.#overrides.reduce((v, r) => r(v), /** @type {T} */ (this.#raw));
93
- });
94
-
95
- /** @type {any} */
96
- #error = $state.raw(undefined);
97
-
98
- /** @type {Promise<T>['then']} */
99
- // @ts-expect-error TS doesn't understand that the promise returns something
100
- #then = $derived.by(() => {
101
- const p = this.#get_promise();
102
- this.#overrides.length;
103
-
104
- return (resolve, reject) => {
105
- const result = p.then(tick).then(() => /** @type {T} */ (this.#current));
106
-
107
- if (resolve || reject) {
108
- return result.then(resolve, reject);
109
- }
110
-
111
- return result;
112
- };
113
- });
114
-
115
- /**
116
- * @param {string} key
117
- * @param {() => Promise<T>} fn
118
- */
119
- constructor(key, fn) {
120
- this.#key = key;
121
- this.#fn = fn;
122
- }
123
-
124
- #get_promise() {
125
- void untrack(() => (this.#promise ??= this.#run()));
126
- return /** @type {Promise<T>} */ (this.#promise);
127
- }
128
-
129
- #start() {
130
- // there is a really weird bug with untrack and writes and initializations
131
- // every time you see this comment, try removing the `tick.then` here and see
132
- // if all the tests still pass with the latest svelte version
133
- // if they do, congrats, you can remove tick.then
134
- void tick().then(() => this.#get_promise());
135
- }
136
-
137
- #clear_pending() {
138
- this.#latest.forEach((r) => r(undefined));
139
- this.#latest.length = 0;
140
- }
141
-
142
- #run() {
143
- this.#loading = true;
144
-
145
- const { promise, resolve, reject } = with_resolvers();
146
-
147
- this.#latest.push(resolve);
148
-
149
- Promise.resolve(this.#fn())
150
- .then((value) => {
151
- // Skip the response if resource was refreshed with a later promise while we were waiting for this one to resolve
152
- const idx = this.#latest.indexOf(resolve);
153
- if (idx === -1) return;
154
-
155
- // Untrack this to not trigger mutation validation errors which can occur if you do e.g. $derived({ a: await queryA(), b: await queryB() })
156
- untrack(() => {
157
- this.#latest.splice(0, idx).forEach((r) => r(undefined));
158
- this.#ready = true;
159
- this.#loading = false;
160
- this.#raw = value;
161
- this.#error = undefined;
162
- });
163
-
164
- resolve(undefined);
165
- })
166
- .catch((e) => {
167
- // TODO: Our behavior here could be better:
168
- // - We should not reject on redirects, but should hook into the router
169
- // to ensure the query is properly refreshed before the navigation completes
170
- // - Instead of failing on transport-level errors, we should probably do what
171
- // LiveQuery does and preserve the last known good value and retry the connection
172
- const idx = this.#latest.indexOf(resolve);
173
- if (idx === -1) return;
174
-
175
- untrack(() => {
176
- this.#latest.splice(0, idx).forEach((r) => r(undefined));
177
- this.#error = e;
178
- this.#loading = false;
179
- });
180
-
181
- reject(e);
182
- });
183
-
184
- return promise;
185
- }
186
-
187
- get then() {
188
- // TODO this should be unnecessary but due to the bug described
189
- // in #start, we need to do this in some circumstances
190
- this.#start();
191
- return this.#then;
192
- }
193
-
194
- get catch() {
195
- this.#start();
196
- this.#then;
197
- return (/** @type {any} */ reject) => {
198
- return this.#then(undefined, reject);
199
- };
200
- }
201
-
202
- get finally() {
203
- this.#start();
204
- this.#then;
205
- return (/** @type {any} */ fn) => {
206
- return this.#then(
207
- (value) => {
208
- fn();
209
- return value;
210
- },
211
- (error) => {
212
- fn();
213
- throw error;
214
- }
215
- );
216
- };
217
- }
218
-
219
- get current() {
220
- this.#start();
221
- return this.#current;
222
- }
223
-
224
- get error() {
225
- this.#start();
226
- return this.#error;
227
- }
228
-
229
- /**
230
- * Returns true if the resource is loading or reloading.
231
- */
232
- get loading() {
233
- this.#start();
234
- return this.#loading;
235
- }
236
-
237
- /**
238
- * Returns true once the resource has been loaded for the first time.
239
- */
240
- get ready() {
241
- this.#start();
242
- return this.#ready;
243
- }
244
-
245
- /**
246
- * @returns {Promise<void>}
247
- */
248
- refresh() {
249
- delete query_responses[this.#key];
250
- return (this.#promise = this.#run());
251
- }
252
-
253
- /**
254
- * @param {T} value
255
- */
256
- set(value) {
257
- this.#clear_pending();
258
- this.#ready = true;
259
- this.#loading = false;
260
- this.#error = undefined;
261
- this.#raw = value;
262
- this.#promise = Promise.resolve();
263
- }
264
-
265
- /**
266
- * @param {unknown} error
267
- */
268
- fail(error) {
269
- this.#clear_pending();
270
- this.#loading = false;
271
- this.#error = error;
272
-
273
- const promise = Promise.reject(error);
274
-
275
- promise.catch(noop);
276
- this.#promise = promise;
277
- }
278
-
279
- /**
280
- * @param {(old: T) => T} fn
281
- * @returns {(() => void) & { [QUERY_OVERRIDE_KEY]: string }}
282
- */
283
- withOverride(fn) {
284
- this.#overrides.push(fn);
285
-
286
- const release = /** @type {(() => void) & { [QUERY_OVERRIDE_KEY]: string }} */ (
287
- () => {
288
- const i = this.#overrides.indexOf(fn);
289
-
290
- if (i !== -1) {
291
- this.#overrides.splice(i, 1);
292
- }
293
- }
294
- );
295
-
296
- Object.defineProperty(release, QUERY_OVERRIDE_KEY, { value: this.#key });
297
-
298
- return release;
299
- }
300
-
301
- get [Symbol.toStringTag]() {
302
- return 'Query';
303
- }
304
- }
305
-
306
- /**
307
- * Manages the caching layer between the user and the actual {@link Query} instance. This is the thing
308
- * the developer actually gets to interact with in their application code.
309
- *
310
- * @template T
311
- * @implements {Promise<T>}
312
- */
313
- export class QueryProxy {
314
- #id;
315
- #key;
316
- #payload;
317
- #fn;
318
- #active = true;
319
- /**
320
- * Whether this proxy was created in a tracking context.
321
- * @readonly
322
- */
323
- #tracking = is_in_effect();
324
-
325
- /**
326
- * @param {string} id
327
- * @param {any} arg
328
- * @param {(key: string, payload: string) => Promise<T>} fn
329
- */
330
- constructor(id, arg, fn) {
331
- this.#id = id;
332
- this.#payload = stringify_remote_arg(arg, app.hooks.transport);
333
- this.#key = create_remote_key(id, this.#payload);
334
- Object.defineProperty(this, QUERY_RESOURCE_KEY, { value: this.#key });
335
- this.#fn = fn;
336
-
337
- if (!this.#tracking) {
338
- this.#active = false;
339
- return;
340
- }
341
-
342
- const entry = this.#get_or_create_cache_entry();
343
-
344
- $effect.pre(() => () => {
345
- const die = this.#release(entry);
346
- void tick().then(die);
347
- });
348
- }
349
-
350
- /** @returns {RemoteQueryCacheEntry<T>} */
351
- #get_or_create_cache_entry() {
352
- let query_instances = query_map.get(this.#id);
353
-
354
- if (!query_instances) {
355
- query_instances = new Map();
356
- query_map.set(this.#id, query_instances);
357
- }
358
-
359
- let this_instance = query_instances.get(this.#payload);
360
-
361
- if (!this_instance) {
362
- const c = (this_instance = {
363
- count: 0,
364
- resource: /** @type {Query<T>} */ (/** @type {unknown} */ (null)),
365
- cleanup: /** @type {() => void} */ (/** @type {unknown} */ (null))
366
- });
367
-
368
- c.cleanup = $effect.root(() => {
369
- c.resource = new Query(this.#key, () => this.#fn(this.#key, this.#payload));
370
- });
371
-
372
- query_instances.set(this.#payload, this_instance);
373
- }
374
-
375
- this_instance.count += 1;
376
-
377
- return this_instance;
378
- }
379
-
380
- /**
381
- * @param {RemoteQueryCacheEntry<T>} entry
382
- * @param {boolean} [deactivate]
383
- * @returns
384
- */
385
- #release(entry, deactivate = true) {
386
- this.#active &&= !deactivate;
387
- entry.count -= 1;
388
-
389
- return () => {
390
- const query_instances = query_map.get(this.#id);
391
- const this_instance = query_instances?.get(this.#payload);
392
-
393
- if (this_instance?.count === 0) {
394
- this_instance.cleanup();
395
- query_instances?.delete(this.#payload);
396
- }
397
- if (query_instances?.size === 0) {
398
- query_map.delete(this.#id);
399
- }
400
- };
401
- }
402
-
403
- #safe_get_cached_query() {
404
- return query_map.get(this.#id)?.get(this.#payload)?.resource;
405
- }
406
-
407
- get current() {
408
- return this.#safe_get_cached_query()?.current;
409
- }
410
-
411
- get error() {
412
- return this.#safe_get_cached_query()?.error;
413
- }
414
-
415
- get loading() {
416
- return this.#safe_get_cached_query()?.loading ?? false;
417
- }
418
-
419
- get ready() {
420
- return this.#safe_get_cached_query()?.ready ?? false;
421
- }
422
-
423
- run() {
424
- if (is_in_effect()) {
425
- throw new Error(
426
- 'On the client, .run() can only be called outside render, e.g. in universal `load` functions and event handlers. In render, await the query directly'
427
- );
428
- }
429
-
430
- if (Object.hasOwn(query_responses, this.#key)) {
431
- return Promise.resolve(query_responses[this.#key]);
432
- }
433
- return this.#fn(this.#key, this.#payload);
434
- }
435
-
436
- refresh() {
437
- return this.#safe_get_cached_query()?.refresh() ?? Promise.resolve();
438
- }
439
-
440
- /** @type {Query<T>['set']} */
441
- set(value) {
442
- this.#safe_get_cached_query()?.set(value);
443
- }
444
-
445
- /** @type {Query<T>['withOverride']} */
446
- withOverride(fn) {
447
- const entry = this.#get_or_create_cache_entry();
448
- const override = /** @type {Query<T>} */ (entry.resource).withOverride(fn);
449
-
450
- const release = /** @type {(() => void) & { [QUERY_OVERRIDE_KEY]: string }} */ (
451
- () => {
452
- override();
453
- this.#release(entry, false)();
454
- }
455
- );
456
-
457
- Object.defineProperty(release, QUERY_OVERRIDE_KEY, { value: override[QUERY_OVERRIDE_KEY] });
458
-
459
- return release;
460
- }
461
-
462
- #get_cached_query() {
463
- // TODO iterate on error messages
464
- if (!this.#tracking) {
465
- throw new Error(
466
- 'This query was not created in a reactive context and cannot be awaited. Use `.run()` to execute the query instead.'
467
- );
468
- }
469
-
470
- if (!this.#active) {
471
- throw new Error(
472
- 'This query instance is no longer active and can no longer be awaited. ' +
473
- 'This typically means you created the query in a tracking context and stashed it somewhere outside of a tracking context.'
474
- );
475
- }
476
-
477
- const cached = query_map.get(this.#id)?.get(this.#payload);
478
-
479
- if (!cached) {
480
- // The only case where `this.#active` can be `true` is when we've added an entry to `query_map`, and the
481
- // only way that entry can get removed is if this instance (and all others) have been deactivated.
482
- // So if we get here, someone (us, check git blame and point fingers) did `entry.count -= 1` improperly.
483
- throw new Error(
484
- 'No cached query found. This should be impossible. Please file a bug report.'
485
- );
486
- }
487
-
488
- return cached.resource;
489
- }
490
-
491
- /** @type {Query<T>['then']} */
492
- get then() {
493
- const cached = this.#get_cached_query();
494
- return cached.then.bind(cached);
495
- }
496
-
497
- /** @type {Query<T>['catch']} */
498
- get catch() {
499
- const cached = this.#get_cached_query();
500
- return cached.catch.bind(cached);
501
- }
502
-
503
- /** @type {Query<T>['finally']} */
504
- get finally() {
505
- const cached = this.#get_cached_query();
506
- return cached.finally.bind(cached);
507
- }
508
-
509
- get [Symbol.toStringTag]() {
510
- return 'QueryProxy';
511
- }
512
- }