@sveltejs/kit 1.0.0-next.44 → 1.0.0-next.442

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 (153) hide show
  1. package/README.md +12 -9
  2. package/package.json +94 -63
  3. package/src/cli.js +112 -0
  4. package/src/core/adapt/builder.js +223 -0
  5. package/src/core/adapt/index.js +19 -0
  6. package/src/core/config/index.js +86 -0
  7. package/src/core/config/options.js +488 -0
  8. package/src/core/config/types.d.ts +1 -0
  9. package/src/core/constants.js +5 -0
  10. package/src/core/env.js +97 -0
  11. package/src/core/generate_manifest/index.js +78 -0
  12. package/src/core/prerender/crawl.js +194 -0
  13. package/src/core/prerender/prerender.js +380 -0
  14. package/src/core/prerender/queue.js +80 -0
  15. package/src/core/sync/create_manifest_data/index.js +452 -0
  16. package/src/core/sync/create_manifest_data/types.d.ts +37 -0
  17. package/src/core/sync/sync.js +59 -0
  18. package/src/core/sync/utils.js +33 -0
  19. package/src/core/sync/write_ambient.js +27 -0
  20. package/src/core/sync/write_client_manifest.js +92 -0
  21. package/src/core/sync/write_matchers.js +25 -0
  22. package/src/core/sync/write_root.js +91 -0
  23. package/src/core/sync/write_tsconfig.js +195 -0
  24. package/src/core/sync/write_types/index.js +580 -0
  25. package/src/core/sync/write_types/test/layout/+layout.js +5 -0
  26. package/src/core/sync/write_types/test/layout/+layout.server.js +5 -0
  27. package/src/core/sync/write_types/test/layout/+layout.svelte +0 -0
  28. package/src/core/sync/write_types/test/layout/+page.js +5 -0
  29. package/src/core/sync/write_types/test/layout/+page.server.js +5 -0
  30. package/src/core/sync/write_types/test/layout/+page.svelte +0 -0
  31. package/src/core/sync/write_types/test/layout/_expected/$types.d.ts +67 -0
  32. package/src/core/sync/write_types/test/layout-advanced/(main)/+layout.server.js +5 -0
  33. package/src/core/sync/write_types/test/layout-advanced/(main)/+layout.svelte +0 -0
  34. package/src/core/sync/write_types/test/layout-advanced/(main)/+page.js +5 -0
  35. package/src/core/sync/write_types/test/layout-advanced/(main)/+page@.svelte +0 -0
  36. package/src/core/sync/write_types/test/layout-advanced/(main)/sub/+page.js +5 -0
  37. package/src/core/sync/write_types/test/layout-advanced/(main)/sub/+page.svelte +0 -0
  38. package/src/core/sync/write_types/test/layout-advanced/+layout.js +5 -0
  39. package/src/core/sync/write_types/test/layout-advanced/+layout.svelte +0 -0
  40. package/src/core/sync/write_types/test/layout-advanced/_expected/$types.d.ts +32 -0
  41. package/src/core/sync/write_types/test/layout-advanced/_expected/(main)/$types.d.ts +42 -0
  42. package/src/core/sync/write_types/test/layout-advanced/_expected/(main)/sub/$types.d.ts +33 -0
  43. package/src/core/sync/write_types/test/simple-page-server-and-shared/+page.js +5 -0
  44. package/src/core/sync/write_types/test/simple-page-server-and-shared/+page.server.js +5 -0
  45. package/src/core/sync/write_types/test/simple-page-server-and-shared/+page.svelte +0 -0
  46. package/src/core/sync/write_types/test/simple-page-server-and-shared/_expected/$types.d.ts +44 -0
  47. package/src/core/sync/write_types/test/simple-page-server-only/+page.server.js +5 -0
  48. package/src/core/sync/write_types/test/simple-page-server-only/+page.svelte +0 -0
  49. package/src/core/sync/write_types/test/simple-page-server-only/_expected/$types.d.ts +30 -0
  50. package/src/core/sync/write_types/test/simple-page-shared-only/+page.js +5 -0
  51. package/src/core/sync/write_types/test/simple-page-shared-only/+page.svelte +0 -0
  52. package/src/core/sync/write_types/test/simple-page-shared-only/_expected/$types.d.ts +33 -0
  53. package/src/core/sync/write_types/test/slugs/+layout.js +1 -0
  54. package/src/core/sync/write_types/test/slugs/+layout.svelte +1 -0
  55. package/src/core/sync/write_types/test/slugs/[...rest]/+page.js +3 -0
  56. package/src/core/sync/write_types/test/slugs/[...rest]/+page.svelte +0 -0
  57. package/src/core/sync/write_types/test/slugs/[slug]/+page.js +3 -0
  58. package/src/core/sync/write_types/test/slugs/[slug]/+page.svelte +0 -0
  59. package/src/core/sync/write_types/test/slugs/_expected/$types.d.ts +32 -0
  60. package/src/core/sync/write_types/test/slugs/_expected/[...rest]/$types.d.ts +29 -0
  61. package/src/core/sync/write_types/test/slugs/_expected/[slug]/$types.d.ts +29 -0
  62. package/src/core/sync/write_types/test/slugs-layout-not-all-pages-have-load/+layout.js +1 -0
  63. package/src/core/sync/write_types/test/slugs-layout-not-all-pages-have-load/+layout.svelte +1 -0
  64. package/src/core/sync/write_types/test/slugs-layout-not-all-pages-have-load/_expected/$types.d.ts +30 -0
  65. package/src/core/sync/write_types/test/slugs-layout-not-all-pages-have-load/_expected/nested/$types.d.ts +32 -0
  66. package/src/core/sync/write_types/test/slugs-layout-not-all-pages-have-load/_expected/nested/[...rest]/$types.d.ts +37 -0
  67. package/src/core/sync/write_types/test/slugs-layout-not-all-pages-have-load/_expected/nested/[slug]/$types.d.ts +17 -0
  68. package/src/core/sync/write_types/test/slugs-layout-not-all-pages-have-load/nested/+layout.js +1 -0
  69. package/src/core/sync/write_types/test/slugs-layout-not-all-pages-have-load/nested/+layout.svelte +1 -0
  70. package/src/core/sync/write_types/test/slugs-layout-not-all-pages-have-load/nested/[...rest]/+page.js +3 -0
  71. package/src/core/sync/write_types/test/slugs-layout-not-all-pages-have-load/nested/[...rest]/+page.svelte +0 -0
  72. package/src/core/sync/write_types/test/slugs-layout-not-all-pages-have-load/nested/[slug]/+page@.svelte +0 -0
  73. package/src/core/utils.js +70 -0
  74. package/src/hooks.js +26 -0
  75. package/src/index/index.js +45 -0
  76. package/src/index/private.js +33 -0
  77. package/src/node/index.js +145 -0
  78. package/src/node/polyfills.js +40 -0
  79. package/src/runtime/app/env.js +11 -0
  80. package/src/runtime/app/navigation.js +22 -0
  81. package/src/runtime/app/paths.js +1 -0
  82. package/src/runtime/app/stores.js +102 -0
  83. package/src/runtime/client/ambient.d.ts +18 -0
  84. package/src/runtime/client/client.js +1362 -0
  85. package/src/runtime/client/fetcher.js +60 -0
  86. package/src/runtime/client/parse.js +41 -0
  87. package/src/runtime/client/singletons.js +21 -0
  88. package/src/runtime/client/start.js +46 -0
  89. package/src/runtime/client/types.d.ts +86 -0
  90. package/src/runtime/client/utils.js +113 -0
  91. package/src/runtime/components/error.svelte +16 -0
  92. package/{assets → src/runtime}/components/layout.svelte +0 -0
  93. package/src/runtime/env/dynamic/private.js +1 -0
  94. package/src/runtime/env/dynamic/public.js +1 -0
  95. package/src/runtime/env-private.js +7 -0
  96. package/src/runtime/env-public.js +7 -0
  97. package/src/runtime/env.js +6 -0
  98. package/src/runtime/hash.js +16 -0
  99. package/src/runtime/paths.js +11 -0
  100. package/src/runtime/server/endpoint.js +50 -0
  101. package/src/runtime/server/index.js +492 -0
  102. package/src/runtime/server/page/cookie.js +25 -0
  103. package/src/runtime/server/page/crypto.js +239 -0
  104. package/src/runtime/server/page/csp.js +249 -0
  105. package/src/runtime/server/page/fetch.js +266 -0
  106. package/src/runtime/server/page/index.js +408 -0
  107. package/src/runtime/server/page/load_data.js +168 -0
  108. package/src/runtime/server/page/render.js +361 -0
  109. package/src/runtime/server/page/respond_with_error.js +95 -0
  110. package/src/runtime/server/page/types.d.ts +44 -0
  111. package/src/runtime/server/utils.js +116 -0
  112. package/src/utils/array.js +9 -0
  113. package/src/utils/error.js +22 -0
  114. package/src/utils/escape.js +104 -0
  115. package/src/utils/filesystem.js +108 -0
  116. package/src/utils/functions.js +16 -0
  117. package/src/utils/http.js +55 -0
  118. package/src/utils/misc.js +1 -0
  119. package/src/utils/routing.js +146 -0
  120. package/src/utils/url.js +142 -0
  121. package/src/vite/build/build_server.js +348 -0
  122. package/src/vite/build/build_service_worker.js +90 -0
  123. package/src/vite/build/utils.js +160 -0
  124. package/src/vite/dev/index.js +543 -0
  125. package/src/vite/index.js +578 -0
  126. package/src/vite/preview/index.js +186 -0
  127. package/src/vite/types.d.ts +3 -0
  128. package/src/vite/utils.js +345 -0
  129. package/svelte-kit.js +1 -1
  130. package/types/ambient.d.ts +366 -0
  131. package/types/index.d.ts +345 -0
  132. package/types/internal.d.ts +374 -0
  133. package/types/private.d.ts +209 -0
  134. package/CHANGELOG.md +0 -437
  135. package/assets/components/error.svelte +0 -13
  136. package/assets/runtime/app/env.js +0 -5
  137. package/assets/runtime/app/navigation.js +0 -41
  138. package/assets/runtime/app/paths.js +0 -1
  139. package/assets/runtime/app/stores.js +0 -93
  140. package/assets/runtime/chunks/utils.js +0 -19
  141. package/assets/runtime/internal/singletons.js +0 -23
  142. package/assets/runtime/internal/start.js +0 -770
  143. package/assets/runtime/paths.js +0 -12
  144. package/dist/chunks/index.js +0 -3521
  145. package/dist/chunks/index2.js +0 -587
  146. package/dist/chunks/index3.js +0 -246
  147. package/dist/chunks/index4.js +0 -545
  148. package/dist/chunks/index5.js +0 -761
  149. package/dist/chunks/index6.js +0 -322
  150. package/dist/chunks/standard.js +0 -99
  151. package/dist/chunks/utils.js +0 -83
  152. package/dist/cli.js +0 -546
  153. package/dist/ssr.js +0 -2580
@@ -0,0 +1,116 @@
1
+ import { HttpError } from '../../index/private.js';
2
+
3
+ /** @param {any} body */
4
+ export function is_pojo(body) {
5
+ if (typeof body !== 'object') return false;
6
+
7
+ if (body) {
8
+ if (body instanceof Uint8Array) return false;
9
+ if (body instanceof ReadableStream) return false;
10
+
11
+ // if body is a node Readable, throw an error
12
+ // TODO remove this for 1.0
13
+ if (body._readableState && typeof body.pipe === 'function') {
14
+ throw new Error('Node streams are no longer supported — use a ReadableStream instead');
15
+ }
16
+ }
17
+
18
+ return true;
19
+ }
20
+
21
+ /**
22
+ * Serialize an error into a JSON string through `error_to_pojo`.
23
+ * This is necessary because `JSON.stringify(error) === '{}'`
24
+ *
25
+ * @param {Error | HttpError} error
26
+ * @param {(error: Error) => string | undefined} get_stack
27
+ */
28
+ export function serialize_error(error, get_stack) {
29
+ return JSON.stringify(error_to_pojo(error, get_stack));
30
+ }
31
+
32
+ /**
33
+ * Transform an error into a POJO, by copying its `name`, `message`
34
+ * and (in dev) `stack`, plus any custom properties, plus recursively
35
+ * serialized `cause` properties.
36
+ * Our own HttpError gets a meta property attached so we can identify it on the client.
37
+ *
38
+ * @param {HttpError | Error } error
39
+ * @param {(error: Error) => string | undefined} get_stack
40
+ */
41
+ export function error_to_pojo(error, get_stack) {
42
+ if (error instanceof HttpError) {
43
+ return /** @type {import('./page/types').SerializedHttpError} */ ({
44
+ message: error.message,
45
+ status: error.status,
46
+ __is_http_error: true // TODO we should probably make this unnecessary
47
+ });
48
+ }
49
+
50
+ const {
51
+ name,
52
+ message,
53
+ stack,
54
+ // @ts-expect-error i guess typescript doesn't know about error.cause yet
55
+ cause,
56
+ ...custom
57
+ } = error;
58
+
59
+ /** @type {Record<string, any>} */
60
+ const object = { name, message, stack: get_stack(error) };
61
+
62
+ if (cause) object.cause = error_to_pojo(cause, get_stack);
63
+
64
+ for (const key in custom) {
65
+ // @ts-expect-error
66
+ object[key] = custom[key];
67
+ }
68
+
69
+ return object;
70
+ }
71
+
72
+ // TODO: Remove for 1.0
73
+ /** @param {Record<string, any>} mod */
74
+ export function check_method_names(mod) {
75
+ ['get', 'post', 'put', 'patch', 'del'].forEach((m) => {
76
+ if (m in mod) {
77
+ const replacement = m === 'del' ? 'DELETE' : m.toUpperCase();
78
+ throw Error(
79
+ `Endpoint method "${m}" has changed to "${replacement}". See https://github.com/sveltejs/kit/discussions/5359 for more information.`
80
+ );
81
+ }
82
+ });
83
+ }
84
+
85
+ /** @type {import('types').SSRErrorPage} */
86
+ export const GENERIC_ERROR = {
87
+ id: '__error'
88
+ };
89
+
90
+ /**
91
+ * @param {Partial<Record<import('types').HttpMethod, any>>} mod
92
+ * @param {import('types').HttpMethod} method
93
+ */
94
+ export function method_not_allowed(mod, method) {
95
+ return new Response(`${method} method not allowed`, {
96
+ status: 405,
97
+ headers: {
98
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405
99
+ // "The server must generate an Allow header field in a 405 status code response"
100
+ allow: allowed_methods(mod).join(', ')
101
+ }
102
+ });
103
+ }
104
+
105
+ /** @param {Partial<Record<import('types').HttpMethod, any>>} mod */
106
+ export function allowed_methods(mod) {
107
+ const allowed = [];
108
+
109
+ for (const method in ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']) {
110
+ if (method in mod) allowed.push(method);
111
+ }
112
+
113
+ if (mod.GET || mod.HEAD) allowed.push('HEAD');
114
+
115
+ return allowed;
116
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Removes nullish values from an array.
3
+ *
4
+ * @template T
5
+ * @param {Array<T>} arr
6
+ */
7
+ export function compact(arr) {
8
+ return arr.filter(/** @returns {val is NonNullable<T>} */ (val) => val != null);
9
+ }
@@ -0,0 +1,22 @@
1
+ import { HttpError, Redirect } from '../index/private.js';
2
+
3
+ /**
4
+ * @param {unknown} err
5
+ * @return {Error}
6
+ */
7
+ export function coalesce_to_error(err) {
8
+ return err instanceof Error ||
9
+ (err && /** @type {any} */ (err).name && /** @type {any} */ (err).message)
10
+ ? /** @type {Error} */ (err)
11
+ : new Error(JSON.stringify(err));
12
+ }
13
+
14
+ /**
15
+ * This is an identity function that exists to make TypeScript less
16
+ * paranoid about people throwing things that aren't errors, which
17
+ * frankly is not something we should care about
18
+ * @param {unknown} error
19
+ */
20
+ export function normalize_error(error) {
21
+ return /** @type {Redirect | HttpError | Error} */ (error);
22
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Inside a script element, only `</script` and `<!--` hold special meaning to the HTML parser.
3
+ *
4
+ * The first closes the script element, so everything after is treated as raw HTML.
5
+ * The second disables further parsing until `-->`, so the script element might be unexpectedly
6
+ * kept open until until an unrelated HTML comment in the page.
7
+ *
8
+ * U+2028 LINE SEPARATOR and U+2029 PARAGRAPH SEPARATOR are escaped for the sake of pre-2018
9
+ * browsers.
10
+ *
11
+ * @see tests for unsafe parsing examples.
12
+ * @see https://html.spec.whatwg.org/multipage/scripting.html#restrictions-for-contents-of-script-elements
13
+ * @see https://html.spec.whatwg.org/multipage/syntax.html#cdata-rcdata-restrictions
14
+ * @see https://html.spec.whatwg.org/multipage/parsing.html#script-data-state
15
+ * @see https://html.spec.whatwg.org/multipage/parsing.html#script-data-double-escaped-state
16
+ * @see https://github.com/tc39/proposal-json-superset
17
+ * @type {Record<string, string>}
18
+ */
19
+ const render_json_payload_script_dict = {
20
+ '<': '\\u003C',
21
+ '\u2028': '\\u2028',
22
+ '\u2029': '\\u2029'
23
+ };
24
+
25
+ const render_json_payload_script_regex = new RegExp(
26
+ `[${Object.keys(render_json_payload_script_dict).join('')}]`,
27
+ 'g'
28
+ );
29
+
30
+ /**
31
+ * Generates a raw HTML string containing a safe script element carrying JSON data and associated attributes.
32
+ *
33
+ * It escapes all the special characters needed to guarantee the element is unbroken, but care must
34
+ * be taken to ensure it is inserted in the document at an acceptable position for a script element,
35
+ * and that the resulting string isn't further modified.
36
+ *
37
+ * Attribute names must be type-checked so we don't need to escape them.
38
+ *
39
+ * @param {import('types').PayloadScriptAttributes} attrs A list of attributes to be added to the element.
40
+ * @param {any} payload The data to be carried by the element. Must be serializable to JSON.
41
+ * @returns {string} The raw HTML of a script element carrying the JSON payload.
42
+ * @example const html = render_json_payload_script({ type: 'data', url: '/data.json' }, { foo: 'bar' });
43
+ */
44
+ export function render_json_payload_script(attrs, payload) {
45
+ const safe_payload = JSON.stringify(payload).replace(
46
+ render_json_payload_script_regex,
47
+ (match) => render_json_payload_script_dict[match]
48
+ );
49
+
50
+ let safe_attrs = '';
51
+ for (const [key, value] of Object.entries(attrs)) {
52
+ if (value === undefined) continue;
53
+ safe_attrs += ` sveltekit:data-${key}=${escape_html_attr(value)}`;
54
+ }
55
+
56
+ return `<script type="application/json"${safe_attrs}>${safe_payload}</script>`;
57
+ }
58
+
59
+ /**
60
+ * When inside a double-quoted attribute value, only `&` and `"` hold special meaning.
61
+ * @see https://html.spec.whatwg.org/multipage/parsing.html#attribute-value-(double-quoted)-state
62
+ * @type {Record<string, string>}
63
+ */
64
+ const escape_html_attr_dict = {
65
+ '&': '&amp;',
66
+ '"': '&quot;'
67
+ };
68
+
69
+ const escape_html_attr_regex = new RegExp(
70
+ // special characters
71
+ `[${Object.keys(escape_html_attr_dict).join('')}]|` +
72
+ // high surrogate without paired low surrogate
73
+ '[\\ud800-\\udbff](?![\\udc00-\\udfff])|' +
74
+ // a valid surrogate pair, the only match with 2 code units
75
+ // we match it so that we can match unpaired low surrogates in the same pass
76
+ // TODO: use lookbehind assertions once they are widely supported: (?<![\ud800-udbff])[\udc00-\udfff]
77
+ '[\\ud800-\\udbff][\\udc00-\\udfff]|' +
78
+ // unpaired low surrogate (see previous match)
79
+ '[\\udc00-\\udfff]',
80
+ 'g'
81
+ );
82
+
83
+ /**
84
+ * Formats a string to be used as an attribute's value in raw HTML.
85
+ *
86
+ * It escapes unpaired surrogates (which are allowed in js strings but invalid in HTML), escapes
87
+ * characters that are special in attributes, and surrounds the whole string in double-quotes.
88
+ *
89
+ * @param {string} str
90
+ * @returns {string} Escaped string surrounded by double-quotes.
91
+ * @example const html = `<tag data-value=${escape_html_attr('value')}>...</tag>`;
92
+ */
93
+ export function escape_html_attr(str) {
94
+ const escaped_str = str.replace(escape_html_attr_regex, (match) => {
95
+ if (match.length === 2) {
96
+ // valid surrogate pair
97
+ return match;
98
+ }
99
+
100
+ return escape_html_attr_dict[match] ?? `&#${match.charCodeAt(0)};`;
101
+ });
102
+
103
+ return `"${escaped_str}"`;
104
+ }
@@ -0,0 +1,108 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ /** @param {string} dir */
5
+ export function mkdirp(dir) {
6
+ try {
7
+ fs.mkdirSync(dir, { recursive: true });
8
+ } catch (/** @type {any} */ e) {
9
+ if (e.code === 'EEXIST') return;
10
+ throw e;
11
+ }
12
+ }
13
+
14
+ /** @param {string} path */
15
+ export function rimraf(path) {
16
+ fs.rmSync(path, { force: true, recursive: true });
17
+ }
18
+
19
+ /**
20
+ * @param {string} source
21
+ * @param {string} target
22
+ * @param {{
23
+ * filter?: (basename: string) => boolean;
24
+ * replace?: Record<string, string>;
25
+ * }} opts
26
+ */
27
+ export function copy(source, target, opts = {}) {
28
+ if (!fs.existsSync(source)) return [];
29
+
30
+ /** @type {string[]} */
31
+ const files = [];
32
+
33
+ const prefix = posixify(target) + '/';
34
+
35
+ const regex = opts.replace
36
+ ? new RegExp(`\\b(${Object.keys(opts.replace).join('|')})\\b`, 'g')
37
+ : null;
38
+
39
+ /**
40
+ * @param {string} from
41
+ * @param {string} to
42
+ */
43
+ function go(from, to) {
44
+ if (opts.filter && !opts.filter(path.basename(from))) return;
45
+
46
+ const stats = fs.statSync(from);
47
+
48
+ if (stats.isDirectory()) {
49
+ fs.readdirSync(from).forEach((file) => {
50
+ go(path.join(from, file), path.join(to, file));
51
+ });
52
+ } else {
53
+ mkdirp(path.dirname(to));
54
+
55
+ if (opts.replace) {
56
+ const data = fs.readFileSync(from, 'utf-8');
57
+ fs.writeFileSync(
58
+ to,
59
+ data.replace(
60
+ /** @type {RegExp} */ (regex),
61
+ (_match, key) => /** @type {Record<string, string>} */ (opts.replace)[key]
62
+ )
63
+ );
64
+ } else {
65
+ fs.copyFileSync(from, to);
66
+ }
67
+
68
+ files.push(to === target ? posixify(path.basename(to)) : posixify(to).replace(prefix, ''));
69
+ }
70
+ }
71
+
72
+ go(source, target);
73
+
74
+ return files;
75
+ }
76
+
77
+ /**
78
+ * Get a list of all files in a directory
79
+ * @param {string} cwd - the directory to walk
80
+ * @param {boolean} [dirs] - whether to include directories in the result
81
+ */
82
+ export function walk(cwd, dirs = false) {
83
+ /** @type {string[]} */
84
+ const all_files = [];
85
+
86
+ /** @param {string} dir */
87
+ function walk_dir(dir) {
88
+ const files = fs.readdirSync(path.join(cwd, dir));
89
+
90
+ for (const file of files) {
91
+ const joined = path.join(dir, file);
92
+ const stats = fs.statSync(path.join(cwd, joined));
93
+ if (stats.isDirectory()) {
94
+ if (dirs) all_files.push(joined);
95
+ walk_dir(joined);
96
+ } else {
97
+ all_files.push(joined);
98
+ }
99
+ }
100
+ }
101
+
102
+ return walk_dir(''), all_files;
103
+ }
104
+
105
+ /** @param {string} str */
106
+ export function posixify(str) {
107
+ return str.replace(/\\/g, '/');
108
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @template T
3
+ * @param {() => T} fn
4
+ */
5
+ export function once(fn) {
6
+ let done = false;
7
+
8
+ /** @type T */
9
+ let result;
10
+
11
+ return () => {
12
+ if (done) return result;
13
+ done = true;
14
+ return (result = fn());
15
+ };
16
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Given an Accept header and a list of possible content types, pick
3
+ * the most suitable one to respond with
4
+ * @param {string} accept
5
+ * @param {string[]} types
6
+ */
7
+ export function negotiate(accept, types) {
8
+ /** @type {Array<{ type: string, subtype: string, q: number, i: number }>} */
9
+ const parts = [];
10
+
11
+ accept.split(',').forEach((str, i) => {
12
+ const match = /([^/]+)\/([^;]+)(?:;q=([0-9.]+))?/.exec(str);
13
+
14
+ // no match equals invalid header — ignore
15
+ if (match) {
16
+ const [, type, subtype, q = '1'] = match;
17
+ parts.push({ type, subtype, q: +q, i });
18
+ }
19
+ });
20
+
21
+ parts.sort((a, b) => {
22
+ if (a.q !== b.q) {
23
+ return b.q - a.q;
24
+ }
25
+
26
+ if ((a.subtype === '*') !== (b.subtype === '*')) {
27
+ return a.subtype === '*' ? 1 : -1;
28
+ }
29
+
30
+ if ((a.type === '*') !== (b.type === '*')) {
31
+ return a.type === '*' ? 1 : -1;
32
+ }
33
+
34
+ return a.i - b.i;
35
+ });
36
+
37
+ let accepted;
38
+ let min_priority = Infinity;
39
+
40
+ for (const mimetype of types) {
41
+ const [type, subtype] = mimetype.split('/');
42
+ const priority = parts.findIndex(
43
+ (part) =>
44
+ (part.type === type || part.type === '*') &&
45
+ (part.subtype === subtype || part.subtype === '*')
46
+ );
47
+
48
+ if (priority !== -1 && priority < min_priority) {
49
+ accepted = mimetype;
50
+ min_priority = priority;
51
+ }
52
+ }
53
+
54
+ return accepted;
55
+ }
@@ -0,0 +1 @@
1
+ export const s = JSON.stringify;
@@ -0,0 +1,146 @@
1
+ const param_pattern = /^(\.\.\.)?(\w+)(?:=(\w+))?$/;
2
+
3
+ /** @param {string} id */
4
+ export function parse_route_id(id) {
5
+ /** @type {string[]} */
6
+ const names = [];
7
+
8
+ /** @type {string[]} */
9
+ const types = [];
10
+
11
+ // `/foo` should get an optional trailing slash, `/foo.json` should not
12
+ // const add_trailing_slash = !/\.[a-z]+$/.test(key);
13
+ let add_trailing_slash = true;
14
+
15
+ if (/\]\[/.test(id)) {
16
+ throw new Error(`Invalid route ${id} — parameters must be separated`);
17
+ }
18
+
19
+ if (count_occurrences('[', id) !== count_occurrences(']', id)) {
20
+ throw new Error(`Invalid route ${id} — brackets are unbalanced`);
21
+ }
22
+
23
+ const pattern =
24
+ id === ''
25
+ ? /^\/$/
26
+ : new RegExp(
27
+ `^${id
28
+ .split(/(?:\/|$)/)
29
+ .filter(affects_path)
30
+ .map((segment, i, segments) => {
31
+ const decoded_segment = decodeURIComponent(segment);
32
+ // special case — /[...rest]/ could contain zero segments
33
+ const match = /^\[\.\.\.(\w+)(?:=(\w+))?\]$/.exec(decoded_segment);
34
+ if (match) {
35
+ names.push(match[1]);
36
+ types.push(match[2]);
37
+ return '(?:/(.*))?';
38
+ }
39
+
40
+ const is_last = i === segments.length - 1;
41
+
42
+ return (
43
+ decoded_segment &&
44
+ '/' +
45
+ decoded_segment
46
+ .split(/\[(.+?)\]/)
47
+ .map((content, i) => {
48
+ if (i % 2) {
49
+ const match = param_pattern.exec(content);
50
+ if (!match) {
51
+ throw new Error(
52
+ `Invalid param: ${content}. Params and matcher names can only have underscores and alphanumeric characters.`
53
+ );
54
+ }
55
+
56
+ const [, rest, name, type] = match;
57
+ names.push(name);
58
+ types.push(type);
59
+ return rest ? '(.*?)' : '([^/]+?)';
60
+ }
61
+
62
+ if (is_last && content.includes('.')) add_trailing_slash = false;
63
+
64
+ return (
65
+ content // allow users to specify characters on the file system in an encoded manner
66
+ .normalize()
67
+ // We use [ and ] to denote parameters, so users must encode these on the file
68
+ // system to match against them. We don't decode all characters since others
69
+ // can already be epressed and so that '%' can be easily used directly in filenames
70
+ .replace(/%5[Bb]/g, '[')
71
+ .replace(/%5[Dd]/g, ']')
72
+ // '#', '/', and '?' can only appear in URL path segments in an encoded manner.
73
+ // They will not be touched by decodeURI so need to be encoded here, so
74
+ // that we can match against them.
75
+ // We skip '/' since you can't create a file with it on any OS
76
+ .replace(/#/g, '%23')
77
+ .replace(/\?/g, '%3F')
78
+ // escape characters that have special meaning in regex
79
+ .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
80
+ ); // TODO handle encoding
81
+ })
82
+ .join('')
83
+ );
84
+ })
85
+ .join('')}${add_trailing_slash ? '/?' : ''}$`
86
+ );
87
+
88
+ return { pattern, names, types };
89
+ }
90
+
91
+ /**
92
+ * Returns `false` for `(group)` segments
93
+ * @param {string} segment
94
+ */
95
+ export function affects_path(segment) {
96
+ return !/^\([^)]+\)$/.test(segment);
97
+ }
98
+
99
+ /**
100
+ * Turns a route ID into a path, if possible
101
+ * @param {string} id
102
+ */
103
+ export function get_path(id) {
104
+ if (id.includes('[')) return null;
105
+ return `/${id.split('/').filter(affects_path).join('/')}`;
106
+ }
107
+
108
+ /**
109
+ * @param {RegExpMatchArray} match
110
+ * @param {string[]} names
111
+ * @param {string[]} types
112
+ * @param {Record<string, import('types').ParamMatcher>} matchers
113
+ */
114
+ export function exec(match, names, types, matchers) {
115
+ /** @type {Record<string, string>} */
116
+ const params = {};
117
+
118
+ for (let i = 0; i < names.length; i += 1) {
119
+ const name = names[i];
120
+ const type = types[i];
121
+ const value = match[i + 1] || '';
122
+
123
+ if (type) {
124
+ const matcher = matchers[type];
125
+ if (!matcher) throw new Error(`Missing "${type}" param matcher`); // TODO do this ahead of time?
126
+
127
+ if (!matcher(value)) return;
128
+ }
129
+
130
+ params[name] = value;
131
+ }
132
+
133
+ return params;
134
+ }
135
+
136
+ /**
137
+ * @param {string} needle
138
+ * @param {string} haystack
139
+ */
140
+ function count_occurrences(needle, haystack) {
141
+ let count = 0;
142
+ for (let i = 0; i < haystack.length; i += 1) {
143
+ if (haystack[i] === needle) count += 1;
144
+ }
145
+ return count;
146
+ }