@sveltejs/kit 3.0.0-next.1 → 3.0.0-next.3
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/analyse.js +0 -8
- package/src/core/postbuild/prerender.js +2 -0
- package/src/core/sync/create_manifest_data/index.js +24 -1
- package/src/core/sync/write_env.js +2 -1
- package/src/exports/internal/env.js +1 -1
- package/src/exports/public.d.ts +1 -1
- package/src/exports/vite/build/build_server.js +47 -58
- package/src/exports/vite/build/remote.js +18 -11
- package/src/exports/vite/build/utils.js +0 -8
- package/src/exports/vite/index.js +221 -217
- package/src/exports/vite/static_analysis/index.js +2 -4
- package/src/exports/vite/static_analysis/types.d.ts +14 -0
- package/src/exports/vite/utils.js +1 -12
- package/src/runtime/app/server/remote/command.js +0 -3
- package/src/runtime/app/server/remote/form.js +18 -13
- package/src/runtime/app/server/remote/prerender.js +28 -34
- package/src/runtime/app/server/remote/query.js +105 -94
- package/src/runtime/app/server/remote/requested.js +14 -10
- package/src/runtime/app/server/remote/shared.js +25 -19
- package/src/runtime/client/client.js +19 -13
- package/src/runtime/client/ndjson.js +6 -33
- package/src/runtime/client/remote-functions/command.svelte.js +7 -32
- package/src/runtime/client/remote-functions/form.svelte.js +62 -82
- package/src/runtime/client/remote-functions/prerender.svelte.js +14 -6
- package/src/runtime/client/remote-functions/query/index.js +6 -14
- package/src/runtime/client/remote-functions/query/instance.svelte.js +20 -0
- package/src/runtime/client/remote-functions/query/proxy.js +3 -3
- package/src/runtime/client/remote-functions/query-batch.svelte.js +59 -68
- package/src/runtime/client/remote-functions/query-live/instance.svelte.js +21 -6
- package/src/runtime/client/remote-functions/query-live/iterator.js +36 -55
- package/src/runtime/client/remote-functions/shared.svelte.js +76 -59
- package/src/runtime/client/sse.js +32 -0
- package/src/runtime/client/stream.js +38 -0
- package/src/runtime/server/page/render.js +23 -80
- package/src/runtime/server/page/server_routing.js +20 -15
- package/src/runtime/server/remote.js +296 -204
- package/src/runtime/server/respond.js +4 -2
- package/src/runtime/shared.js +83 -13
- package/src/types/global-private.d.ts +3 -3
- package/src/types/internal.d.ts +54 -35
- package/src/utils/error.js +12 -0
- package/src/version.js +1 -1
- package/types/index.d.ts +17 -7
- package/types/index.d.ts.map +5 -3
|
@@ -2,6 +2,7 @@ import path from 'node:path';
|
|
|
2
2
|
import { posixify } from '../../utils/os.js';
|
|
3
3
|
import { negotiate } from '../../utils/http.js';
|
|
4
4
|
import { escape_html } from '../../utils/escape.js';
|
|
5
|
+
import { stackless } from '../../utils/error.js';
|
|
5
6
|
import { dedent } from '../../core/sync/utils.js';
|
|
6
7
|
import {
|
|
7
8
|
app_server,
|
|
@@ -148,18 +149,6 @@ export function normalize_id(id, lib, cwd) {
|
|
|
148
149
|
return posixify(id);
|
|
149
150
|
}
|
|
150
151
|
|
|
151
|
-
/**
|
|
152
|
-
* For times when you need to throw an error, but without
|
|
153
|
-
* displaying a useless stack trace (since the developer
|
|
154
|
-
* can't do anything useful with it)
|
|
155
|
-
* @param {string} message
|
|
156
|
-
*/
|
|
157
|
-
export function stackless(message) {
|
|
158
|
-
const error = new Error(message);
|
|
159
|
-
error.stack = '';
|
|
160
|
-
return error;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
152
|
export const strip_virtual_prefix = /** @param {string} id */ (id) => id.replace('\0virtual:', '');
|
|
164
153
|
|
|
165
154
|
/**
|
|
@@ -77,9 +77,6 @@ export function command(validate_or_fn, maybe_fn) {
|
|
|
77
77
|
);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
state.remote.refreshes ??= new Map();
|
|
81
|
-
state.remote.reconnects ??= new Map();
|
|
82
|
-
|
|
83
80
|
const promise = Promise.resolve(
|
|
84
81
|
run_remote_function(event, state, true, () => validate(arg), fn)
|
|
85
82
|
);
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
normalize_issue,
|
|
10
10
|
flatten_issues
|
|
11
11
|
} from '../../../form-utils.js';
|
|
12
|
-
import { get_cache, run_remote_function } from './shared.js';
|
|
12
|
+
import { get_cache, get_implicit_lookup, run_remote_function } from './shared.js';
|
|
13
13
|
import { ValidationError } from '@sveltejs/kit/internal';
|
|
14
14
|
|
|
15
15
|
/**
|
|
@@ -108,9 +108,6 @@ export function form(validate_or_fn, maybe_fn) {
|
|
|
108
108
|
data = validated.value;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
state.remote.refreshes ??= new Map();
|
|
112
|
-
state.remote.reconnects ??= new Map();
|
|
113
|
-
|
|
114
111
|
const issue = create_issues();
|
|
115
112
|
|
|
116
113
|
try {
|
|
@@ -133,7 +130,12 @@ export function form(validate_or_fn, maybe_fn) {
|
|
|
133
130
|
// We don't need to care about args or deduplicating calls, because uneval results are only relevant in full page reloads
|
|
134
131
|
// where only one form submission is active at the same time
|
|
135
132
|
if (!event.isRemoteRequest) {
|
|
136
|
-
get_cache(__, state)
|
|
133
|
+
const cache = get_cache(__, state);
|
|
134
|
+
cache[''] ??= output;
|
|
135
|
+
|
|
136
|
+
// register under the client-side action id so the output is serialized
|
|
137
|
+
// into the page, allowing the hydrated client to restore `result`/`issues`/`input`
|
|
138
|
+
get_implicit_lookup(__, state)[__.action_id ?? __.id] = () => cache[''];
|
|
137
139
|
}
|
|
138
140
|
|
|
139
141
|
return output;
|
|
@@ -149,28 +151,30 @@ export function form(validate_or_fn, maybe_fn) {
|
|
|
149
151
|
|
|
150
152
|
Object.defineProperty(instance, 'fields', {
|
|
151
153
|
get() {
|
|
154
|
+
// the form instance is created once per module and shared across requests,
|
|
155
|
+
// so the current request's state has to be resolved at access time
|
|
152
156
|
return create_field_proxy(
|
|
153
157
|
{},
|
|
154
|
-
() => get_cache(__)?.['']?.
|
|
158
|
+
() => get_cache(__, get_request_store().state)?.['']?.input ?? {},
|
|
155
159
|
(path, value) => {
|
|
156
|
-
const cache = get_cache(__);
|
|
160
|
+
const cache = get_cache(__, get_request_store().state);
|
|
157
161
|
const entry = cache[''];
|
|
158
162
|
|
|
159
|
-
if (entry?.
|
|
163
|
+
if (entry?.submission) {
|
|
160
164
|
// don't override a submission
|
|
161
165
|
return;
|
|
162
166
|
}
|
|
163
167
|
|
|
164
168
|
if (path.length === 0) {
|
|
165
|
-
(cache[''] ??= {
|
|
169
|
+
(cache[''] ??= {}).input = value;
|
|
166
170
|
return;
|
|
167
171
|
}
|
|
168
172
|
|
|
169
|
-
const input = entry?.
|
|
173
|
+
const input = entry?.input ?? {};
|
|
170
174
|
deep_set(input, path.map(String), value);
|
|
171
|
-
(cache[''] ??= {
|
|
175
|
+
(cache[''] ??= {}).input = input;
|
|
172
176
|
},
|
|
173
|
-
() => flatten_issues(get_cache(__)?.['']?.
|
|
177
|
+
() => flatten_issues(get_cache(__, get_request_store().state)?.['']?.issues ?? [])
|
|
174
178
|
);
|
|
175
179
|
}
|
|
176
180
|
});
|
|
@@ -178,7 +182,7 @@ export function form(validate_or_fn, maybe_fn) {
|
|
|
178
182
|
Object.defineProperty(instance, 'result', {
|
|
179
183
|
get() {
|
|
180
184
|
try {
|
|
181
|
-
return get_cache(__)?.['']?.
|
|
185
|
+
return get_cache(__, get_request_store().state)?.['']?.result;
|
|
182
186
|
} catch {
|
|
183
187
|
return undefined;
|
|
184
188
|
}
|
|
@@ -222,6 +226,7 @@ export function form(validate_or_fn, maybe_fn) {
|
|
|
222
226
|
if (!instance) {
|
|
223
227
|
instance = create_instance(key);
|
|
224
228
|
instance.__.id = `${__.id}/${encodeURIComponent(JSON.stringify(key))}`;
|
|
229
|
+
instance.__.action_id = `${__.id}/${JSON.stringify(key)}`;
|
|
225
230
|
instance.__.name = __.name;
|
|
226
231
|
|
|
227
232
|
state.remote.forms.set(cache_key, instance);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** @import { RemoteResource, RemotePrerenderFunction } from '@sveltejs/kit' */
|
|
2
2
|
/** @import { RemotePrerenderInputsGenerator, RemotePrerenderInternals, MaybePromise } from 'types' */
|
|
3
3
|
/** @import { StandardSchemaV1 } from '@standard-schema/spec' */
|
|
4
|
-
import {
|
|
4
|
+
import { json, error } from '@sveltejs/kit';
|
|
5
5
|
import { DEV } from 'esm-env';
|
|
6
6
|
import { get_request_store } from '@sveltejs/kit/internal/server';
|
|
7
7
|
import { stringify, stringify_remote_arg } from '../../../shared.js';
|
|
@@ -9,7 +9,6 @@ import { noop } from '../../../../utils/functions.js';
|
|
|
9
9
|
import { app_dir, base } from '$app/paths/internal/server';
|
|
10
10
|
import {
|
|
11
11
|
create_validator,
|
|
12
|
-
get_cache,
|
|
13
12
|
get_response,
|
|
14
13
|
parse_remote_response,
|
|
15
14
|
run_remote_function
|
|
@@ -89,51 +88,46 @@ export function prerender(validate_or_fn, fn_or_options, maybe_options) {
|
|
|
89
88
|
|
|
90
89
|
/** @type {RemotePrerenderFunction<Input, Output> & { __: RemotePrerenderInternals }} */
|
|
91
90
|
const wrapper = (arg) => {
|
|
91
|
+
const { event, state } = get_request_store();
|
|
92
|
+
const payload = stringify_remote_arg(arg, state.transport);
|
|
93
|
+
|
|
94
|
+
// `get_response` (as opposed to bare `get_cache`) also registers the call in the
|
|
95
|
+
// implicit lookup, so that the result is inlined into the page payload (`data.p`)
|
|
96
|
+
// and the client doesn't need to fetch it again upon hydration
|
|
92
97
|
/** @type {Promise<Output> & Partial<RemoteResource<Output>>} */
|
|
93
|
-
const promise = (async () => {
|
|
94
|
-
const { event, state } = get_request_store();
|
|
95
|
-
const payload = stringify_remote_arg(arg, state.transport);
|
|
98
|
+
const promise = get_response(__, payload, state, async () => {
|
|
96
99
|
const id = __.id;
|
|
97
100
|
const url = `${base}/${app_dir}/remote/${id}${payload ? `/${payload}` : ''}`;
|
|
98
101
|
|
|
99
102
|
if (!state.prerendering && !DEV && !event.isRemoteRequest) {
|
|
100
103
|
try {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
error(prerendered.status, prerendered.error);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return prerendered.result;
|
|
120
|
-
})
|
|
121
|
-
}).data;
|
|
122
|
-
|
|
123
|
-
return parse_remote_response(await promise, state.transport);
|
|
124
|
-
});
|
|
104
|
+
// TODO adapters can provide prerendered data more efficiently than
|
|
105
|
+
// fetching from the public internet
|
|
106
|
+
const response = await fetch(new URL(url, event.url.origin).href);
|
|
107
|
+
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
throw new Error('Prerendered response not found');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const prerendered = /** @type {RemoteFunctionResponse} */ await response.json();
|
|
113
|
+
|
|
114
|
+
if (prerendered.type === 'error') {
|
|
115
|
+
error(prerendered.status, prerendered.error);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return parse_remote_response(prerendered.data, state.transport)._;
|
|
125
119
|
} catch {
|
|
126
120
|
// not available prerendered, fallback to normal function
|
|
127
121
|
}
|
|
128
122
|
}
|
|
129
123
|
|
|
124
|
+
// during a prerender run, the same function might be invoked while rendering
|
|
125
|
+
// multiple pages — share the result across the entire run
|
|
130
126
|
if (state.prerendering?.remote_responses.has(url)) {
|
|
131
127
|
return /** @type {Promise<any>} */ (state.prerendering.remote_responses.get(url));
|
|
132
128
|
}
|
|
133
129
|
|
|
134
|
-
const promise =
|
|
135
|
-
run_remote_function(event, state, false, () => validate(arg), fn)
|
|
136
|
-
);
|
|
130
|
+
const promise = run_remote_function(event, state, false, () => validate(arg), fn);
|
|
137
131
|
|
|
138
132
|
if (state.prerendering) {
|
|
139
133
|
state.prerendering.remote_responses.set(url, promise);
|
|
@@ -142,7 +136,7 @@ export function prerender(validate_or_fn, fn_or_options, maybe_options) {
|
|
|
142
136
|
const result = await promise;
|
|
143
137
|
|
|
144
138
|
if (state.prerendering) {
|
|
145
|
-
const body = { type: 'result',
|
|
139
|
+
const body = { type: 'result', data: stringify({ _: result }, state.transport) };
|
|
146
140
|
state.prerendering.dependencies.set(url, {
|
|
147
141
|
body: JSON.stringify(body),
|
|
148
142
|
response: json(body)
|
|
@@ -151,7 +145,7 @@ export function prerender(validate_or_fn, fn_or_options, maybe_options) {
|
|
|
151
145
|
|
|
152
146
|
// TODO this is missing error/loading/current/status
|
|
153
147
|
return result;
|
|
154
|
-
})
|
|
148
|
+
});
|
|
155
149
|
|
|
156
150
|
promise.catch(noop);
|
|
157
151
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
/** @import { RemoteLiveQuery, RemoteLiveQueryFunction, RemoteQuery, RemoteQueryFunction } from '@sveltejs/kit' */
|
|
1
|
+
/** @import { RemoteLiveQuery, RemoteLiveQueryFunction, RemoteQuery, RemoteQueryFunction, RequestEvent } from '@sveltejs/kit' */
|
|
2
2
|
/** @import { RemoteInternals, MaybePromise, RequestState, RemoteQueryLiveInternals, RemoteQueryBatchInternals, RemoteQueryInternals, RemoteLiveQueryUserFunctionReturnType } from 'types' */
|
|
3
3
|
/** @import { StandardSchemaV1 } from '@standard-schema/spec' */
|
|
4
4
|
import { get_request_store } from '@sveltejs/kit/internal/server';
|
|
5
|
-
import { create_remote_key,
|
|
5
|
+
import { create_remote_key, stringify_remote_arg } from '../../../shared.js';
|
|
6
6
|
import { prerendering } from '$app/env/internal';
|
|
7
7
|
import {
|
|
8
8
|
create_validator,
|
|
@@ -11,10 +11,10 @@ import {
|
|
|
11
11
|
run_remote_function,
|
|
12
12
|
run_remote_generator
|
|
13
13
|
} from './shared.js';
|
|
14
|
-
import { handle_error_and_jsonify } from '../../../server/utils.js';
|
|
15
|
-
import { HttpError, SvelteKitError } from '@sveltejs/kit/internal';
|
|
16
14
|
import { noop } from '../../../../utils/functions.js';
|
|
17
15
|
import { SharedIterator } from '../../../../utils/shared-iterator.js';
|
|
16
|
+
import { handle_error_and_jsonify } from '../../../server/utils.js';
|
|
17
|
+
import { HttpError, SvelteKitError } from '@sveltejs/kit/internal';
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Creates a remote query. When called from the browser, the function will be invoked on the server via a `fetch` call.
|
|
@@ -78,8 +78,14 @@ export function query(validate_or_fn, maybe_fn) {
|
|
|
78
78
|
bind(payload, validated_arg) {
|
|
79
79
|
const { event, state } = get_request_store();
|
|
80
80
|
|
|
81
|
-
return create_query_resource(__, payload, state, () =>
|
|
82
|
-
run_remote_function(
|
|
81
|
+
return create_query_resource(__, payload, event, state, () =>
|
|
82
|
+
run_remote_function(
|
|
83
|
+
event,
|
|
84
|
+
{ ...state, is_in_remote_query: true },
|
|
85
|
+
false,
|
|
86
|
+
() => validated_arg,
|
|
87
|
+
fn
|
|
88
|
+
)
|
|
83
89
|
);
|
|
84
90
|
}
|
|
85
91
|
};
|
|
@@ -95,8 +101,14 @@ export function query(validate_or_fn, maybe_fn) {
|
|
|
95
101
|
const { event, state } = get_request_store();
|
|
96
102
|
const payload = stringify_remote_arg(arg, state.transport);
|
|
97
103
|
|
|
98
|
-
return create_query_resource(__, payload, state, () =>
|
|
99
|
-
run_remote_function(
|
|
104
|
+
return create_query_resource(__, payload, event, state, () =>
|
|
105
|
+
run_remote_function(
|
|
106
|
+
event,
|
|
107
|
+
{ ...state, is_in_remote_query: true },
|
|
108
|
+
false,
|
|
109
|
+
() => validate(arg),
|
|
110
|
+
fn
|
|
111
|
+
)
|
|
100
112
|
);
|
|
101
113
|
};
|
|
102
114
|
|
|
@@ -152,7 +164,14 @@ function live(validate_or_fn, maybe_fn) {
|
|
|
152
164
|
* @param {any} get_input
|
|
153
165
|
*/
|
|
154
166
|
const run = (event, state, get_input) =>
|
|
155
|
-
run_remote_generator(
|
|
167
|
+
run_remote_generator(
|
|
168
|
+
event,
|
|
169
|
+
{ ...state, is_in_remote_query: true },
|
|
170
|
+
false,
|
|
171
|
+
get_input,
|
|
172
|
+
fn,
|
|
173
|
+
__.name
|
|
174
|
+
);
|
|
156
175
|
|
|
157
176
|
/** @type {RemoteQueryLiveInternals} */
|
|
158
177
|
const __ = {
|
|
@@ -164,7 +183,7 @@ function live(validate_or_fn, maybe_fn) {
|
|
|
164
183
|
bind(payload, validated_arg) {
|
|
165
184
|
const { event, state } = get_request_store();
|
|
166
185
|
|
|
167
|
-
return create_live_query_resource(__, payload,
|
|
186
|
+
return create_live_query_resource(__, payload, event, state, () =>
|
|
168
187
|
run(event, state, () => validated_arg)
|
|
169
188
|
);
|
|
170
189
|
}
|
|
@@ -181,7 +200,7 @@ function live(validate_or_fn, maybe_fn) {
|
|
|
181
200
|
const { event, state } = get_request_store();
|
|
182
201
|
const payload = stringify_remote_arg(arg, state.transport);
|
|
183
202
|
|
|
184
|
-
return create_live_query_resource(__, payload,
|
|
203
|
+
return create_live_query_resource(__, payload, event, state, () =>
|
|
185
204
|
run(event, state, () => validate(arg))
|
|
186
205
|
);
|
|
187
206
|
};
|
|
@@ -273,7 +292,7 @@ function batch(validate_or_fn, maybe_fn) {
|
|
|
273
292
|
try {
|
|
274
293
|
return await run_remote_function(
|
|
275
294
|
event,
|
|
276
|
-
state,
|
|
295
|
+
{ ...state, is_in_remote_query: true },
|
|
277
296
|
false,
|
|
278
297
|
async () => Promise.all(entries.map((entry) => entry.get_validated())),
|
|
279
298
|
async (input) => {
|
|
@@ -316,7 +335,7 @@ function batch(validate_or_fn, maybe_fn) {
|
|
|
316
335
|
|
|
317
336
|
return run_remote_function(
|
|
318
337
|
event,
|
|
319
|
-
state,
|
|
338
|
+
{ ...state, is_in_remote_query: true },
|
|
320
339
|
false,
|
|
321
340
|
async () => Promise.all(args.map(validate)),
|
|
322
341
|
async (/** @type {any[]} */ input) => {
|
|
@@ -326,7 +345,7 @@ function batch(validate_or_fn, maybe_fn) {
|
|
|
326
345
|
input.map(async (arg, i) => {
|
|
327
346
|
try {
|
|
328
347
|
const data = get_result(arg, i);
|
|
329
|
-
return { type: 'result', data
|
|
348
|
+
return { type: 'result', data };
|
|
330
349
|
} catch (error) {
|
|
331
350
|
return {
|
|
332
351
|
type: 'error',
|
|
@@ -343,9 +362,11 @@ function batch(validate_or_fn, maybe_fn) {
|
|
|
343
362
|
);
|
|
344
363
|
},
|
|
345
364
|
bind(payload, validated_arg) {
|
|
346
|
-
const { state } = get_request_store();
|
|
365
|
+
const { event, state } = get_request_store();
|
|
347
366
|
|
|
348
|
-
return create_query_resource(__, payload,
|
|
367
|
+
return create_query_resource(__, payload, event, state, () =>
|
|
368
|
+
enqueue(payload, () => validated_arg)
|
|
369
|
+
);
|
|
349
370
|
}
|
|
350
371
|
};
|
|
351
372
|
|
|
@@ -357,10 +378,10 @@ function batch(validate_or_fn, maybe_fn) {
|
|
|
357
378
|
);
|
|
358
379
|
}
|
|
359
380
|
|
|
360
|
-
const { state } = get_request_store();
|
|
381
|
+
const { event, state } = get_request_store();
|
|
361
382
|
const payload = stringify_remote_arg(arg, state.transport);
|
|
362
383
|
|
|
363
|
-
return create_query_resource(__, payload, state, () =>
|
|
384
|
+
return create_query_resource(__, payload, event, state, () =>
|
|
364
385
|
// Collect all the calls to the same query in the same macrotask,
|
|
365
386
|
// then execute them as one backend request.
|
|
366
387
|
enqueue(payload, () => validate(arg))
|
|
@@ -372,14 +393,53 @@ function batch(validate_or_fn, maybe_fn) {
|
|
|
372
393
|
return wrapper;
|
|
373
394
|
}
|
|
374
395
|
|
|
396
|
+
/**
|
|
397
|
+
* Include this value in the returned payload...
|
|
398
|
+
* @param {RequestEvent} event
|
|
399
|
+
* @param {RequestState} state
|
|
400
|
+
* @param {RemoteInternals} internals
|
|
401
|
+
* @param {string} payload
|
|
402
|
+
* @param {() => Promise<any>} fn
|
|
403
|
+
*/
|
|
404
|
+
export function refresh(event, state, internals, payload, fn) {
|
|
405
|
+
if (!internals.id) {
|
|
406
|
+
// unless this is a non-exported (i.e. private) query...
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (!event.isRemoteRequest) {
|
|
411
|
+
// or this is a no-JS form submission
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const key = create_remote_key(internals.id, payload);
|
|
416
|
+
|
|
417
|
+
// `fn()` is invoked eagerly here, which starts running the query immediately.
|
|
418
|
+
// The resulting promise is normally awaited (and its rejection handled) in
|
|
419
|
+
// `collect_remote_data`, but some code paths (e.g. a command throwing a
|
|
420
|
+
// non-redirect error) never reach that point. Attach a no-op `catch` to the
|
|
421
|
+
// promise so a rejection is always considered handled and can never become an
|
|
422
|
+
// unhandled promise rejection (which crashes the process on modern Node).
|
|
423
|
+
// We still store the original promise so `collect_remote_data` can serialize
|
|
424
|
+
// either its value or its error as before.
|
|
425
|
+
const promise = fn();
|
|
426
|
+
promise.catch(() => {});
|
|
427
|
+
|
|
428
|
+
(state.remote.explicit ??= new Map()).set(key, {
|
|
429
|
+
internals,
|
|
430
|
+
promise
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
375
434
|
/**
|
|
376
435
|
* @param {RemoteInternals} __
|
|
377
436
|
* @param {string} payload — the stringified raw argument (i.e. the cache key the client will use)
|
|
437
|
+
* @param {RequestEvent} event
|
|
378
438
|
* @param {RequestState} state
|
|
379
439
|
* @param {() => Promise<any>} fn
|
|
380
440
|
* @returns {RemoteQuery<any>}
|
|
381
441
|
*/
|
|
382
|
-
function create_query_resource(__, payload, state, fn) {
|
|
442
|
+
function create_query_resource(__, payload, event, state, fn) {
|
|
383
443
|
/** @type {Promise<any> | null} */
|
|
384
444
|
let promise = null;
|
|
385
445
|
|
|
@@ -391,7 +451,11 @@ function create_query_resource(__, payload, state, fn) {
|
|
|
391
451
|
// accessing data properties needs to kick off the work
|
|
392
452
|
// so that it gets seeded in the hydration cache
|
|
393
453
|
// and becomes available on the client
|
|
394
|
-
|
|
454
|
+
if (__.id && state.is_in_render) {
|
|
455
|
+
// swallow rejections so they don't crash the server — the error is
|
|
456
|
+
// serialized into the response and surfaced on the client instead
|
|
457
|
+
get_promise().catch(noop);
|
|
458
|
+
}
|
|
395
459
|
};
|
|
396
460
|
|
|
397
461
|
return {
|
|
@@ -420,20 +484,19 @@ function create_query_resource(__, payload, state, fn) {
|
|
|
420
484
|
return false;
|
|
421
485
|
},
|
|
422
486
|
refresh() {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
const refresh_context = get_refresh_context(__, 'refresh', payload);
|
|
430
|
-
const is_immediate_refresh = !refresh_context.cache[refresh_context.payload];
|
|
431
|
-
const value = is_immediate_refresh ? get_promise() : fn();
|
|
432
|
-
return update_refresh_value(refresh_context, value, is_immediate_refresh);
|
|
487
|
+
promise = null;
|
|
488
|
+
delete get_cache(__, state)[payload];
|
|
489
|
+
|
|
490
|
+
refresh(event, state, __, payload, get_promise);
|
|
491
|
+
|
|
492
|
+
return Promise.resolve();
|
|
433
493
|
},
|
|
434
494
|
/** @param {any} value */
|
|
435
495
|
set(value) {
|
|
436
|
-
|
|
496
|
+
const p = (promise = Promise.resolve(value));
|
|
497
|
+
get_cache(__, state)[payload] = p;
|
|
498
|
+
|
|
499
|
+
refresh(event, state, __, payload, () => p);
|
|
437
500
|
},
|
|
438
501
|
/** @type {Promise<any>['then']} */
|
|
439
502
|
then(onfulfilled, onrejected) {
|
|
@@ -451,12 +514,12 @@ function create_query_resource(__, payload, state, fn) {
|
|
|
451
514
|
/**
|
|
452
515
|
* @param {RemoteQueryLiveInternals} __
|
|
453
516
|
* @param {string} payload — the stringified raw argument (i.e. the cache key the client will use)
|
|
517
|
+
* @param {RequestEvent} event
|
|
454
518
|
* @param {RequestState} state
|
|
455
|
-
* @param {AbortSignal} signal — the request signal; aborts in-flight iteration when the client disconnects
|
|
456
519
|
* @param {() => AsyncGenerator<any, void, void>} get_generator
|
|
457
520
|
* @returns {RemoteLiveQuery<any>}
|
|
458
521
|
*/
|
|
459
|
-
function create_live_query_resource(__, payload,
|
|
522
|
+
function create_live_query_resource(__, payload, event, state, get_generator) {
|
|
460
523
|
/** @type {Promise<any> | null} */
|
|
461
524
|
let promise = null;
|
|
462
525
|
|
|
@@ -472,7 +535,11 @@ function create_live_query_resource(__, payload, state, signal, get_generator) {
|
|
|
472
535
|
};
|
|
473
536
|
|
|
474
537
|
const populate_hydratable = () => {
|
|
475
|
-
|
|
538
|
+
if (__.id && state.is_in_render) {
|
|
539
|
+
// swallow rejections so they don't crash the server — the error is
|
|
540
|
+
// serialized into the response and surfaced on the client instead
|
|
541
|
+
get_promise().catch(noop);
|
|
542
|
+
}
|
|
476
543
|
};
|
|
477
544
|
|
|
478
545
|
return {
|
|
@@ -509,15 +576,11 @@ function create_live_query_resource(__, payload, state, signal, get_generator) {
|
|
|
509
576
|
return false;
|
|
510
577
|
},
|
|
511
578
|
reconnect() {
|
|
512
|
-
|
|
579
|
+
promise = null;
|
|
580
|
+
delete get_cache(__, state)[payload];
|
|
513
581
|
|
|
514
|
-
|
|
515
|
-
throw new Error(
|
|
516
|
-
`Cannot call reconnect on query.live '${__.name}' because it is not executed in the context of a command/form remote function`
|
|
517
|
-
);
|
|
518
|
-
}
|
|
582
|
+
refresh(event, state, __, payload, get_promise);
|
|
519
583
|
|
|
520
|
-
reconnects.set(create_remote_key(__.id, payload), get_promise());
|
|
521
584
|
return Promise.resolve();
|
|
522
585
|
},
|
|
523
586
|
/** @ts-expect-error This method no longer exists */
|
|
@@ -535,7 +598,7 @@ function create_live_query_resource(__, payload, state, signal, get_generator) {
|
|
|
535
598
|
const cache = (state.remote.live_iterators ??= new Map());
|
|
536
599
|
let cached = cache.get(key);
|
|
537
600
|
if (!cached) {
|
|
538
|
-
cached = create_shared_live_iterator(signal, get_generator);
|
|
601
|
+
cached = create_shared_live_iterator(event.request.signal, get_generator);
|
|
539
602
|
cache.set(key, cached);
|
|
540
603
|
}
|
|
541
604
|
return cached.subscribe();
|
|
@@ -613,55 +676,3 @@ function create_shared_live_iterator(signal, get_generator) {
|
|
|
613
676
|
// Add batch as a property to the query function
|
|
614
677
|
Object.defineProperty(query, 'batch', { value: batch, enumerable: true });
|
|
615
678
|
Object.defineProperty(query, 'live', { value: live, enumerable: true });
|
|
616
|
-
|
|
617
|
-
/**
|
|
618
|
-
* @param {RemoteInternals} __
|
|
619
|
-
* @param {'set' | 'refresh'} action
|
|
620
|
-
* @param {string} payload — the stringified raw argument (i.e. the cache key the client will use)
|
|
621
|
-
* @returns {{ __: RemoteInternals; state: any; refreshes: Map<string, Promise<any>>; cache: Record<string, { serialize: boolean; data: any }>; refreshes_key: string; payload: string }}
|
|
622
|
-
*/
|
|
623
|
-
function get_refresh_context(__, action, payload) {
|
|
624
|
-
const { state } = get_request_store();
|
|
625
|
-
const { refreshes } = state.remote;
|
|
626
|
-
|
|
627
|
-
if (!refreshes) {
|
|
628
|
-
const name = __.type === 'query_batch' ? `query.batch '${__.name}'` : `query '${__.name}'`;
|
|
629
|
-
throw new Error(
|
|
630
|
-
`Cannot call ${action} on ${name} because it is not executed in the context of a command/form remote function`
|
|
631
|
-
);
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
const cache = get_cache(__, state);
|
|
635
|
-
const refreshes_key = create_remote_key(__.id, payload);
|
|
636
|
-
|
|
637
|
-
return { __, state, refreshes, refreshes_key, cache, payload };
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
/**
|
|
641
|
-
* @param {{ __: RemoteInternals; refreshes: Map<string, Promise<any>>; cache: Record<string, { serialize: boolean; data: any }>; refreshes_key: string; payload: string }} context
|
|
642
|
-
* @param {any} value
|
|
643
|
-
* @param {boolean} [is_immediate_refresh=false]
|
|
644
|
-
* @returns {Promise<void>}
|
|
645
|
-
*/
|
|
646
|
-
function update_refresh_value(
|
|
647
|
-
{ __, refreshes, refreshes_key, cache, payload },
|
|
648
|
-
value,
|
|
649
|
-
is_immediate_refresh = false
|
|
650
|
-
) {
|
|
651
|
-
const promise = Promise.resolve(value);
|
|
652
|
-
|
|
653
|
-
if (!is_immediate_refresh) {
|
|
654
|
-
cache[payload] = { serialize: true, data: promise };
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
if (__.id) {
|
|
658
|
-
refreshes.set(refreshes_key, promise);
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
promise.catch(noop);
|
|
662
|
-
|
|
663
|
-
// we return an immediately-resolving promise so that the `refresh()` signature is consistent,
|
|
664
|
-
// but it doesn't delay anything if awaited inside a command. this way, people aren't
|
|
665
|
-
// penalised if they do `await q1.refresh(); await q2.refresh()`
|
|
666
|
-
return Promise.resolve();
|
|
667
|
-
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/** @import { RemoteLiveQuery, RemoteLiveQueryFunction, RemoteQuery, RemoteQueryFunction, RequestedResult, QueryRequestedResult, LiveQueryRequestedResult } from '@sveltejs/kit' */
|
|
2
2
|
/** @import { MaybePromise, RemoteAnyQueryInternals } from 'types' */
|
|
3
|
+
import { HttpError } from '@sveltejs/kit/internal';
|
|
3
4
|
import { get_request_store } from '@sveltejs/kit/internal/server';
|
|
4
|
-
import {
|
|
5
|
+
import { parse_remote_arg } from '../../../shared.js';
|
|
5
6
|
import { noop } from '../../../../utils/functions.js';
|
|
7
|
+
import { get_cache } from './shared.js';
|
|
8
|
+
import { refresh } from './query.js';
|
|
6
9
|
|
|
7
10
|
/**
|
|
8
11
|
* In the context of a remote `command` or `form` request, returns an iterable
|
|
@@ -95,7 +98,7 @@ import { noop } from '../../../../utils/functions.js';
|
|
|
95
98
|
* @returns {RequestedResult<Validated, Output>}
|
|
96
99
|
*/
|
|
97
100
|
export function requested(query, limit) {
|
|
98
|
-
const { state } = get_request_store();
|
|
101
|
+
const { event, state } = get_request_store();
|
|
99
102
|
const internals = /** @type {RemoteAnyQueryInternals | undefined} */ (
|
|
100
103
|
/** @type {any} */ (query).__
|
|
101
104
|
);
|
|
@@ -115,15 +118,12 @@ export function requested(query, limit) {
|
|
|
115
118
|
|
|
116
119
|
const requested = state.remote.requested;
|
|
117
120
|
const payloads = requested?.get(__.id) ?? [];
|
|
121
|
+
|
|
118
122
|
// note: don't initialize these maps here -- they will be initialized by the
|
|
119
123
|
// command/form wrapper when we enter them, and if we initialize them here
|
|
120
124
|
// we will enable requested(...) in contexts where it shouldn't be allowed,
|
|
121
125
|
// such as load functions or other server functions
|
|
122
|
-
|
|
123
|
-
const reconnects = state.remote.reconnects;
|
|
124
|
-
const store = __.type === 'query_live' ? reconnects : refreshes;
|
|
125
|
-
|
|
126
|
-
if (!store) {
|
|
126
|
+
if (!state.is_in_remote_form_or_command) {
|
|
127
127
|
throw new Error(
|
|
128
128
|
'requested(...) can only be called in the context of a command/form remote function'
|
|
129
129
|
);
|
|
@@ -131,6 +131,9 @@ export function requested(query, limit) {
|
|
|
131
131
|
const [selected, skipped] = split_limit(payloads, limit);
|
|
132
132
|
|
|
133
133
|
/**
|
|
134
|
+
* Registers the failure exactly like `.set()` registers a value: the error record
|
|
135
|
+
* is serialized to the client (putting the query there into a failed state), and
|
|
136
|
+
* subsequent server-side calls of the query with the same argument reject with it.
|
|
134
137
|
* @param {string} payload
|
|
135
138
|
* @param {unknown} error
|
|
136
139
|
*/
|
|
@@ -138,14 +141,15 @@ export function requested(query, limit) {
|
|
|
138
141
|
const promise = Promise.reject(error);
|
|
139
142
|
promise.catch(noop);
|
|
140
143
|
|
|
141
|
-
|
|
142
|
-
|
|
144
|
+
get_cache(__, state)[payload] = promise;
|
|
145
|
+
refresh(event, state, __, payload, () => promise);
|
|
143
146
|
};
|
|
144
147
|
|
|
145
148
|
for (const payload of skipped) {
|
|
146
149
|
record_failure(
|
|
147
150
|
payload,
|
|
148
|
-
new
|
|
151
|
+
new HttpError(
|
|
152
|
+
400,
|
|
149
153
|
`Requested refresh was rejected because it exceeded requested(${__.name}, ${limit}) limit`
|
|
150
154
|
)
|
|
151
155
|
);
|