@sveltejs/kit 1.0.0-next.98 → 1.0.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 (141) hide show
  1. package/README.md +5 -1
  2. package/package.json +91 -77
  3. package/postinstall.js +47 -0
  4. package/src/cli.js +44 -0
  5. package/src/constants.js +5 -0
  6. package/src/core/adapt/builder.js +221 -0
  7. package/src/core/adapt/index.js +31 -0
  8. package/src/core/config/default-error.html +56 -0
  9. package/src/core/config/index.js +100 -0
  10. package/src/core/config/options.js +387 -0
  11. package/src/core/config/types.d.ts +1 -0
  12. package/src/core/env.js +138 -0
  13. package/src/core/generate_manifest/index.js +116 -0
  14. package/src/core/prerender/crawl.js +207 -0
  15. package/src/core/prerender/entities.js +2252 -0
  16. package/src/core/prerender/fallback.js +43 -0
  17. package/src/core/prerender/prerender.js +459 -0
  18. package/src/core/prerender/queue.js +80 -0
  19. package/src/core/sync/create_manifest_data/conflict.js +0 -0
  20. package/src/core/sync/create_manifest_data/index.js +523 -0
  21. package/src/core/sync/create_manifest_data/sort.js +161 -0
  22. package/src/core/sync/create_manifest_data/types.d.ts +37 -0
  23. package/src/core/sync/sync.js +59 -0
  24. package/src/core/sync/utils.js +33 -0
  25. package/src/core/sync/write_ambient.js +58 -0
  26. package/src/core/sync/write_client_manifest.js +107 -0
  27. package/src/core/sync/write_matchers.js +25 -0
  28. package/src/core/sync/write_root.js +91 -0
  29. package/src/core/sync/write_tsconfig.js +195 -0
  30. package/src/core/sync/write_types/index.js +809 -0
  31. package/src/core/utils.js +67 -0
  32. package/src/exports/hooks/index.js +1 -0
  33. package/src/exports/hooks/sequence.js +44 -0
  34. package/src/exports/index.js +55 -0
  35. package/src/exports/node/index.js +172 -0
  36. package/src/exports/node/polyfills.js +28 -0
  37. package/src/exports/vite/build/build_server.js +359 -0
  38. package/src/exports/vite/build/build_service_worker.js +85 -0
  39. package/src/exports/vite/build/utils.js +230 -0
  40. package/src/exports/vite/dev/index.js +597 -0
  41. package/src/exports/vite/graph_analysis/index.js +99 -0
  42. package/src/exports/vite/graph_analysis/types.d.ts +5 -0
  43. package/src/exports/vite/graph_analysis/utils.js +6 -0
  44. package/src/exports/vite/index.js +708 -0
  45. package/src/exports/vite/preview/index.js +194 -0
  46. package/src/exports/vite/types.d.ts +3 -0
  47. package/src/exports/vite/utils.js +184 -0
  48. package/src/runtime/app/env.js +1 -0
  49. package/src/runtime/app/environment.js +13 -0
  50. package/src/runtime/app/forms.js +135 -0
  51. package/src/runtime/app/navigation.js +22 -0
  52. package/src/runtime/app/paths.js +1 -0
  53. package/src/runtime/app/stores.js +57 -0
  54. package/src/runtime/client/ambient.d.ts +30 -0
  55. package/src/runtime/client/client.js +1725 -0
  56. package/src/runtime/client/constants.js +10 -0
  57. package/src/runtime/client/fetcher.js +127 -0
  58. package/src/runtime/client/parse.js +60 -0
  59. package/src/runtime/client/singletons.js +21 -0
  60. package/src/runtime/client/start.js +45 -0
  61. package/src/runtime/client/types.d.ts +86 -0
  62. package/src/runtime/client/utils.js +257 -0
  63. package/src/runtime/components/error.svelte +6 -0
  64. package/{assets → src/runtime}/components/layout.svelte +0 -0
  65. package/src/runtime/control.js +45 -0
  66. package/src/runtime/env/dynamic/private.js +1 -0
  67. package/src/runtime/env/dynamic/public.js +1 -0
  68. package/src/runtime/env-private.js +6 -0
  69. package/src/runtime/env-public.js +6 -0
  70. package/src/runtime/env.js +12 -0
  71. package/src/runtime/hash.js +20 -0
  72. package/src/runtime/paths.js +11 -0
  73. package/src/runtime/server/cookie.js +228 -0
  74. package/src/runtime/server/data/index.js +158 -0
  75. package/src/runtime/server/endpoint.js +86 -0
  76. package/src/runtime/server/fetch.js +175 -0
  77. package/src/runtime/server/index.js +405 -0
  78. package/src/runtime/server/page/actions.js +267 -0
  79. package/src/runtime/server/page/crypto.js +239 -0
  80. package/src/runtime/server/page/csp.js +250 -0
  81. package/src/runtime/server/page/index.js +326 -0
  82. package/src/runtime/server/page/load_data.js +270 -0
  83. package/src/runtime/server/page/render.js +393 -0
  84. package/src/runtime/server/page/respond_with_error.js +103 -0
  85. package/src/runtime/server/page/serialize_data.js +87 -0
  86. package/src/runtime/server/page/types.d.ts +35 -0
  87. package/src/runtime/server/utils.js +179 -0
  88. package/src/utils/array.js +9 -0
  89. package/src/utils/error.js +22 -0
  90. package/src/utils/escape.js +46 -0
  91. package/src/utils/exports.js +54 -0
  92. package/src/utils/filesystem.js +178 -0
  93. package/src/utils/functions.js +16 -0
  94. package/src/utils/http.js +72 -0
  95. package/src/utils/misc.js +1 -0
  96. package/src/utils/promises.js +17 -0
  97. package/src/utils/routing.js +201 -0
  98. package/src/utils/unit_test.js +11 -0
  99. package/src/utils/url.js +161 -0
  100. package/svelte-kit.js +1 -1
  101. package/types/ambient.d.ts +451 -0
  102. package/types/index.d.ts +1168 -5
  103. package/types/internal.d.ts +348 -160
  104. package/types/private.d.ts +237 -0
  105. package/types/synthetic/$env+dynamic+private.md +10 -0
  106. package/types/synthetic/$env+dynamic+public.md +8 -0
  107. package/types/synthetic/$env+static+private.md +19 -0
  108. package/types/synthetic/$env+static+public.md +7 -0
  109. package/types/synthetic/$lib.md +5 -0
  110. package/CHANGELOG.md +0 -819
  111. package/assets/components/error.svelte +0 -21
  112. package/assets/runtime/app/env.js +0 -16
  113. package/assets/runtime/app/navigation.js +0 -53
  114. package/assets/runtime/app/paths.js +0 -1
  115. package/assets/runtime/app/stores.js +0 -87
  116. package/assets/runtime/chunks/utils.js +0 -13
  117. package/assets/runtime/env.js +0 -8
  118. package/assets/runtime/internal/singletons.js +0 -20
  119. package/assets/runtime/internal/start.js +0 -1061
  120. package/assets/runtime/paths.js +0 -12
  121. package/dist/chunks/_commonjsHelpers.js +0 -8
  122. package/dist/chunks/cert.js +0 -29079
  123. package/dist/chunks/constants.js +0 -3
  124. package/dist/chunks/index.js +0 -3526
  125. package/dist/chunks/index2.js +0 -583
  126. package/dist/chunks/index3.js +0 -31
  127. package/dist/chunks/index4.js +0 -1005
  128. package/dist/chunks/index5.js +0 -327
  129. package/dist/chunks/index6.js +0 -325
  130. package/dist/chunks/standard.js +0 -99
  131. package/dist/chunks/utils.js +0 -149
  132. package/dist/cli.js +0 -711
  133. package/dist/http.js +0 -66
  134. package/dist/install-fetch.js +0 -1699
  135. package/dist/ssr.js +0 -1529
  136. package/types/ambient-modules.d.ts +0 -115
  137. package/types/config.d.ts +0 -101
  138. package/types/endpoint.d.ts +0 -23
  139. package/types/helper.d.ts +0 -19
  140. package/types/hooks.d.ts +0 -23
  141. package/types/page.d.ts +0 -30
@@ -0,0 +1,179 @@
1
+ import * as devalue from 'devalue';
2
+ import { coalesce_to_error } from '../../utils/error.js';
3
+ import { negotiate } from '../../utils/http.js';
4
+ import { has_data_suffix } from '../../utils/url.js';
5
+ import { HttpError } from '../control.js';
6
+
7
+ /** @param {any} body */
8
+ export function is_pojo(body) {
9
+ if (typeof body !== 'object') return false;
10
+
11
+ if (body) {
12
+ if (body instanceof Uint8Array) return false;
13
+ if (body instanceof ReadableStream) return false;
14
+ }
15
+
16
+ return true;
17
+ }
18
+
19
+ /** @type {import('types').SSRErrorPage} */
20
+ export const GENERIC_ERROR = {
21
+ id: '__error'
22
+ };
23
+
24
+ /**
25
+ * @param {Partial<Record<import('types').HttpMethod, any>>} mod
26
+ * @param {import('types').HttpMethod} method
27
+ */
28
+ export function method_not_allowed(mod, method) {
29
+ return new Response(`${method} method not allowed`, {
30
+ status: 405,
31
+ headers: {
32
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405
33
+ // "The server must generate an Allow header field in a 405 status code response"
34
+ allow: allowed_methods(mod).join(', ')
35
+ }
36
+ });
37
+ }
38
+
39
+ /** @param {Partial<Record<import('types').HttpMethod, any>>} mod */
40
+ export function allowed_methods(mod) {
41
+ const allowed = [];
42
+
43
+ for (const method in ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']) {
44
+ if (method in mod) allowed.push(method);
45
+ }
46
+
47
+ if (mod.GET || mod.HEAD) allowed.push('HEAD');
48
+
49
+ return allowed;
50
+ }
51
+
52
+ /**
53
+ * @template {'prerender' | 'ssr' | 'csr' | 'trailingSlash'} Option
54
+ * @template {Option extends 'prerender' ? import('types').PrerenderOption : Option extends 'trailingSlash' ? import('types').TrailingSlash : boolean} Value
55
+ *
56
+ * @param {Array<import('types').SSRNode | undefined>} nodes
57
+ * @param {Option} option
58
+ *
59
+ * @returns {Value | undefined}
60
+ */
61
+ export function get_option(nodes, option) {
62
+ return nodes.reduce((value, node) => {
63
+ return /** @type {any} TypeScript's too dumb to understand this */ (
64
+ node?.universal?.[option] ?? node?.server?.[option] ?? value
65
+ );
66
+ }, /** @type {Value | undefined} */ (undefined));
67
+ }
68
+
69
+ /**
70
+ * Return as a response that renders the error.html
71
+ *
72
+ * @param {import('types').SSROptions} options
73
+ * @param {number} status
74
+ * @param {string} message
75
+ */
76
+ export function static_error_page(options, status, message) {
77
+ return new Response(options.error_template({ status, message }), {
78
+ headers: { 'content-type': 'text/html; charset=utf-8' },
79
+ status
80
+ });
81
+ }
82
+
83
+ /**
84
+ * @param {import('types').RequestEvent} event
85
+ * @param {import('types').SSROptions} options
86
+ * @param {unknown} error
87
+ */
88
+ export async function handle_fatal_error(event, options, error) {
89
+ error = error instanceof HttpError ? error : coalesce_to_error(error);
90
+ const status = error instanceof HttpError ? error.status : 500;
91
+ const body = await handle_error_and_jsonify(event, options, error);
92
+
93
+ // ideally we'd use sec-fetch-dest instead, but Safari — quelle surprise — doesn't support it
94
+ const type = negotiate(event.request.headers.get('accept') || 'text/html', [
95
+ 'application/json',
96
+ 'text/html'
97
+ ]);
98
+
99
+ if (has_data_suffix(new URL(event.request.url).pathname) || type === 'application/json') {
100
+ return new Response(JSON.stringify(body), {
101
+ status,
102
+ headers: { 'content-type': 'application/json; charset=utf-8' }
103
+ });
104
+ }
105
+
106
+ return static_error_page(options, status, body.message);
107
+ }
108
+
109
+ /**
110
+ * @param {import('types').RequestEvent} event
111
+ * @param {import('types').SSROptions} options
112
+ * @param {any} error
113
+ * @returns {import('types').MaybePromise<App.Error>}
114
+ */
115
+ export function handle_error_and_jsonify(event, options, error) {
116
+ if (error instanceof HttpError) {
117
+ return error.body;
118
+ } else {
119
+ return options.handle_error(error, event);
120
+ }
121
+ }
122
+
123
+ /**
124
+ * @param {number} status
125
+ * @param {string} location
126
+ */
127
+ export function redirect_response(status, location) {
128
+ const response = new Response(undefined, {
129
+ status,
130
+ headers: { location }
131
+ });
132
+ return response;
133
+ }
134
+
135
+ /**
136
+ * @param {import('types').RequestEvent} event
137
+ * @param {Error & { path: string }} error
138
+ */
139
+ export function clarify_devalue_error(event, error) {
140
+ if (error.path) {
141
+ return `Data returned from \`load\` while rendering ${event.route.id} is not serializable: ${error.message} (data${error.path})`;
142
+ }
143
+
144
+ if (error.path === '') {
145
+ return `Data returned from \`load\` while rendering ${event.route.id} is not a plain object`;
146
+ }
147
+
148
+ // belt and braces — this should never happen
149
+ return error.message;
150
+ }
151
+
152
+ /** @param {import('types').ServerDataNode | import('types').ServerDataSkippedNode | import('types').ServerErrorNode | null} node */
153
+ export function serialize_data_node(node) {
154
+ if (!node) return 'null';
155
+
156
+ if (node.type === 'error' || node.type === 'skip') {
157
+ return JSON.stringify(node);
158
+ }
159
+
160
+ const stringified = devalue.stringify(node.data);
161
+
162
+ const uses = [];
163
+
164
+ if (node.uses.dependencies.size > 0) {
165
+ uses.push(`"dependencies":${JSON.stringify(Array.from(node.uses.dependencies))}`);
166
+ }
167
+
168
+ if (node.uses.params.size > 0) {
169
+ uses.push(`"params":${JSON.stringify(Array.from(node.uses.params))}`);
170
+ }
171
+
172
+ if (node.uses.parent) uses.push(`"parent":1`);
173
+ if (node.uses.route) uses.push(`"route":1`);
174
+ if (node.uses.url) uses.push(`"url":1`);
175
+
176
+ return `{"type":"data","data":${stringified},"uses":{${uses.join(',')}}${
177
+ node.slash ? `,"slash":${JSON.stringify(node.slash)}` : ''
178
+ }}`;
179
+ }
@@ -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 '../runtime/control.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,46 @@
1
+ /**
2
+ * When inside a double-quoted attribute value, only `&` and `"` hold special meaning.
3
+ * @see https://html.spec.whatwg.org/multipage/parsing.html#attribute-value-(double-quoted)-state
4
+ * @type {Record<string, string>}
5
+ */
6
+ const escape_html_attr_dict = {
7
+ '&': '&amp;',
8
+ '"': '&quot;'
9
+ };
10
+
11
+ const escape_html_attr_regex = new RegExp(
12
+ // special characters
13
+ `[${Object.keys(escape_html_attr_dict).join('')}]|` +
14
+ // high surrogate without paired low surrogate
15
+ '[\\ud800-\\udbff](?![\\udc00-\\udfff])|' +
16
+ // a valid surrogate pair, the only match with 2 code units
17
+ // we match it so that we can match unpaired low surrogates in the same pass
18
+ // TODO: use lookbehind assertions once they are widely supported: (?<![\ud800-udbff])[\udc00-\udfff]
19
+ '[\\ud800-\\udbff][\\udc00-\\udfff]|' +
20
+ // unpaired low surrogate (see previous match)
21
+ '[\\udc00-\\udfff]',
22
+ 'g'
23
+ );
24
+
25
+ /**
26
+ * Formats a string to be used as an attribute's value in raw HTML.
27
+ *
28
+ * It escapes unpaired surrogates (which are allowed in js strings but invalid in HTML), escapes
29
+ * characters that are special in attributes, and surrounds the whole string in double-quotes.
30
+ *
31
+ * @param {string} str
32
+ * @returns {string} Escaped string surrounded by double-quotes.
33
+ * @example const html = `<tag data-value=${escape_html_attr('value')}>...</tag>`;
34
+ */
35
+ export function escape_html_attr(str) {
36
+ const escaped_str = str.replace(escape_html_attr_regex, (match) => {
37
+ if (match.length === 2) {
38
+ // valid surrogate pair
39
+ return match;
40
+ }
41
+
42
+ return escape_html_attr_dict[match] ?? `&#${match.charCodeAt(0)};`;
43
+ });
44
+
45
+ return `"${escaped_str}"`;
46
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @param {string[]} expected
3
+ */
4
+ function validator(expected) {
5
+ const set = new Set(expected);
6
+
7
+ /**
8
+ * @param {any} module
9
+ * @param {string} [route_id]
10
+ */
11
+ function validate(module, route_id) {
12
+ if (!module) return;
13
+
14
+ for (const key in module) {
15
+ if (key[0] !== '_' && !set.has(key)) {
16
+ const valid = expected.join(', ');
17
+ throw new Error(
18
+ `Invalid export '${key}'${
19
+ route_id ? ` in ${route_id}` : ''
20
+ } (valid exports are ${valid}, or anything with a '_' prefix)`
21
+ );
22
+ }
23
+ }
24
+ }
25
+
26
+ return validate;
27
+ }
28
+
29
+ export const validate_common_exports = validator([
30
+ 'load',
31
+ 'prerender',
32
+ 'csr',
33
+ 'ssr',
34
+ 'trailingSlash'
35
+ ]);
36
+
37
+ export const validate_page_server_exports = validator([
38
+ 'load',
39
+ 'prerender',
40
+ 'csr',
41
+ 'ssr',
42
+ 'actions',
43
+ 'trailingSlash'
44
+ ]);
45
+
46
+ export const validate_server_exports = validator([
47
+ 'GET',
48
+ 'POST',
49
+ 'PATCH',
50
+ 'PUT',
51
+ 'DELETE',
52
+ 'prerender',
53
+ 'trailingSlash'
54
+ ]);
@@ -0,0 +1,178 @@
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') {
10
+ if (!fs.statSync(dir).isDirectory()) {
11
+ throw new Error(`Cannot create directory ${dir}, a file already exists at this position`);
12
+ }
13
+ return;
14
+ }
15
+ throw e;
16
+ }
17
+ }
18
+
19
+ /** @param {string} path */
20
+ export function rimraf(path) {
21
+ fs.rmSync(path, { force: true, recursive: true });
22
+ }
23
+
24
+ /**
25
+ * @param {string} source
26
+ * @param {string} target
27
+ * @param {{
28
+ * filter?: (basename: string) => boolean;
29
+ * replace?: Record<string, string>;
30
+ * }} opts
31
+ */
32
+ export function copy(source, target, opts = {}) {
33
+ if (!fs.existsSync(source)) return [];
34
+
35
+ /** @type {string[]} */
36
+ const files = [];
37
+
38
+ const prefix = posixify(target) + '/';
39
+
40
+ const regex = opts.replace
41
+ ? new RegExp(`\\b(${Object.keys(opts.replace).join('|')})\\b`, 'g')
42
+ : null;
43
+
44
+ /**
45
+ * @param {string} from
46
+ * @param {string} to
47
+ */
48
+ function go(from, to) {
49
+ if (opts.filter && !opts.filter(path.basename(from))) return;
50
+
51
+ const stats = fs.statSync(from);
52
+
53
+ if (stats.isDirectory()) {
54
+ fs.readdirSync(from).forEach((file) => {
55
+ go(path.join(from, file), path.join(to, file));
56
+ });
57
+ } else {
58
+ mkdirp(path.dirname(to));
59
+
60
+ if (opts.replace) {
61
+ const data = fs.readFileSync(from, 'utf-8');
62
+ fs.writeFileSync(
63
+ to,
64
+ data.replace(
65
+ /** @type {RegExp} */ (regex),
66
+ (_match, key) => /** @type {Record<string, string>} */ (opts.replace)[key]
67
+ )
68
+ );
69
+ } else {
70
+ fs.copyFileSync(from, to);
71
+ }
72
+
73
+ files.push(to === target ? posixify(path.basename(to)) : posixify(to).replace(prefix, ''));
74
+ }
75
+ }
76
+
77
+ go(source, target);
78
+
79
+ return files;
80
+ }
81
+
82
+ /**
83
+ * Get a list of all files in a directory
84
+ * @param {string} cwd - the directory to walk
85
+ * @param {boolean} [dirs] - whether to include directories in the result
86
+ * @returns {string[]} a list of all found files (and possibly directories) relative to `cwd`
87
+ */
88
+ export function walk(cwd, dirs = false) {
89
+ /** @type {string[]} */
90
+ const all_files = [];
91
+
92
+ /** @param {string} dir */
93
+ function walk_dir(dir) {
94
+ const files = fs.readdirSync(path.join(cwd, dir));
95
+
96
+ for (const file of files) {
97
+ const joined = path.join(dir, file);
98
+ const stats = fs.statSync(path.join(cwd, joined));
99
+ if (stats.isDirectory()) {
100
+ if (dirs) all_files.push(joined);
101
+ walk_dir(joined);
102
+ } else {
103
+ all_files.push(joined);
104
+ }
105
+ }
106
+ }
107
+
108
+ return walk_dir(''), all_files;
109
+ }
110
+
111
+ /** @param {string} str */
112
+ export function posixify(str) {
113
+ return str.replace(/\\/g, '/');
114
+ }
115
+
116
+ /**
117
+ * Like `path.join`, but posixified and with a leading `./` if necessary
118
+ * @param {string[]} str
119
+ */
120
+ export function join_relative(...str) {
121
+ let result = posixify(path.join(...str));
122
+ if (!result.startsWith('.')) {
123
+ result = `./${result}`;
124
+ }
125
+ return result;
126
+ }
127
+
128
+ /**
129
+ * Like `path.relative`, but always posixified and with a leading `./` if necessary.
130
+ * Useful for JS imports so the path can safely reside inside of `node_modules`.
131
+ * Otherwise paths could be falsely interpreted as package paths.
132
+ * @param {string} from
133
+ * @param {string} to
134
+ */
135
+ export function relative_path(from, to) {
136
+ return join_relative(path.relative(from, to));
137
+ }
138
+
139
+ /**
140
+ * Prepend given path with `/@fs` prefix
141
+ * @param {string} str
142
+ */
143
+ export function to_fs(str) {
144
+ str = posixify(str);
145
+ return `/@fs${
146
+ // Windows/Linux separation - Windows starts with a drive letter, we need a / in front there
147
+ str.startsWith('/') ? '' : '/'
148
+ }${str}`;
149
+ }
150
+
151
+ /**
152
+ * Given an entry point like [cwd]/src/hooks, returns a filename like [cwd]/src/hooks.js or [cwd]/src/hooks/index.js
153
+ * @param {string} entry
154
+ * @returns {string|null}
155
+ */
156
+ export function resolve_entry(entry) {
157
+ if (fs.existsSync(entry)) {
158
+ const stats = fs.statSync(entry);
159
+ if (stats.isDirectory()) {
160
+ return resolve_entry(path.join(entry, 'index'));
161
+ }
162
+
163
+ return entry;
164
+ } else {
165
+ const dir = path.dirname(entry);
166
+
167
+ if (fs.existsSync(dir)) {
168
+ const base = path.basename(entry);
169
+ const files = fs.readdirSync(dir);
170
+
171
+ const found = files.find((file) => file.replace(/\.[^.]+$/, '') === base);
172
+
173
+ if (found) return path.join(dir, found);
174
+ }
175
+ }
176
+
177
+ return null;
178
+ }
@@ -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,72 @@
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
+ }
56
+
57
+ /**
58
+ * Returns `true` if the request contains a `content-type` header with the given type
59
+ * @param {Request} request
60
+ * @param {...string} types
61
+ */
62
+ export function is_content_type(request, ...types) {
63
+ const type = request.headers.get('content-type')?.split(';', 1)[0].trim() ?? '';
64
+ return types.includes(type);
65
+ }
66
+
67
+ /**
68
+ * @param {Request} request
69
+ */
70
+ export function is_form_content_type(request) {
71
+ return is_content_type(request, 'application/x-www-form-urlencoded', 'multipart/form-data');
72
+ }
@@ -0,0 +1 @@
1
+ export const s = JSON.stringify;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Given an object, return a new object where all top level values are awaited
3
+ *
4
+ * @param {Record<string, any>} object
5
+ * @returns {Promise<Record<string, any>>}
6
+ */
7
+ export async function unwrap_promises(object) {
8
+ for (const key in object) {
9
+ if (typeof object[key]?.then === 'function') {
10
+ return Object.fromEntries(
11
+ await Promise.all(Object.entries(object).map(async ([key, value]) => [key, await value]))
12
+ );
13
+ }
14
+ }
15
+
16
+ return object;
17
+ }