@sveltejs/kit 3.0.0-next.24 → 3.0.0-next.27
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 +16 -10
- package/src/constants.js +6 -0
- package/src/core/adapt/builder.js +128 -33
- package/src/core/adapt/index.js +3 -0
- package/src/core/config/index.js +4 -7
- package/src/core/env.js +39 -14
- package/src/core/generate_manifest/index.js +42 -43
- package/src/core/postbuild/analyse.js +4 -4
- package/src/core/postbuild/fallback.js +1 -1
- package/src/core/postbuild/prerender.js +4 -4
- package/src/core/sync/create_manifest_data/index.js +0 -1
- package/src/core/sync/write_app_manifest.js +3 -2
- package/src/core/sync/write_server.js +3 -17
- package/src/core/utils.js +10 -0
- package/src/exports/adapter.js +22 -0
- package/src/exports/public.d.ts +101 -49
- package/src/exports/vite/build/index.js +1173 -0
- package/src/exports/vite/build/remote.js +4 -4
- package/src/exports/vite/build/service-worker.js +98 -0
- package/src/exports/vite/dev/generate_manifest.js +308 -0
- package/src/exports/vite/dev/index.js +40 -282
- package/src/exports/vite/index.js +156 -1717
- package/src/exports/vite/plugins/env-vars.js +53 -34
- package/src/exports/vite/plugins/guard.js +217 -0
- package/src/exports/vite/plugins/remote.js +237 -0
- package/src/exports/vite/preview/index.js +8 -8
- package/src/exports/vite/static_analysis/index.js +15 -15
- package/src/exports/vite/utils.js +98 -1
- package/src/runner.js +4 -3
- package/src/runtime/app/paths/server.js +2 -2
- package/src/runtime/app/server/index.js +3 -3
- package/src/runtime/app/server/public.d.ts +114 -57
- package/src/runtime/app/server/remote/query.js +2 -2
- package/src/runtime/app/server/remote/requested.js +31 -15
- package/src/runtime/app/state/client.svelte.js +3 -1
- package/src/runtime/client/client.js +43 -198
- package/src/runtime/client/fetcher.js +6 -6
- package/src/runtime/client/focus.js +120 -0
- package/src/runtime/client/remote-functions/command.svelte.js +20 -9
- package/src/runtime/client/remote-functions/form.svelte.js +12 -7
- package/src/runtime/client/remote-functions/query/instance.svelte.js +19 -5
- package/src/runtime/client/remote-functions/shared.svelte.js +22 -1
- package/src/runtime/client/scroll.js +39 -0
- package/src/runtime/client/utils.js +28 -0
- package/src/runtime/form-utils.js +254 -264
- package/src/runtime/server/data/index.js +14 -36
- package/src/runtime/server/errors.js +7 -10
- package/src/runtime/server/fetch.js +21 -25
- package/src/runtime/server/index.js +35 -31
- package/src/runtime/server/internal.js +34 -5
- package/src/runtime/server/page/actions.js +6 -8
- package/src/runtime/server/page/data_serializer.js +6 -10
- package/src/runtime/server/page/index.js +18 -28
- package/src/runtime/server/page/load_data.js +2 -2
- package/src/runtime/server/page/render.js +22 -40
- package/src/runtime/server/page/respond_with_error.js +10 -13
- package/src/runtime/server/page/server_routing.js +21 -27
- package/src/runtime/server/remote-functions.js +136 -136
- package/src/runtime/server/respond.js +66 -64
- package/src/runtime/server/state.js +2 -0
- package/src/runtime/server/utils.js +4 -11
- package/src/runtime/shared.js +9 -0
- package/src/runtime/utils.js +41 -0
- package/src/types/ambient-private.d.ts +1 -2
- package/src/types/global-private.d.ts +8 -0
- package/src/types/internal.d.ts +56 -17
- package/src/utils/filesystem.js +44 -28
- package/src/utils/streaming.js +5 -15
- package/src/version.js +1 -1
- package/types/index.d.ts +235 -93
- package/types/index.d.ts.map +9 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
/** @import { RequestEvent
|
|
1
|
+
/** @import { RequestEvent } from '@sveltejs/kit' */
|
|
2
2
|
/** @import { RemoteForm } from '$app/server' */
|
|
3
|
-
/** @import { RemoteFormInternals, RemoteFunctionData, RemoteFunctionResponse, RemoteInternals, RequestState, ServerActionResult
|
|
3
|
+
/** @import { RemoteFormInternals, RemoteFunctionData, RemoteFunctionResponse, RemoteInternals, RequestState, ServerActionResult } from 'types' */
|
|
4
4
|
|
|
5
5
|
import { error } from '@sveltejs/kit';
|
|
6
6
|
import { Redirect, SvelteKitError } from '@sveltejs/kit/internal';
|
|
@@ -16,7 +16,9 @@ import {
|
|
|
16
16
|
method_not_allowed_result
|
|
17
17
|
} from './page/actions.js';
|
|
18
18
|
import { deserialize_binary_form } from '../form-utils.js';
|
|
19
|
+
import { text_encoder } from '../utils.js';
|
|
19
20
|
import { with_version_header } from './utils.js';
|
|
21
|
+
import { manifest } from './internal.js';
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
24
|
* How long (in milliseconds) to wait after the last message was sent before
|
|
@@ -25,8 +27,121 @@ import { with_version_header } from './utils.js';
|
|
|
25
27
|
*/
|
|
26
28
|
const KEEP_ALIVE_INTERVAL = 30_000;
|
|
27
29
|
|
|
30
|
+
/**
|
|
31
|
+
* @param {RequestEvent} event
|
|
32
|
+
* @param {RequestState} state
|
|
33
|
+
* @param {import('types').RemoteQueryLiveInternals} internals
|
|
34
|
+
* @param {any} arg
|
|
35
|
+
*/
|
|
36
|
+
export function create_live_query_response(event, state, internals, arg) {
|
|
37
|
+
const cancellation = new AbortController();
|
|
38
|
+
const live_event = {
|
|
39
|
+
...event,
|
|
40
|
+
request: new Request(event.request, {
|
|
41
|
+
signal: AbortSignal.any([event.request.signal, cancellation.signal])
|
|
42
|
+
})
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const generator = internals.run(live_event, state, arg);
|
|
46
|
+
|
|
47
|
+
let open = true;
|
|
48
|
+
let pulling = false;
|
|
49
|
+
/** @type {ReadableStreamDefaultController<Uint8Array>} */
|
|
50
|
+
let stream_controller;
|
|
51
|
+
/** @type {ReturnType<typeof setTimeout> | undefined} */
|
|
52
|
+
let keep_alive;
|
|
53
|
+
/** @type {string | undefined} */
|
|
54
|
+
let result;
|
|
55
|
+
|
|
56
|
+
function schedule_keep_alive() {
|
|
57
|
+
clearTimeout(keep_alive);
|
|
58
|
+
keep_alive = setTimeout(() => {
|
|
59
|
+
if (!open) return;
|
|
60
|
+
if ((stream_controller.desiredSize ?? 0) > 0) {
|
|
61
|
+
stream_controller.enqueue(text_encoder.encode(': keep-alive\n\n'));
|
|
62
|
+
}
|
|
63
|
+
schedule_keep_alive();
|
|
64
|
+
}, KEEP_ALIVE_INTERVAL);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** @param {any} data */
|
|
68
|
+
function send(data) {
|
|
69
|
+
if (!open) return;
|
|
70
|
+
stream_controller.enqueue(text_encoder.encode('data: ' + JSON.stringify(data) + '\n\n'));
|
|
71
|
+
schedule_keep_alive();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** @param {boolean} cancelled */
|
|
75
|
+
function teardown(cancelled) {
|
|
76
|
+
if (!open) return;
|
|
77
|
+
open = false;
|
|
78
|
+
clearTimeout(keep_alive);
|
|
79
|
+
cancellation.abort();
|
|
80
|
+
if (!cancelled) stream_controller.close();
|
|
81
|
+
// AsyncGenerator.return() cannot interrupt a pending next(). Cleanup is
|
|
82
|
+
// cooperative via request.signal, so stream cancellation must not await it.
|
|
83
|
+
void generator.return(undefined).catch(() => {});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
event.request.signal.addEventListener('abort', () => teardown(true), { once: true });
|
|
87
|
+
|
|
88
|
+
return new Response(
|
|
89
|
+
new ReadableStream({
|
|
90
|
+
start(controller) {
|
|
91
|
+
stream_controller = controller;
|
|
92
|
+
schedule_keep_alive();
|
|
93
|
+
},
|
|
94
|
+
async pull() {
|
|
95
|
+
if (!open || pulling) return;
|
|
96
|
+
pulling = true;
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
while (open) {
|
|
100
|
+
const { value, done } = await generator.next();
|
|
101
|
+
|
|
102
|
+
if (!open) return;
|
|
103
|
+
|
|
104
|
+
if (done) {
|
|
105
|
+
teardown(false);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (result !== (result = stringify(value))) {
|
|
110
|
+
send({ type: 'result', result });
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (!open) return;
|
|
116
|
+
|
|
117
|
+
if (error instanceof Redirect) {
|
|
118
|
+
send({ type: 'redirect', location: error.location });
|
|
119
|
+
} else {
|
|
120
|
+
const transformed = await handle_error_and_jsonify(event, state, error);
|
|
121
|
+
|
|
122
|
+
send({ type: 'error', error: transformed });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
teardown(false);
|
|
126
|
+
} finally {
|
|
127
|
+
pulling = false;
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
cancel() {
|
|
131
|
+
teardown(true);
|
|
132
|
+
}
|
|
133
|
+
}),
|
|
134
|
+
{
|
|
135
|
+
headers: {
|
|
136
|
+
'cache-control': 'private, no-store',
|
|
137
|
+
'content-type': 'text/event-stream'
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
28
143
|
/** @type {typeof handle_remote_call_internal} */
|
|
29
|
-
export async function handle_remote_call(event, state,
|
|
144
|
+
export async function handle_remote_call(event, state, id) {
|
|
30
145
|
return record_span({
|
|
31
146
|
name: 'sveltekit.remote.call',
|
|
32
147
|
attributes: {
|
|
@@ -35,7 +150,7 @@ export async function handle_remote_call(event, state, options, manifest, id) {
|
|
|
35
150
|
fn: async (current) => {
|
|
36
151
|
const traced_event = merge_tracing(event, current);
|
|
37
152
|
const response = await with_request_store({ event: traced_event, state }, () =>
|
|
38
|
-
handle_remote_call_internal(traced_event, state,
|
|
153
|
+
handle_remote_call_internal(traced_event, state, id)
|
|
39
154
|
);
|
|
40
155
|
return with_version_header(response);
|
|
41
156
|
}
|
|
@@ -45,13 +160,11 @@ export async function handle_remote_call(event, state, options, manifest, id) {
|
|
|
45
160
|
/**
|
|
46
161
|
* @param {RequestEvent} event
|
|
47
162
|
* @param {RequestState} state
|
|
48
|
-
* @param {SSROptions} options
|
|
49
|
-
* @param {SSRManifest} manifest
|
|
50
163
|
* @param {string} id
|
|
51
164
|
*/
|
|
52
|
-
async function handle_remote_call_internal(event, state,
|
|
165
|
+
async function handle_remote_call_internal(event, state, id) {
|
|
53
166
|
const [hash, name, additional_args] = id.split('/');
|
|
54
|
-
const remotes = manifest.
|
|
167
|
+
const remotes = manifest.remotes;
|
|
55
168
|
|
|
56
169
|
if (!Object.hasOwn(remotes, hash)) error(404);
|
|
57
170
|
|
|
@@ -89,119 +202,7 @@ async function handle_remote_call_internal(event, state, options, manifest, id)
|
|
|
89
202
|
new URL(event.request.url).searchParams.get('payload')
|
|
90
203
|
);
|
|
91
204
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const encoder = new TextEncoder();
|
|
95
|
-
|
|
96
|
-
let closed = false;
|
|
97
|
-
|
|
98
|
-
/** @type {ReturnType<typeof setTimeout> | undefined} */
|
|
99
|
-
let keep_alive;
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* (Re)schedule the keep-alive comment. Called whenever a message is sent, so
|
|
103
|
-
* that a keep-alive is only emitted once `KEEP_ALIVE_INTERVAL` has elapsed
|
|
104
|
-
* without any other activity.
|
|
105
|
-
* @param {ReadableStreamDefaultController} controller
|
|
106
|
-
*/
|
|
107
|
-
function schedule_keep_alive(controller) {
|
|
108
|
-
clearTimeout(keep_alive);
|
|
109
|
-
keep_alive = setTimeout(() => {
|
|
110
|
-
if (closed || event.request.signal.aborted) return;
|
|
111
|
-
// SSE comments (lines starting with `:`) are ignored by the client
|
|
112
|
-
controller.enqueue(encoder.encode(': keep-alive\n\n'));
|
|
113
|
-
schedule_keep_alive(controller);
|
|
114
|
-
}, KEEP_ALIVE_INTERVAL);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* @param {ReadableStreamDefaultController} controller
|
|
119
|
-
* @param {any} payload
|
|
120
|
-
*/
|
|
121
|
-
function send(controller, payload) {
|
|
122
|
-
controller.enqueue(encoder.encode('data: ' + JSON.stringify(payload) + '\n\n'));
|
|
123
|
-
schedule_keep_alive(controller);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/** @type {string | undefined} */
|
|
127
|
-
let result = undefined;
|
|
128
|
-
|
|
129
|
-
async function cancel() {
|
|
130
|
-
if (closed) return;
|
|
131
|
-
closed = true;
|
|
132
|
-
clearTimeout(keep_alive);
|
|
133
|
-
await generator.return(undefined);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
event.request.signal.addEventListener('abort', cancel, { once: true });
|
|
137
|
-
|
|
138
|
-
return new Response(
|
|
139
|
-
new ReadableStream({
|
|
140
|
-
start(controller) {
|
|
141
|
-
schedule_keep_alive(controller);
|
|
142
|
-
},
|
|
143
|
-
async pull(controller) {
|
|
144
|
-
if (event.request.signal.aborted) {
|
|
145
|
-
await cancel();
|
|
146
|
-
controller.close();
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
try {
|
|
151
|
-
while (true) {
|
|
152
|
-
const { value, done } = await generator.next();
|
|
153
|
-
|
|
154
|
-
if (done) {
|
|
155
|
-
await cancel();
|
|
156
|
-
controller.close();
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// only send changed data
|
|
161
|
-
if (result !== (result = stringify(value))) {
|
|
162
|
-
send(controller, {
|
|
163
|
-
type: 'result',
|
|
164
|
-
result
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
} catch (error) {
|
|
171
|
-
if (!event.request.signal.aborted) {
|
|
172
|
-
if (error instanceof Redirect) {
|
|
173
|
-
send(controller, {
|
|
174
|
-
type: 'redirect',
|
|
175
|
-
location: error.location
|
|
176
|
-
});
|
|
177
|
-
} else {
|
|
178
|
-
const transformed = await handle_error_and_jsonify(
|
|
179
|
-
event,
|
|
180
|
-
state,
|
|
181
|
-
options,
|
|
182
|
-
error
|
|
183
|
-
);
|
|
184
|
-
|
|
185
|
-
send(controller, {
|
|
186
|
-
type: 'error',
|
|
187
|
-
error: transformed
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
await cancel();
|
|
193
|
-
controller.close();
|
|
194
|
-
}
|
|
195
|
-
},
|
|
196
|
-
cancel
|
|
197
|
-
}),
|
|
198
|
-
{
|
|
199
|
-
headers: {
|
|
200
|
-
'cache-control': 'private, no-store',
|
|
201
|
-
'content-type': 'text/event-stream'
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
);
|
|
205
|
+
return create_live_query_response(event, state, internals, parse_remote_arg(payload));
|
|
205
206
|
}
|
|
206
207
|
|
|
207
208
|
case 'query_batch': {
|
|
@@ -218,7 +219,7 @@ async function handle_remote_call_internal(event, state, options, manifest, id)
|
|
|
218
219
|
|
|
219
220
|
const args = await Promise.all(payloads.map((payload) => parse_remote_arg(payload)));
|
|
220
221
|
|
|
221
|
-
data._ = await with_request_store({ event, state }, () => internals.run(args
|
|
222
|
+
data._ = await with_request_store({ event, state }, () => internals.run(args));
|
|
222
223
|
|
|
223
224
|
break;
|
|
224
225
|
}
|
|
@@ -309,7 +310,8 @@ async function handle_remote_call_internal(event, state, options, manifest, id)
|
|
|
309
310
|
}
|
|
310
311
|
}
|
|
311
312
|
|
|
312
|
-
await collect_remote_data(data, event, state
|
|
313
|
+
await collect_remote_data(data, event, state);
|
|
314
|
+
if (state.remote.ignored?.size) data.i = Array.from(state.remote.ignored);
|
|
313
315
|
|
|
314
316
|
return Response.json(
|
|
315
317
|
/** @type {RemoteFunctionResponse} */ ({
|
|
@@ -320,7 +322,7 @@ async function handle_remote_call_internal(event, state, options, manifest, id)
|
|
|
320
322
|
);
|
|
321
323
|
} catch (error) {
|
|
322
324
|
if (error instanceof Redirect) {
|
|
323
|
-
const data = await collect_remote_data({ redirect: error.location }, event, state
|
|
325
|
+
const data = await collect_remote_data({ redirect: error.location }, event, state);
|
|
324
326
|
|
|
325
327
|
return Response.json(
|
|
326
328
|
/** @type {RemoteFunctionResponse} */ ({
|
|
@@ -331,7 +333,7 @@ async function handle_remote_call_internal(event, state, options, manifest, id)
|
|
|
331
333
|
);
|
|
332
334
|
}
|
|
333
335
|
|
|
334
|
-
const transformed = await handle_error_and_jsonify(event, state,
|
|
336
|
+
const transformed = await handle_error_and_jsonify(event, state, error);
|
|
335
337
|
|
|
336
338
|
return Response.json(
|
|
337
339
|
/** @type {RemoteFunctionResponse} */ ({
|
|
@@ -356,9 +358,8 @@ async function handle_remote_call_internal(event, state, options, manifest, id)
|
|
|
356
358
|
* @param {RemoteFunctionData} data
|
|
357
359
|
* @param {RequestEvent} event
|
|
358
360
|
* @param {RequestState} state
|
|
359
|
-
* @param {SSROptions} options
|
|
360
361
|
*/
|
|
361
|
-
export async function collect_remote_data(data, event, state
|
|
362
|
+
export async function collect_remote_data(data, event, state) {
|
|
362
363
|
/**
|
|
363
364
|
*
|
|
364
365
|
* @param {unknown} error
|
|
@@ -366,7 +367,7 @@ export async function collect_remote_data(data, event, state, options) {
|
|
|
366
367
|
*/
|
|
367
368
|
function convert_error(error) {
|
|
368
369
|
// TODO 4.0 remove the `Promise.resolve(...)`
|
|
369
|
-
return Promise.resolve(handle_error_and_jsonify(event, state,
|
|
370
|
+
return Promise.resolve(handle_error_and_jsonify(event, state, error));
|
|
370
371
|
}
|
|
371
372
|
|
|
372
373
|
/** @type {Promise<any>[]} */
|
|
@@ -493,7 +494,7 @@ export async function collect_remote_data(data, event, state, options) {
|
|
|
493
494
|
* @param {string[] | undefined} refreshes
|
|
494
495
|
*/
|
|
495
496
|
function create_requested_map(refreshes) {
|
|
496
|
-
/** @type {Map<string, string
|
|
497
|
+
/** @type {Map<string, Set<string>>} */
|
|
497
498
|
const requested = new Map();
|
|
498
499
|
|
|
499
500
|
for (const key of refreshes ?? []) {
|
|
@@ -502,9 +503,9 @@ function create_requested_map(refreshes) {
|
|
|
502
503
|
const existing = requested.get(parts.id);
|
|
503
504
|
|
|
504
505
|
if (existing) {
|
|
505
|
-
existing.
|
|
506
|
+
existing.add(parts.payload);
|
|
506
507
|
} else {
|
|
507
|
-
requested.set(parts.id, [parts.payload]);
|
|
508
|
+
requested.set(parts.id, new Set([parts.payload]));
|
|
508
509
|
}
|
|
509
510
|
}
|
|
510
511
|
|
|
@@ -512,7 +513,7 @@ function create_requested_map(refreshes) {
|
|
|
512
513
|
}
|
|
513
514
|
|
|
514
515
|
/** @type {typeof handle_remote_form_post_internal} */
|
|
515
|
-
export async function handle_remote_form_post(event, state,
|
|
516
|
+
export async function handle_remote_form_post(event, state, id) {
|
|
516
517
|
return record_span({
|
|
517
518
|
name: 'sveltekit.remote.form.post',
|
|
518
519
|
attributes: {
|
|
@@ -521,7 +522,7 @@ export async function handle_remote_form_post(event, state, manifest, id) {
|
|
|
521
522
|
fn: (current) => {
|
|
522
523
|
const traced_event = merge_tracing(event, current);
|
|
523
524
|
return with_request_store({ event: traced_event, state }, () =>
|
|
524
|
-
handle_remote_form_post_internal(traced_event, state,
|
|
525
|
+
handle_remote_form_post_internal(traced_event, state, id)
|
|
525
526
|
);
|
|
526
527
|
}
|
|
527
528
|
});
|
|
@@ -530,17 +531,16 @@ export async function handle_remote_form_post(event, state, manifest, id) {
|
|
|
530
531
|
/**
|
|
531
532
|
* @param {RequestEvent} event
|
|
532
533
|
* @param {RequestState} state
|
|
533
|
-
* @param {SSRManifest} manifest
|
|
534
534
|
* @param {string} id
|
|
535
535
|
* @returns {Promise<ServerActionResult>}
|
|
536
536
|
*/
|
|
537
|
-
async function handle_remote_form_post_internal(event, state,
|
|
537
|
+
async function handle_remote_form_post_internal(event, state, id) {
|
|
538
538
|
const location = get_action_location(event.url);
|
|
539
539
|
// `hash` and `name` can never contain a `/`, but the JSON-stringified key of a
|
|
540
540
|
// keyed (`form.for(key)`) instance can — rejoin the remaining segments
|
|
541
541
|
const [hash, name, ...rest] = id.split('/');
|
|
542
542
|
const action_id = rest.join('/');
|
|
543
|
-
const remotes = manifest.
|
|
543
|
+
const remotes = manifest.remotes;
|
|
544
544
|
const module = Object.hasOwn(remotes, hash) ? await remotes[hash]() : undefined;
|
|
545
545
|
|
|
546
546
|
let form = /** @type {RemoteForm<any, any>} */ (
|