@sveltejs/kit 2.26.0 → 2.27.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 (56) hide show
  1. package/README.md +1 -1
  2. package/package.json +3 -2
  3. package/src/core/adapt/builder.js +6 -1
  4. package/src/core/config/options.js +4 -0
  5. package/src/core/generate_manifest/index.js +3 -0
  6. package/src/core/postbuild/analyse.js +25 -1
  7. package/src/core/postbuild/fallback.js +2 -1
  8. package/src/core/postbuild/prerender.js +41 -10
  9. package/src/core/sync/create_manifest_data/index.js +35 -1
  10. package/src/core/sync/write_server.js +4 -2
  11. package/src/core/sync/write_types/index.js +12 -5
  12. package/src/exports/index.js +1 -1
  13. package/src/exports/internal/index.js +3 -1
  14. package/src/exports/internal/remote-functions.js +21 -0
  15. package/src/exports/public.d.ts +162 -2
  16. package/src/exports/vite/build/build_remote.js +129 -0
  17. package/src/exports/vite/dev/index.js +7 -0
  18. package/src/exports/vite/index.js +123 -8
  19. package/src/exports/vite/module_ids.js +3 -2
  20. package/src/exports/vite/preview/index.js +3 -1
  21. package/src/runtime/app/navigation.js +1 -0
  22. package/src/runtime/app/server/index.js +2 -0
  23. package/src/runtime/app/server/remote/command.js +91 -0
  24. package/src/runtime/app/server/remote/form.js +124 -0
  25. package/src/runtime/app/server/remote/index.js +4 -0
  26. package/src/runtime/app/server/remote/prerender.js +163 -0
  27. package/src/runtime/app/server/remote/query.js +115 -0
  28. package/src/runtime/app/server/remote/shared.js +153 -0
  29. package/src/runtime/client/client.js +107 -39
  30. package/src/runtime/client/fetcher.js +1 -1
  31. package/src/runtime/client/remote-functions/command.js +71 -0
  32. package/src/runtime/client/remote-functions/form.svelte.js +312 -0
  33. package/src/runtime/client/remote-functions/index.js +4 -0
  34. package/src/runtime/client/remote-functions/prerender.svelte.js +166 -0
  35. package/src/runtime/client/remote-functions/query.svelte.js +219 -0
  36. package/src/runtime/client/remote-functions/shared.svelte.js +143 -0
  37. package/src/runtime/client/types.d.ts +2 -0
  38. package/src/runtime/server/data/index.js +6 -4
  39. package/src/runtime/server/event-state.js +41 -0
  40. package/src/runtime/server/index.js +12 -3
  41. package/src/runtime/server/page/actions.js +1 -1
  42. package/src/runtime/server/page/index.js +10 -3
  43. package/src/runtime/server/page/load_data.js +18 -12
  44. package/src/runtime/server/page/render.js +31 -5
  45. package/src/runtime/server/page/serialize_data.js +1 -1
  46. package/src/runtime/server/remote.js +237 -0
  47. package/src/runtime/server/respond.js +57 -36
  48. package/src/runtime/shared.js +61 -0
  49. package/src/types/global-private.d.ts +2 -0
  50. package/src/types/internal.d.ts +51 -4
  51. package/src/types/synthetic/$env+static+private.md +1 -1
  52. package/src/utils/routing.js +1 -1
  53. package/src/version.js +1 -1
  54. package/types/index.d.ts +266 -3
  55. package/types/index.d.ts.map +14 -1
  56. /package/src/{runtime → utils}/hash.js +0 -0
@@ -0,0 +1,129 @@
1
+ /** @import { ManifestData, ServerMetadata } from 'types' */
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { posixify } from '../../../utils/filesystem.js';
5
+ import { dedent } from '../../../core/sync/utils.js';
6
+ import { import_peer } from '../../../utils/import.js';
7
+
8
+ /**
9
+ * Moves the remote files to a sibling file and rewrites the original remote file to import from that sibling file,
10
+ * enhancing the remote functions with their hashed ID.
11
+ * This is not done through a self-import like during DEV because we want to treeshake prerendered remote functions
12
+ * later, which wouldn't work if we do a self-import and iterate over all exports (since we're reading them then).
13
+ * @param {string} out
14
+ * @param {ManifestData} manifest_data
15
+ */
16
+ export function build_remotes(out, manifest_data) {
17
+ const dir = `${out}/server/remote`;
18
+
19
+ for (const remote of manifest_data.remotes) {
20
+ const entry = `${dir}/${remote.hash}.js`;
21
+ const tmp = `${remote.hash}.tmp.js`;
22
+
23
+ fs.renameSync(entry, `${dir}/${tmp}`);
24
+ fs.writeFileSync(
25
+ entry,
26
+ dedent`
27
+ import * as $$_self_$$ from './${tmp}';
28
+
29
+ for (const [name, fn] of Object.entries($$_self_$$)) {
30
+ fn.__.id = '${remote.hash}/' + name;
31
+ fn.__.name = name;
32
+ }
33
+
34
+ export * from './${tmp}';
35
+ `
36
+ );
37
+ }
38
+ }
39
+
40
+
41
+ /**
42
+ * For each remote module, checks if there are treeshakeable prerendered remote functions,
43
+ * then accomplishes the treeshaking by rewriting the remote files to only include the non-prerendered imports,
44
+ * replacing the prerendered remote functions with a dummy function that should never be called,
45
+ * and doing a Vite build. This will not treeshake perfectly yet as everything except the remote files are treated as external,
46
+ * so it will not go into those files to check what can be treeshaken inside them.
47
+ * @param {string} out
48
+ * @param {ManifestData} manifest_data
49
+ * @param {ServerMetadata} metadata
50
+ */
51
+ export async function treeshake_prerendered_remotes(out, manifest_data, metadata) {
52
+ if (manifest_data.remotes.length === 0) {
53
+ return;
54
+ }
55
+
56
+ const dir = posixify(`${out}/server/remote`);
57
+
58
+ const vite = /** @type {typeof import('vite')} */ (await import_peer('vite'));
59
+ const remote_entry = posixify(`${out}/server/remote-entry.js`);
60
+
61
+ const prefix = 'optimized/';
62
+
63
+ const input = {
64
+ // include this file in the bundle, so that Rollup understands
65
+ // that functions like `prerender` are side-effect free
66
+ [path.basename(remote_entry.slice(0, -3))]: remote_entry
67
+ };
68
+
69
+ for (const remote of manifest_data.remotes) {
70
+ const exports = metadata.remotes.get(remote.hash);
71
+ if (!exports) throw new Error('An impossible situation occurred');
72
+
73
+ /** @type {string[]} */
74
+ const dynamic = [];
75
+
76
+ /** @type {string[]} */
77
+ const prerendered = [];
78
+
79
+ for (const [name, value] of exports) {
80
+ (value.dynamic ? dynamic : prerendered).push(name);
81
+ }
82
+
83
+ const remote_file = posixify(`${dir}/${remote.hash}.js`);
84
+
85
+ fs.writeFileSync(
86
+ remote_file,
87
+ dedent`
88
+ import { ${dynamic.join(', ')} } from './${remote.hash}.tmp.js';
89
+ import { prerender } from '../${path.basename(remote_entry)}';
90
+
91
+ ${prerendered.map((name) => `export const ${name} = prerender('unchecked', () => { throw new Error('Unexpectedly called prerender function. Did you forget to set { dynamic: true } ?') });`).join('\n')}
92
+
93
+ for (const [name, fn] of Object.entries({ ${Array.from(exports.keys()).join(', ')} })) {
94
+ fn.__.id = '${remote.hash}/' + name;
95
+ fn.__.name = name;
96
+ }
97
+
98
+ export { ${dynamic.join(', ')} };
99
+ `
100
+ );
101
+
102
+ input[prefix + remote.hash] = remote_file;
103
+ }
104
+
105
+ const bundle = await vite.build({
106
+ configFile: false,
107
+ build: {
108
+ ssr: true,
109
+ rollupOptions: {
110
+ external: (id) => {
111
+ if (id[0] === '.') return;
112
+ return !id.startsWith(dir);
113
+ },
114
+ input
115
+ }
116
+ }
117
+ });
118
+
119
+ // @ts-expect-error TypeScript doesn't know what type `bundle` is
120
+ for (const chunk of bundle.output) {
121
+ if (chunk.name.startsWith(prefix)) {
122
+ fs.writeFileSync(`${dir}/${chunk.fileName.slice(prefix.length)}`, chunk.code);
123
+ }
124
+ }
125
+
126
+ for (const remote of manifest_data.remotes) {
127
+ fs.unlinkSync(`${dir}/${remote.hash}.tmp.js`);
128
+ }
129
+ }
@@ -266,6 +266,12 @@ export async function dev(vite, vite_config, svelte_config) {
266
266
  };
267
267
  }),
268
268
  prerendered_routes: new Set(),
269
+ remotes: Object.fromEntries(
270
+ manifest_data.remotes.map((remote) => [
271
+ remote.hash,
272
+ () => vite.ssrLoadModule(remote.file)
273
+ ])
274
+ ),
269
275
  routes: compact(
270
276
  manifest_data.routes.map((route) => {
271
277
  if (!route.page && !route.endpoint) return null;
@@ -331,6 +337,7 @@ export async function dev(vite, vite_config, svelte_config) {
331
337
  if (
332
338
  file.startsWith(svelte_config.kit.files.routes + path.sep) ||
333
339
  file.startsWith(svelte_config.kit.files.params + path.sep) ||
340
+ svelte_config.kit.moduleExtensions.some((ext) => file.endsWith(`.remote${ext}`)) ||
334
341
  // in contrast to server hooks, client hooks are written to the client manifest
335
342
  // and therefore need rebuilding when they are added/removed
336
343
  file.startsWith(svelte_config.kit.files.hooks.client)
@@ -22,7 +22,7 @@ import { write_client_manifest } from '../../core/sync/write_client_manifest.js'
22
22
  import prerender from '../../core/postbuild/prerender.js';
23
23
  import analyse from '../../core/postbuild/analyse.js';
24
24
  import { s } from '../../utils/misc.js';
25
- import { hash } from '../../runtime/hash.js';
25
+ import { hash } from '../../utils/hash.js';
26
26
  import { dedent, isSvelte5Plus } from '../../core/sync/utils.js';
27
27
  import {
28
28
  env_dynamic_private,
@@ -36,6 +36,7 @@ import {
36
36
  } from './module_ids.js';
37
37
  import { import_peer } from '../../utils/import.js';
38
38
  import { compact } from '../../utils/array.js';
39
+ import { build_remotes, treeshake_prerendered_remotes } from './build/build_remote.js';
39
40
 
40
41
  const cwd = process.cwd();
41
42
 
@@ -167,6 +168,9 @@ let secondary_build_started = false;
167
168
  /** @type {import('types').ManifestData} */
168
169
  let manifest_data;
169
170
 
171
+ /** @type {import('types').ServerMetadata['remotes'] | undefined} only set at build time */
172
+ let remote_exports = undefined;
173
+
170
174
  /**
171
175
  * Returns the SvelteKit Vite plugin. Vite executes Rollup hooks as well as some of its own.
172
176
  * Background reading is available at:
@@ -213,6 +217,9 @@ async function kit({ svelte_config }) {
213
217
  const service_worker_entry_file = resolve_entry(kit.files.serviceWorker);
214
218
  const parsed_service_worker = path.parse(kit.files.serviceWorker);
215
219
 
220
+ const normalized_cwd = vite.normalizePath(cwd);
221
+ const normalized_lib = vite.normalizePath(kit.files.lib);
222
+
216
223
  /**
217
224
  * A map showing which features (such as `$app/server:read`) are defined
218
225
  * in which chunks, so that we can later determine which routes use which features
@@ -322,7 +329,8 @@ async function kit({ svelte_config }) {
322
329
  __SVELTEKIT_APP_VERSION_FILE__: s(`${kit.appDir}/version.json`),
323
330
  __SVELTEKIT_APP_VERSION_POLL_INTERVAL__: s(kit.version.pollInterval),
324
331
  __SVELTEKIT_DEV__: 'false',
325
- __SVELTEKIT_EMBEDDED__: kit.embedded ? 'true' : 'false',
332
+ __SVELTEKIT_EMBEDDED__: s(kit.embedded),
333
+ __SVELTEKIT_EXPERIMENTAL__REMOTE_FUNCTIONS__: s(kit.experimental.remoteFunctions),
326
334
  __SVELTEKIT_CLIENT_ROUTING__: kit.router.resolution === 'client' ? 'true' : 'false'
327
335
  };
328
336
 
@@ -333,7 +341,8 @@ async function kit({ svelte_config }) {
333
341
  new_config.define = {
334
342
  __SVELTEKIT_APP_VERSION_POLL_INTERVAL__: '0',
335
343
  __SVELTEKIT_DEV__: 'true',
336
- __SVELTEKIT_EMBEDDED__: kit.embedded ? 'true' : 'false',
344
+ __SVELTEKIT_EMBEDDED__: s(kit.embedded),
345
+ __SVELTEKIT_EXPERIMENTAL__REMOTE_FUNCTIONS__: s(kit.experimental.remoteFunctions),
337
346
  __SVELTEKIT_CLIENT_ROUTING__: kit.router.resolution === 'client' ? 'true' : 'false'
338
347
  };
339
348
 
@@ -381,8 +390,6 @@ async function kit({ svelte_config }) {
381
390
  parsed_importer.name === parsed_service_worker.name;
382
391
 
383
392
  if (importer_is_service_worker && id !== '$service-worker' && id !== '$env/static/public') {
384
- const normalized_cwd = vite.normalizePath(cwd);
385
- const normalized_lib = vite.normalizePath(kit.files.lib);
386
393
  throw new Error(
387
394
  `Cannot import ${normalize_id(
388
395
  id,
@@ -400,6 +407,9 @@ async function kit({ svelte_config }) {
400
407
  // ids with :$ don't work with reverse proxies like nginx
401
408
  return `\0virtual:${id.substring(1)}`;
402
409
  }
410
+ if (id === '__sveltekit/remote') {
411
+ return `${runtime_directory}/client/remote-functions/index.js`;
412
+ }
403
413
  if (id.startsWith('__sveltekit/')) {
404
414
  return `\0virtual:${id}`;
405
415
  }
@@ -413,8 +423,6 @@ async function kit({ svelte_config }) {
413
423
  : 'globalThis.__sveltekit_dev';
414
424
 
415
425
  if (options?.ssr === false && process.env.TEST !== 'true') {
416
- const normalized_cwd = vite.normalizePath(cwd);
417
- const normalized_lib = vite.normalizePath(kit.files.lib);
418
426
  if (
419
427
  is_illegal(id, {
420
428
  cwd: normalized_cwd,
@@ -580,6 +588,93 @@ Tips:
580
588
  }
581
589
  };
582
590
 
591
+ /** @type {import('vite').ViteDevServer} */
592
+ let dev_server;
593
+
594
+ /** @type {import('vite').Plugin} */
595
+ const plugin_remote = {
596
+ name: 'vite-plugin-sveltekit-remote',
597
+
598
+ configureServer(_dev_server) {
599
+ dev_server = _dev_server;
600
+ },
601
+
602
+ async transform(code, id, opts) {
603
+ if (!svelte_config.kit.moduleExtensions.some((ext) => id.endsWith(`.remote${ext}`))) {
604
+ return;
605
+ }
606
+
607
+ const file = posixify(path.relative(cwd, id));
608
+ const hashed = hash(file);
609
+
610
+ if (opts?.ssr) {
611
+ // in dev, add metadata to remote functions by self-importing
612
+ if (dev_server) {
613
+ return (
614
+ code +
615
+ dedent`
616
+ import * as $$_self_$$ from './${path.basename(id)}';
617
+ import { validate_remote_functions as $$_validate_$$ } from '@sveltejs/kit/internal';
618
+
619
+ $$_validate_$$($$_self_$$, ${s(file)});
620
+
621
+ for (const [name, fn] of Object.entries($$_self_$$)) {
622
+ fn.__.id = ${s(hashed)} + '/' + name;
623
+ fn.__.name = name;
624
+ }
625
+ `
626
+ );
627
+ }
628
+
629
+ // in prod, return as-is, and augment the build result instead.
630
+ // this allows us to treeshake non-dynamic `prerender` functions
631
+ return;
632
+ }
633
+
634
+ // For the client, read the exports and create a new module that only contains fetch functions with the correct metadata
635
+
636
+ /** @type {Map<string, import('types').RemoteInfo['type']>} */
637
+ const remotes = new Map();
638
+
639
+ // in dev, load the server module here (which will result in this hook
640
+ // being called again with `opts.ssr === true` if the module isn't
641
+ // already loaded) so we can determine what it exports
642
+ if (dev_server) {
643
+ const module = await dev_server.ssrLoadModule(id);
644
+
645
+ for (const [name, value] of Object.entries(module)) {
646
+ const type = value?.__?.type;
647
+ if (type) {
648
+ remotes.set(name, type);
649
+ }
650
+ }
651
+ }
652
+
653
+ // in prod, we already built and analysed the server code before
654
+ // building the client code, so `remote_exports` is populated
655
+ else if (remote_exports) {
656
+ const exports = remote_exports.get(hashed);
657
+ if (!exports) throw new Error('Expected to find metadata for remote file ' + id);
658
+
659
+ for (const [name, value] of exports) {
660
+ remotes.set(name, value.type);
661
+ }
662
+ }
663
+
664
+ let namespace = '__remote';
665
+ let uid = 1;
666
+ while (remotes.has(namespace)) namespace = `__remote${uid++}`;
667
+
668
+ const exports = Array.from(remotes).map(([name, type]) => {
669
+ return `export const ${name} = ${namespace}.${type}('${hashed}/${name}');`;
670
+ });
671
+
672
+ return {
673
+ code: `import * as ${namespace} from '__sveltekit/remote';\n\n${exports.join('\n')}\n`
674
+ };
675
+ }
676
+ };
677
+
583
678
  /** @type {import('vite').Plugin} */
584
679
  const plugin_compile = {
585
680
  name: 'vite-plugin-sveltekit-compile',
@@ -602,6 +697,7 @@ Tips:
602
697
  if (ssr) {
603
698
  input.index = `${runtime_directory}/server/index.js`;
604
699
  input.internal = `${kit.outDir}/generated/server/internal.js`;
700
+ input['remote-entry'] = `${runtime_directory}/app/server/remote/index.js`;
605
701
 
606
702
  // add entry points for every endpoint...
607
703
  manifest_data.routes.forEach((route) => {
@@ -633,6 +729,11 @@ Tips:
633
729
  const name = posixify(path.join('entries/matchers', key));
634
730
  input[name] = path.resolve(file);
635
731
  });
732
+
733
+ // ...and every .remote file
734
+ for (const remote of manifest_data.remotes) {
735
+ input[`remote/${remote.hash}`] = path.resolve(remote.file);
736
+ }
636
737
  } else if (svelte_config.kit.output.bundleStrategy !== 'split') {
637
738
  input['bundle'] = `${runtime_directory}/client/bundle.js`;
638
739
  } else {
@@ -834,6 +935,8 @@ Tips:
834
935
  output_config: svelte_config.output
835
936
  });
836
937
 
938
+ remote_exports = metadata.remotes;
939
+
837
940
  log.info('Building app');
838
941
 
839
942
  // create client build
@@ -984,6 +1087,9 @@ Tips:
984
1087
  static_exports
985
1088
  );
986
1089
 
1090
+ // ...make sure remote exports have their IDs assigned...
1091
+ build_remotes(out, manifest_data);
1092
+
987
1093
  // ...and prerender
988
1094
  const { prerendered, prerender_map } = await prerender({
989
1095
  hash: kit.router.type === 'hash',
@@ -1005,6 +1111,9 @@ Tips:
1005
1111
  })};\n`
1006
1112
  );
1007
1113
 
1114
+ // remove prerendered remote functions
1115
+ await treeshake_prerendered_remotes(out, manifest_data, metadata);
1116
+
1008
1117
  if (service_worker_entry_file) {
1009
1118
  if (kit.paths.assets) {
1010
1119
  throw new Error('Cannot use service worker alongside config.kit.paths.assets');
@@ -1075,7 +1184,13 @@ Tips:
1075
1184
  }
1076
1185
  };
1077
1186
 
1078
- return [plugin_setup, plugin_virtual_modules, plugin_guard, plugin_compile];
1187
+ return [
1188
+ plugin_setup,
1189
+ kit.experimental.remoteFunctions && plugin_remote,
1190
+ plugin_virtual_modules,
1191
+ plugin_guard,
1192
+ plugin_compile
1193
+ ].filter((p) => !!p);
1079
1194
  }
1080
1195
 
1081
1196
  /**
@@ -1,4 +1,5 @@
1
1
  import { fileURLToPath } from 'node:url';
2
+ import { posixify } from '../../utils/filesystem.js';
2
3
 
3
4
  export const env_static_private = '\0virtual:env/static/private';
4
5
  export const env_static_public = '\0virtual:env/static/public';
@@ -11,6 +12,6 @@ export const sveltekit_environment = '\0virtual:__sveltekit/environment';
11
12
  export const sveltekit_paths = '\0virtual:__sveltekit/paths';
12
13
  export const sveltekit_server = '\0virtual:__sveltekit/server';
13
14
 
14
- export const app_server = fileURLToPath(
15
- new URL('../../runtime/app/server/index.js', import.meta.url)
15
+ export const app_server = posixify(
16
+ fileURLToPath(new URL('../../runtime/app/server/index.js', import.meta.url))
16
17
  );
@@ -128,8 +128,10 @@ export async function preview(vite, vite_config, svelte_config) {
128
128
 
129
129
  const { pathname, search } = new URL(/** @type {string} */ (req.url), 'http://dummy');
130
130
 
131
+ const dir = pathname.startsWith(`/${svelte_config.kit.appDir}/remote/`) ? 'data' : 'pages';
132
+
131
133
  let filename = normalizePath(
132
- join(svelte_config.kit.outDir, 'output/prerendered/pages' + pathname)
134
+ join(svelte_config.kit.outDir, `output/prerendered/${dir}` + pathname)
133
135
  );
134
136
 
135
137
  try {
@@ -5,6 +5,7 @@ export {
5
5
  goto,
6
6
  invalidate,
7
7
  invalidateAll,
8
+ refreshAll,
8
9
  onNavigate,
9
10
  preloadCode,
10
11
  preloadData,
@@ -73,3 +73,5 @@ export function read(asset) {
73
73
  }
74
74
 
75
75
  export { getRequestEvent } from './event.js';
76
+
77
+ export { query, prerender, command, form } from './remote/index.js';
@@ -0,0 +1,91 @@
1
+ /** @import { RemoteCommand } from '@sveltejs/kit' */
2
+ /** @import { RemoteInfo, MaybePromise } from 'types' */
3
+ /** @import { StandardSchemaV1 } from '@standard-schema/spec' */
4
+ import { getRequestEvent } from '../event.js';
5
+ import { check_experimental, create_validator, run_remote_function } from './shared.js';
6
+ import { get_event_state } from '../../../server/event-state.js';
7
+
8
+ /**
9
+ * Creates a remote command. When called from the browser, the function will be invoked on the server via a `fetch` call.
10
+ *
11
+ * See [Remote functions](https://svelte.dev/docs/kit/remote-functions#command) for full documentation.
12
+ *
13
+ * @template Output
14
+ * @overload
15
+ * @param {() => Output} fn
16
+ * @returns {RemoteCommand<void, Output>}
17
+ * @since 2.27
18
+ */
19
+ /**
20
+ * Creates a remote command. When called from the browser, the function will be invoked on the server via a `fetch` call.
21
+ *
22
+ * See [Remote functions](https://svelte.dev/docs/kit/remote-functions#command) for full documentation.
23
+ *
24
+ * @template Input
25
+ * @template Output
26
+ * @overload
27
+ * @param {'unchecked'} validate
28
+ * @param {(arg: Input) => Output} fn
29
+ * @returns {RemoteCommand<Input, Output>}
30
+ * @since 2.27
31
+ */
32
+ /**
33
+ * Creates a remote command. When called from the browser, the function will be invoked on the server via a `fetch` call.
34
+ *
35
+ * See [Remote functions](https://svelte.dev/docs/kit/remote-functions#command) for full documentation.
36
+ *
37
+ * @template {StandardSchemaV1} Schema
38
+ * @template Output
39
+ * @overload
40
+ * @param {Schema} validate
41
+ * @param {(arg: StandardSchemaV1.InferOutput<Schema>) => Output} fn
42
+ * @returns {RemoteCommand<StandardSchemaV1.InferOutput<Schema>, Output>}
43
+ * @since 2.27
44
+ */
45
+ /**
46
+ * @template Input
47
+ * @template Output
48
+ * @param {any} validate_or_fn
49
+ * @param {(arg?: Input) => Output} [maybe_fn]
50
+ * @returns {RemoteCommand<Input, Output>}
51
+ * @since 2.27
52
+ */
53
+ /*@__NO_SIDE_EFFECTS__*/
54
+ export function command(validate_or_fn, maybe_fn) {
55
+ check_experimental('command');
56
+
57
+ /** @type {(arg?: Input) => Output} */
58
+ const fn = maybe_fn ?? validate_or_fn;
59
+
60
+ /** @type {(arg?: any) => MaybePromise<Input>} */
61
+ const validate = create_validator(validate_or_fn, maybe_fn);
62
+
63
+ /** @type {RemoteInfo} */
64
+ const __ = { type: 'command', id: '', name: '' };
65
+
66
+ /** @type {RemoteCommand<Input, Output> & { __: RemoteInfo }} */
67
+ const wrapper = (arg) => {
68
+ const event = getRequestEvent();
69
+
70
+ if (!event.isRemoteRequest) {
71
+ throw new Error(
72
+ `Cannot call a command (\`${__.name}(${maybe_fn ? '...' : ''})\`) during server-side rendering`
73
+ );
74
+ }
75
+
76
+ get_event_state(event).refreshes ??= {};
77
+
78
+ const promise = Promise.resolve(run_remote_function(event, true, arg, validate, fn));
79
+
80
+ // @ts-expect-error
81
+ promise.updates = () => {
82
+ throw new Error(`Cannot call '${__.name}(...).updates(...)' on the server`);
83
+ };
84
+
85
+ return /** @type {ReturnType<RemoteCommand<Input, Output>>} */ (promise);
86
+ };
87
+
88
+ Object.defineProperty(wrapper, '__', { value: __ });
89
+
90
+ return wrapper;
91
+ }
@@ -0,0 +1,124 @@
1
+ /** @import { RemoteForm } from '@sveltejs/kit' */
2
+ /** @import { RemoteInfo, MaybePromise } from 'types' */
3
+ import { getRequestEvent } from '../event.js';
4
+ import { check_experimental, run_remote_function } from './shared.js';
5
+ import { get_event_state } from '../../../server/event-state.js';
6
+
7
+ /**
8
+ * Creates a form object that can be spread onto a `<form>` element.
9
+ *
10
+ * See [Remote functions](https://svelte.dev/docs/kit/remote-functions#form) for full documentation.
11
+ *
12
+ * @template T
13
+ * @param {(data: FormData) => MaybePromise<T>} fn
14
+ * @returns {RemoteForm<T>}
15
+ * @since 2.27
16
+ */
17
+ /*@__NO_SIDE_EFFECTS__*/
18
+ // @ts-ignore we don't want to prefix `fn` with an underscore, as that will be user-visible
19
+ export function form(fn) {
20
+ check_experimental('form');
21
+
22
+ /**
23
+ * @param {string | number | boolean} [key]
24
+ */
25
+ function create_instance(key) {
26
+ /** @type {RemoteForm<T>} */
27
+ const instance = {};
28
+
29
+ instance.method = 'POST';
30
+ instance.onsubmit = () => {};
31
+
32
+ Object.defineProperty(instance, 'enhance', {
33
+ value: () => {
34
+ return { action: instance.action, method: instance.method, onsubmit: instance.onsubmit };
35
+ }
36
+ });
37
+
38
+ const button_props = {
39
+ type: 'submit',
40
+ onclick: () => {}
41
+ };
42
+
43
+ Object.defineProperty(button_props, 'enhance', {
44
+ value: () => {
45
+ return { type: 'submit', formaction: instance.buttonProps.formaction, onclick: () => {} };
46
+ }
47
+ });
48
+
49
+ Object.defineProperty(instance, 'buttonProps', {
50
+ value: button_props
51
+ });
52
+
53
+ /** @type {RemoteInfo} */
54
+ const __ = {
55
+ type: 'form',
56
+ name: '',
57
+ id: '',
58
+ /** @param {FormData} form_data */
59
+ fn: async (form_data) => {
60
+ const event = getRequestEvent();
61
+ const state = get_event_state(event);
62
+
63
+ state.refreshes ??= {};
64
+
65
+ const result = await run_remote_function(event, true, form_data, (d) => d, fn);
66
+
67
+ // We don't need to care about args or deduplicating calls, because uneval results are only relevant in full page reloads
68
+ // where only one form submission is active at the same time
69
+ if (!event.isRemoteRequest) {
70
+ state.form_result = [key, result];
71
+ }
72
+
73
+ return result;
74
+ }
75
+ };
76
+
77
+ Object.defineProperty(instance, '__', { value: __ });
78
+
79
+ Object.defineProperty(instance, 'action', {
80
+ get: () => `?/remote=${__.id}`,
81
+ enumerable: true
82
+ });
83
+
84
+ Object.defineProperty(button_props, 'formaction', {
85
+ get: () => `?/remote=${__.id}`,
86
+ enumerable: true
87
+ });
88
+
89
+ Object.defineProperty(instance, 'result', {
90
+ get() {
91
+ try {
92
+ const { form_result } = get_event_state(getRequestEvent());
93
+ return form_result && form_result[0] === key ? form_result[1] : undefined;
94
+ } catch {
95
+ return undefined;
96
+ }
97
+ }
98
+ });
99
+
100
+ if (key == undefined) {
101
+ Object.defineProperty(instance, 'for', {
102
+ /** @type {RemoteForm<any>['for']} */
103
+ value: (key) => {
104
+ const state = get_event_state(getRequestEvent());
105
+ let instance = (state.form_instances ??= new Map()).get(key);
106
+
107
+ if (!instance) {
108
+ instance = create_instance(key);
109
+ instance.__.id = `${__.id}/${encodeURIComponent(JSON.stringify(key))}`;
110
+ instance.__.name = __.name;
111
+
112
+ state.form_instances.set(key, instance);
113
+ }
114
+
115
+ return instance;
116
+ }
117
+ });
118
+ }
119
+
120
+ return instance;
121
+ }
122
+
123
+ return create_instance();
124
+ }
@@ -0,0 +1,4 @@
1
+ export { command } from './command.js';
2
+ export { form } from './form.js';
3
+ export { prerender } from './prerender.js';
4
+ export { query } from './query.js';