remote-entry.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. import { get_request_store, with_request_store } from "@sveltejs/kit/internal/server";
  2. import { parse } from "devalue";
  3. import { error, json } from "@sveltejs/kit";
  4. import { u as stringify_remote_arg, M as MUTATIVE_METHODS, v as create_field_proxy, w as normalize_issue, x as set_nested_value, y as flatten_issues, z as deep_set, k as stringify, f as create_remote_key, h as handle_error_and_jsonify } from "./chunks/shared.js";
  5. import { ValidationError, HttpError, SvelteKitError } from "@sveltejs/kit/internal";
  6. import { B as BROWSER } from "./chunks/false.js";
  7. import { b as base, c as app_dir, p as prerendering } from "./chunks/environment.js";
  8. function create_validator(validate_or_fn, maybe_fn) {
  9. if (!maybe_fn) {
  10. return (arg) => {
  11. if (arg !== void 0) {
  12. error(400, "Bad Request");
  13. }
  14. };
  15. }
  16. if (validate_or_fn === "unchecked") {
  17. return (arg) => arg;
  18. }
  19. if ("~standard" in validate_or_fn) {
  20. return async (arg) => {
  21. const { event, state } = get_request_store();
  22. const result = await validate_or_fn["~standard"].validate(arg);
  23. if (result.issues) {
  24. error(
  25. 400,
  26. await state.handleValidationError({
  27. issues: result.issues,
  28. event
  29. })
  30. );
  31. }
  32. return result.value;
  33. };
  34. }
  35. throw new Error(
  36. 'Invalid validator passed to remote function. Expected "unchecked" or a Standard Schema (https://standardschema.dev)'
  37. );
  38. }
  39. async function get_response(info, arg, state, get_result) {
  40. await 0;
  41. const cache = get_cache(info, state);
  42. return cache[stringify_remote_arg(arg, state.transport)] ??= get_result();
  43. }
  44. function parse_remote_response(data, transport) {
  45. const revivers = {};
  46. for (const key in transport) {
  47. revivers[key] = transport[key].decode;
  48. }
  49. return parse(data, revivers);
  50. }
  51. async function run_remote_function(event, state, allow_cookies, get_input, fn) {
  52. const store = {
  53. event: {
  54. ...event,
  55. setHeaders: () => {
  56. throw new Error("setHeaders is not allowed in remote functions");
  57. },
  58. cookies: {
  59. ...event.cookies,
  60. set: (name, value, opts) => {
  61. if (!allow_cookies) {
  62. throw new Error("Cannot set cookies in `query` or `prerender` functions");
  63. }
  64. if (opts.path && !opts.path.startsWith("/")) {
  65. throw new Error("Cookies set in remote functions must have an absolute path");
  66. }
  67. return event.cookies.set(name, value, opts);
  68. },
  69. delete: (name, opts) => {
  70. if (!allow_cookies) {
  71. throw new Error("Cannot delete cookies in `query` or `prerender` functions");
  72. }
  73. if (opts.path && !opts.path.startsWith("/")) {
  74. throw new Error("Cookies deleted in remote functions must have an absolute path");
  75. }
  76. return event.cookies.delete(name, opts);
  77. }
  78. }
  79. },
  80. state: {
  81. ...state,
  82. is_in_remote_function: true
  83. }
  84. };
  85. const input = await with_request_store(store, get_input);
  86. return with_request_store(store, () => fn(input));
  87. }
  88. function get_cache(info, state = get_request_store().state) {
  89. let cache = state.remote_data?.get(info);
  90. if (cache === void 0) {
  91. cache = {};
  92. (state.remote_data ??= /* @__PURE__ */ new Map()).set(info, cache);
  93. }
  94. return cache;
  95. }
  96. // @__NO_SIDE_EFFECTS__
  97. function command(validate_or_fn, maybe_fn) {
  98. const fn = maybe_fn ?? validate_or_fn;
  99. const validate = create_validator(validate_or_fn, maybe_fn);
  100. const __ = { type: "command", id: "", name: "" };
  101. const wrapper = (arg) => {
  102. const { event, state } = get_request_store();
  103. if (!state.allows_commands) {
  104. const disallowed_method = !MUTATIVE_METHODS.includes(event.request.method);
  105. throw new Error(
  106. `Cannot call a command (\`${__.name}(${maybe_fn ? "..." : ""})\`) ${disallowed_method ? `from a ${event.request.method} handler or ` : ""}during server-side rendering`
  107. );
  108. }
  109. state.refreshes ??= {};
  110. const promise = Promise.resolve(
  111. run_remote_function(event, state, true, () => validate(arg), fn)
  112. );
  113. promise.updates = () => {
  114. throw new Error(`Cannot call '${__.name}(...).updates(...)' on the server`);
  115. };
  116. return (
  117. /** @type {ReturnType<RemoteCommand<Input, Output>>} */
  118. promise
  119. );
  120. };
  121. Object.defineProperty(wrapper, "__", { value: __ });
  122. Object.defineProperty(wrapper, "pending", {
  123. get: () => 0
  124. });
  125. return wrapper;
  126. }
  127. // @__NO_SIDE_EFFECTS__
  128. function form(validate_or_fn, maybe_fn) {
  129. const fn = maybe_fn ?? validate_or_fn;
  130. const schema = !maybe_fn || validate_or_fn === "unchecked" ? null : (
  131. /** @type {any} */
  132. validate_or_fn
  133. );
  134. function create_instance(key) {
  135. const instance = {};
  136. instance.method = "POST";
  137. Object.defineProperty(instance, "enhance", {
  138. value: () => {
  139. return { action: instance.action, method: instance.method };
  140. }
  141. });
  142. const __ = {
  143. type: "form",
  144. name: "",
  145. id: "",
  146. fn: async (data, meta, form_data) => {
  147. const output = {};
  148. output.submission = true;
  149. const { event, state } = get_request_store();
  150. const validated = await schema?.["~standard"].validate(data);
  151. if (meta.validate_only) {
  152. return validated?.issues?.map((issue) => normalize_issue(issue, true)) ?? [];
  153. }
  154. if (validated?.issues !== void 0) {
  155. handle_issues(output, validated.issues, form_data);
  156. } else {
  157. if (validated !== void 0) {
  158. data = validated.value;
  159. }
  160. state.refreshes ??= {};
  161. const issue = create_issues();
  162. try {
  163. output.result = await run_remote_function(
  164. event,
  165. state,
  166. true,
  167. () => data,
  168. (data2) => !maybe_fn ? fn() : fn(data2, issue)
  169. );
  170. } catch (e) {
  171. if (e instanceof ValidationError) {
  172. handle_issues(output, e.issues, form_data);
  173. } else {
  174. throw e;
  175. }
  176. }
  177. }
  178. if (!event.isRemoteRequest) {
  179. get_cache(__, state)[""] ??= output;
  180. }
  181. return output;
  182. }
  183. };
  184. Object.defineProperty(instance, "__", { value: __ });
  185. Object.defineProperty(instance, "action", {
  186. get: () => `?/remote=${__.id}`,
  187. enumerable: true
  188. });
  189. Object.defineProperty(instance, "fields", {
  190. get() {
  191. return create_field_proxy(
  192. {},
  193. () => get_cache(__)?.[""]?.input ?? {},
  194. (path, value) => {
  195. const cache = get_cache(__);
  196. const data = cache[""];
  197. if (data?.submission) {
  198. return;
  199. }
  200. if (path.length === 0) {
  201. (cache[""] ??= {}).input = value;
  202. return;
  203. }
  204. const input = data?.input ?? {};
  205. deep_set(input, path.map(String), value);
  206. (cache[""] ??= {}).input = input;
  207. },
  208. () => flatten_issues(get_cache(__)?.[""]?.issues ?? [])
  209. );
  210. }
  211. });
  212. Object.defineProperty(instance, "result", {
  213. get() {
  214. try {
  215. return get_cache(__)?.[""]?.result;
  216. } catch {
  217. return void 0;
  218. }
  219. }
  220. });
  221. Object.defineProperty(instance, "pending", {
  222. get: () => 0
  223. });
  224. Object.defineProperty(instance, "preflight", {
  225. // preflight is a noop on the server
  226. value: () => instance
  227. });
  228. Object.defineProperty(instance, "validate", {
  229. value: () => {
  230. throw new Error("Cannot call validate() on the server");
  231. }
  232. });
  233. if (key == void 0) {
  234. Object.defineProperty(instance, "for", {
  235. /** @type {RemoteForm<any, any>['for']} */
  236. value: (key2) => {
  237. const { state } = get_request_store();
  238. const cache_key = __.id + "|" + JSON.stringify(key2);
  239. let instance2 = (state.form_instances ??= /* @__PURE__ */ new Map()).get(cache_key);
  240. if (!instance2) {
  241. instance2 = create_instance(key2);
  242. instance2.__.id = `${__.id}/${encodeURIComponent(JSON.stringify(key2))}`;
  243. instance2.__.name = __.name;
  244. state.form_instances.set(cache_key, instance2);
  245. }
  246. return instance2;
  247. }
  248. });
  249. }
  250. return instance;
  251. }
  252. return create_instance();
  253. }
  254. function handle_issues(output, issues, form_data) {
  255. output.issues = issues.map((issue) => normalize_issue(issue, true));
  256. if (form_data) {
  257. output.input = {};
  258. for (let key of form_data.keys()) {
  259. if (/^[.\]]?_/.test(key)) continue;
  260. const is_array = key.endsWith("[]");
  261. const values = form_data.getAll(key).filter((value) => typeof value === "string");
  262. if (is_array) key = key.slice(0, -2);
  263. set_nested_value(
  264. /** @type {Record<string, any>} */
  265. output.input,
  266. key,
  267. is_array ? values : values[0]
  268. );
  269. }
  270. }
  271. }
  272. function create_issues() {
  273. return (
  274. /** @type {InvalidField<any>} */
  275. new Proxy(
  276. /** @param {string} message */
  277. (message) => {
  278. if (typeof message !== "string") {
  279. throw new Error(
  280. "`invalid` should now be imported from `@sveltejs/kit` to throw validation issues. The second parameter provided to the form function (renamed to `issue`) is still used to construct issues, e.g. `invalid(issue.field('message'))`. For more info see https://github.com/sveltejs/kit/pulls/14768"
  281. );
  282. }
  283. return create_issue(message);
  284. },
  285. {
  286. get(target, prop) {
  287. if (typeof prop === "symbol") return (
  288. /** @type {any} */
  289. target[prop]
  290. );
  291. return create_issue_proxy(prop, []);
  292. }
  293. }
  294. )
  295. );
  296. function create_issue(message, path = []) {
  297. return {
  298. message,
  299. path
  300. };
  301. }
  302. function create_issue_proxy(key, path) {
  303. const new_path = [...path, key];
  304. const issue_func = (message) => create_issue(message, new_path);
  305. return new Proxy(issue_func, {
  306. get(target, prop) {
  307. if (typeof prop === "symbol") return (
  308. /** @type {any} */
  309. target[prop]
  310. );
  311. if (/^\d+$/.test(prop)) {
  312. return create_issue_proxy(parseInt(prop, 10), new_path);
  313. }
  314. return create_issue_proxy(prop, new_path);
  315. }
  316. });
  317. }
  318. }
  319. // @__NO_SIDE_EFFECTS__
  320. function prerender(validate_or_fn, fn_or_options, maybe_options) {
  321. const maybe_fn = typeof fn_or_options === "function" ? fn_or_options : void 0;
  322. const options = maybe_options ?? (maybe_fn ? void 0 : fn_or_options);
  323. const fn = maybe_fn ?? validate_or_fn;
  324. const validate = create_validator(validate_or_fn, maybe_fn);
  325. const __ = {
  326. type: "prerender",
  327. id: "",
  328. name: "",
  329. has_arg: !!maybe_fn,
  330. inputs: options?.inputs,
  331. dynamic: options?.dynamic
  332. };
  333. const wrapper = (arg) => {
  334. const promise = (async () => {
  335. const { event, state } = get_request_store();
  336. const payload = stringify_remote_arg(arg, state.transport);
  337. const id = __.id;
  338. const url = `${base}/${app_dir}/remote/${id}${payload ? `/${payload}` : ""}`;
  339. if (!state.prerendering && !BROWSER && !event.isRemoteRequest) {
  340. try {
  341. return await get_response(__, arg, state, async () => {
  342. const key = stringify_remote_arg(arg, state.transport);
  343. const cache = get_cache(__, state);
  344. const promise3 = cache[key] ??= fetch(new URL(url, event.url.origin).href).then(
  345. async (response) => {
  346. if (!response.ok) {
  347. throw new Error("Prerendered response not found");
  348. }
  349. const prerendered = await response.json();
  350. if (prerendered.type === "error") {
  351. error(prerendered.status, prerendered.error);
  352. }
  353. return prerendered.result;
  354. }
  355. );
  356. return parse_remote_response(await promise3, state.transport);
  357. });
  358. } catch {
  359. }
  360. }
  361. if (state.prerendering?.remote_responses.has(url)) {
  362. return (
  363. /** @type {Promise<any>} */
  364. state.prerendering.remote_responses.get(url)
  365. );
  366. }
  367. const promise2 = get_response(
  368. __,
  369. arg,
  370. state,
  371. () => run_remote_function(event, state, false, () => validate(arg), fn)
  372. );
  373. if (state.prerendering) {
  374. state.prerendering.remote_responses.set(url, promise2);
  375. }
  376. const result = await promise2;
  377. if (state.prerendering) {
  378. const body = { type: "result", result: stringify(result, state.transport) };
  379. state.prerendering.dependencies.set(url, {
  380. body: JSON.stringify(body),
  381. response: json(body)
  382. });
  383. }
  384. return result;
  385. })();
  386. promise.catch(() => {
  387. });
  388. return (
  389. /** @type {RemoteResource<Output>} */
  390. promise
  391. );
  392. };
  393. Object.defineProperty(wrapper, "__", { value: __ });
  394. return wrapper;
  395. }
  396. // @__NO_SIDE_EFFECTS__
  397. function query(validate_or_fn, maybe_fn) {
  398. const fn = maybe_fn ?? validate_or_fn;
  399. const validate = create_validator(validate_or_fn, maybe_fn);
  400. const __ = { type: "query", id: "", name: "" };
  401. const wrapper = (arg) => {
  402. if (prerendering) {
  403. throw new Error(
  404. `Cannot call query '${__.name}' while prerendering, as prerendered pages need static data. Use 'prerender' from $app/server instead`
  405. );
  406. }
  407. const { event, state } = get_request_store();
  408. const get_remote_function_result = () => run_remote_function(event, state, false, () => validate(arg), fn);
  409. const promise = get_response(__, arg, state, get_remote_function_result);
  410. promise.catch(() => {
  411. });
  412. promise.set = (value) => update_refresh_value(get_refresh_context(__, "set", arg), value);
  413. promise.refresh = () => {
  414. const refresh_context = get_refresh_context(__, "refresh", arg);
  415. const is_immediate_refresh = !refresh_context.cache[refresh_context.cache_key];
  416. const value = is_immediate_refresh ? promise : get_remote_function_result();
  417. return update_refresh_value(refresh_context, value, is_immediate_refresh);
  418. };
  419. promise.withOverride = () => {
  420. throw new Error(`Cannot call '${__.name}.withOverride()' on the server`);
  421. };
  422. return (
  423. /** @type {RemoteQuery<Output>} */
  424. promise
  425. );
  426. };
  427. Object.defineProperty(wrapper, "__", { value: __ });
  428. return wrapper;
  429. }
  430. // @__NO_SIDE_EFFECTS__
  431. function batch(validate_or_fn, maybe_fn) {
  432. const fn = maybe_fn ?? validate_or_fn;
  433. const validate = create_validator(validate_or_fn, maybe_fn);
  434. const __ = {
  435. type: "query_batch",
  436. id: "",
  437. name: "",
  438. run: async (args, options) => {
  439. const { event, state } = get_request_store();
  440. return run_remote_function(
  441. event,
  442. state,
  443. false,
  444. async () => Promise.all(args.map(validate)),
  445. async (input) => {
  446. const get_result = await fn(input);
  447. return Promise.all(
  448. input.map(async (arg, i) => {
  449. try {
  450. return { type: "result", data: get_result(arg, i) };
  451. } catch (error2) {
  452. return {
  453. type: "error",
  454. error: await handle_error_and_jsonify(event, state, options, error2),
  455. status: error2 instanceof HttpError || error2 instanceof SvelteKitError ? error2.status : 500
  456. };
  457. }
  458. })
  459. );
  460. }
  461. );
  462. }
  463. };
  464. let batching = { args: [], resolvers: [] };
  465. const wrapper = (arg) => {
  466. if (prerendering) {
  467. throw new Error(
  468. `Cannot call query.batch '${__.name}' while prerendering, as prerendered pages need static data. Use 'prerender' from $app/server instead`
  469. );
  470. }
  471. const { event, state } = get_request_store();
  472. const get_remote_function_result = () => {
  473. return new Promise((resolve, reject) => {
  474. batching.args.push(arg);
  475. batching.resolvers.push({ resolve, reject });
  476. if (batching.args.length > 1) return;
  477. setTimeout(async () => {
  478. const batched = batching;
  479. batching = { args: [], resolvers: [] };
  480. try {
  481. return await run_remote_function(
  482. event,
  483. state,
  484. false,
  485. async () => Promise.all(batched.args.map(validate)),
  486. async (input) => {
  487. const get_result = await fn(input);
  488. for (let i = 0; i < batched.resolvers.length; i++) {
  489. try {
  490. batched.resolvers[i].resolve(get_result(input[i], i));
  491. } catch (error2) {
  492. batched.resolvers[i].reject(error2);
  493. }
  494. }
  495. }
  496. );
  497. } catch (error2) {
  498. for (const resolver of batched.resolvers) {
  499. resolver.reject(error2);
  500. }
  501. }
  502. }, 0);
  503. });
  504. };
  505. const promise = get_response(__, arg, state, get_remote_function_result);
  506. promise.catch(() => {
  507. });
  508. promise.set = (value) => update_refresh_value(get_refresh_context(__, "set", arg), value);
  509. promise.refresh = () => {
  510. const refresh_context = get_refresh_context(__, "refresh", arg);
  511. const is_immediate_refresh = !refresh_context.cache[refresh_context.cache_key];
  512. const value = is_immediate_refresh ? promise : get_remote_function_result();
  513. return update_refresh_value(refresh_context, value, is_immediate_refresh);
  514. };
  515. promise.withOverride = () => {
  516. throw new Error(`Cannot call '${__.name}.withOverride()' on the server`);
  517. };
  518. return (
  519. /** @type {RemoteQuery<Output>} */
  520. promise
  521. );
  522. };
  523. Object.defineProperty(wrapper, "__", { value: __ });
  524. return wrapper;
  525. }
  526. Object.defineProperty(query, "batch", { value: batch, enumerable: true });
  527. function get_refresh_context(__, action, arg) {
  528. const { state } = get_request_store();
  529. const { refreshes } = state;
  530. if (!refreshes) {
  531. const name = __.type === "query_batch" ? `query.batch '${__.name}'` : `query '${__.name}'`;
  532. throw new Error(
  533. `Cannot call ${action} on ${name} because it is not executed in the context of a command/form remote function`
  534. );
  535. }
  536. const cache = get_cache(__, state);
  537. const cache_key = stringify_remote_arg(arg, state.transport);
  538. const refreshes_key = create_remote_key(__.id, cache_key);
  539. return { __, state, refreshes, refreshes_key, cache, cache_key };
  540. }
  541. function update_refresh_value({ __, refreshes, refreshes_key, cache, cache_key }, value, is_immediate_refresh = false) {
  542. const promise = Promise.resolve(value);
  543. if (!is_immediate_refresh) {
  544. cache[cache_key] = promise;
  545. }
  546. if (__.id) {
  547. refreshes[refreshes_key] = promise;
  548. }
  549. return promise.then(() => {
  550. });
  551. }
  552. export {
  553. command,
  554. form,
  555. prerender,
  556. query
  557. };