@sveltejs/kit 1.0.0-next.41 → 1.0.0-next.410

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 (129) hide show
  1. package/README.md +12 -9
  2. package/package.json +97 -63
  3. package/src/cli.js +119 -0
  4. package/src/core/adapt/builder.js +207 -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 +3 -0
  10. package/src/core/generate_manifest/index.js +99 -0
  11. package/src/core/prerender/crawl.js +194 -0
  12. package/src/core/prerender/prerender.js +378 -0
  13. package/src/core/prerender/queue.js +80 -0
  14. package/src/core/sync/create_manifest_data/index.js +492 -0
  15. package/src/core/sync/create_manifest_data/types.d.ts +40 -0
  16. package/src/core/sync/sync.js +59 -0
  17. package/src/core/sync/utils.js +97 -0
  18. package/src/core/sync/write_ambient.js +87 -0
  19. package/src/core/sync/write_client_manifest.js +82 -0
  20. package/src/core/sync/write_matchers.js +25 -0
  21. package/src/core/sync/write_root.js +88 -0
  22. package/src/core/sync/write_tsconfig.js +189 -0
  23. package/src/core/sync/write_types.js +723 -0
  24. package/src/core/utils.js +58 -0
  25. package/src/hooks.js +26 -0
  26. package/src/index/index.js +45 -0
  27. package/src/index/private.js +33 -0
  28. package/src/node/index.js +145 -0
  29. package/src/node/polyfills.js +40 -0
  30. package/src/packaging/index.js +218 -0
  31. package/src/packaging/types.d.ts +8 -0
  32. package/src/packaging/typescript.js +150 -0
  33. package/src/packaging/utils.js +143 -0
  34. package/src/runtime/app/env.js +11 -0
  35. package/src/runtime/app/navigation.js +22 -0
  36. package/src/runtime/app/paths.js +1 -0
  37. package/src/runtime/app/stores.js +94 -0
  38. package/src/runtime/client/ambient.d.ts +17 -0
  39. package/src/runtime/client/client.js +1269 -0
  40. package/src/runtime/client/fetcher.js +60 -0
  41. package/src/runtime/client/parse.js +36 -0
  42. package/src/runtime/client/singletons.js +11 -0
  43. package/src/runtime/client/start.js +48 -0
  44. package/src/runtime/client/types.d.ts +106 -0
  45. package/src/runtime/client/utils.js +113 -0
  46. package/src/runtime/components/error.svelte +16 -0
  47. package/{assets → src/runtime}/components/layout.svelte +0 -0
  48. package/src/runtime/env/dynamic/private.js +1 -0
  49. package/src/runtime/env/dynamic/public.js +1 -0
  50. package/src/runtime/env-private.js +7 -0
  51. package/src/runtime/env-public.js +7 -0
  52. package/src/runtime/env.js +6 -0
  53. package/src/runtime/hash.js +16 -0
  54. package/src/runtime/paths.js +11 -0
  55. package/src/runtime/server/endpoint.js +42 -0
  56. package/src/runtime/server/index.js +434 -0
  57. package/src/runtime/server/page/cookie.js +25 -0
  58. package/src/runtime/server/page/crypto.js +239 -0
  59. package/src/runtime/server/page/csp.js +249 -0
  60. package/src/runtime/server/page/fetch.js +265 -0
  61. package/src/runtime/server/page/index.js +418 -0
  62. package/src/runtime/server/page/load_data.js +94 -0
  63. package/src/runtime/server/page/render.js +357 -0
  64. package/src/runtime/server/page/respond_with_error.js +105 -0
  65. package/src/runtime/server/page/types.d.ts +44 -0
  66. package/src/runtime/server/utils.js +116 -0
  67. package/src/utils/error.js +22 -0
  68. package/src/utils/escape.js +104 -0
  69. package/src/utils/filesystem.js +108 -0
  70. package/src/utils/http.js +55 -0
  71. package/src/utils/misc.js +1 -0
  72. package/src/utils/routing.js +107 -0
  73. package/src/utils/url.js +97 -0
  74. package/src/vite/build/build_server.js +335 -0
  75. package/src/vite/build/build_service_worker.js +90 -0
  76. package/src/vite/build/utils.js +153 -0
  77. package/src/vite/dev/index.js +565 -0
  78. package/src/vite/index.js +540 -0
  79. package/src/vite/preview/index.js +186 -0
  80. package/src/vite/types.d.ts +3 -0
  81. package/src/vite/utils.js +335 -0
  82. package/svelte-kit.js +1 -1
  83. package/types/ambient.d.ts +368 -0
  84. package/types/index.d.ts +345 -0
  85. package/types/internal.d.ts +313 -0
  86. package/types/private.d.ts +236 -0
  87. package/CHANGELOG.md +0 -419
  88. package/assets/components/error.svelte +0 -13
  89. package/assets/runtime/app/env.js +0 -5
  90. package/assets/runtime/app/navigation.js +0 -41
  91. package/assets/runtime/app/paths.js +0 -1
  92. package/assets/runtime/app/stores.js +0 -93
  93. package/assets/runtime/chunks/utils.js +0 -19
  94. package/assets/runtime/internal/singletons.js +0 -23
  95. package/assets/runtime/internal/start.js +0 -770
  96. package/assets/runtime/paths.js +0 -12
  97. package/dist/api.js +0 -28
  98. package/dist/api.js.map +0 -1
  99. package/dist/chunks/index.js +0 -3519
  100. package/dist/chunks/index2.js +0 -587
  101. package/dist/chunks/index3.js +0 -246
  102. package/dist/chunks/index4.js +0 -524
  103. package/dist/chunks/index5.js +0 -761
  104. package/dist/chunks/index6.js +0 -322
  105. package/dist/chunks/standard.js +0 -99
  106. package/dist/chunks/utils.js +0 -83
  107. package/dist/cli.js +0 -546
  108. package/dist/cli.js.map +0 -1
  109. package/dist/create_app.js +0 -592
  110. package/dist/create_app.js.map +0 -1
  111. package/dist/index.js +0 -392
  112. package/dist/index.js.map +0 -1
  113. package/dist/index2.js +0 -3519
  114. package/dist/index2.js.map +0 -1
  115. package/dist/index3.js +0 -320
  116. package/dist/index3.js.map +0 -1
  117. package/dist/index4.js +0 -323
  118. package/dist/index4.js.map +0 -1
  119. package/dist/index5.js +0 -247
  120. package/dist/index5.js.map +0 -1
  121. package/dist/index6.js +0 -761
  122. package/dist/index6.js.map +0 -1
  123. package/dist/renderer.js +0 -2499
  124. package/dist/renderer.js.map +0 -1
  125. package/dist/ssr.js +0 -2581
  126. package/dist/standard.js +0 -100
  127. package/dist/standard.js.map +0 -1
  128. package/dist/utils.js +0 -84
  129. package/dist/utils.js.map +0 -1
@@ -0,0 +1,249 @@
1
+ import { escape_html_attr } from '../../../utils/escape.js';
2
+ import { sha256, base64 } from './crypto.js';
3
+
4
+ const array = new Uint8Array(16);
5
+
6
+ function generate_nonce() {
7
+ crypto.getRandomValues(array);
8
+ return base64(array);
9
+ }
10
+
11
+ const quoted = new Set([
12
+ 'self',
13
+ 'unsafe-eval',
14
+ 'unsafe-hashes',
15
+ 'unsafe-inline',
16
+ 'none',
17
+ 'strict-dynamic',
18
+ 'report-sample'
19
+ ]);
20
+
21
+ const crypto_pattern = /^(nonce|sha\d\d\d)-/;
22
+
23
+ // CSP and CSP Report Only are extremely similar with a few caveats
24
+ // the easiest/DRYest way to express this is with some private encapsulation
25
+ class BaseProvider {
26
+ /** @type {boolean} */
27
+ #use_hashes;
28
+
29
+ /** @type {boolean} */
30
+ #script_needs_csp;
31
+
32
+ /** @type {boolean} */
33
+ #style_needs_csp;
34
+
35
+ /** @type {import('types').CspDirectives} */
36
+ #directives;
37
+
38
+ /** @type {import('types').Csp.Source[]} */
39
+ #script_src;
40
+
41
+ /** @type {import('types').Csp.Source[]} */
42
+ #style_src;
43
+
44
+ /** @type {string} */
45
+ #nonce;
46
+
47
+ /**
48
+ * @param {boolean} use_hashes
49
+ * @param {import('types').CspDirectives} directives
50
+ * @param {string} nonce
51
+ * @param {boolean} dev
52
+ */
53
+ constructor(use_hashes, directives, nonce, dev) {
54
+ this.#use_hashes = use_hashes;
55
+ this.#directives = dev ? { ...directives } : directives; // clone in dev so we can safely mutate
56
+
57
+ const d = this.#directives;
58
+
59
+ if (dev) {
60
+ // remove strict-dynamic in dev...
61
+ // TODO reinstate this if we can figure out how to make strict-dynamic work
62
+ // if (d['default-src']) {
63
+ // d['default-src'] = d['default-src'].filter((name) => name !== 'strict-dynamic');
64
+ // if (d['default-src'].length === 0) delete d['default-src'];
65
+ // }
66
+
67
+ // if (d['script-src']) {
68
+ // d['script-src'] = d['script-src'].filter((name) => name !== 'strict-dynamic');
69
+ // if (d['script-src'].length === 0) delete d['script-src'];
70
+ // }
71
+
72
+ const effective_style_src = d['style-src'] || d['default-src'];
73
+
74
+ // ...and add unsafe-inline so we can inject <style> elements
75
+ if (effective_style_src && !effective_style_src.includes('unsafe-inline')) {
76
+ d['style-src'] = [...effective_style_src, 'unsafe-inline'];
77
+ }
78
+ }
79
+
80
+ this.#script_src = [];
81
+ this.#style_src = [];
82
+
83
+ const effective_script_src = d['script-src'] || d['default-src'];
84
+ const effective_style_src = d['style-src'] || d['default-src'];
85
+
86
+ this.#script_needs_csp =
87
+ !!effective_script_src &&
88
+ effective_script_src.filter((value) => value !== 'unsafe-inline').length > 0;
89
+
90
+ this.#style_needs_csp =
91
+ !dev &&
92
+ !!effective_style_src &&
93
+ effective_style_src.filter((value) => value !== 'unsafe-inline').length > 0;
94
+
95
+ this.script_needs_nonce = this.#script_needs_csp && !this.#use_hashes;
96
+ this.style_needs_nonce = this.#style_needs_csp && !this.#use_hashes;
97
+ this.#nonce = nonce;
98
+ }
99
+
100
+ /** @param {string} content */
101
+ add_script(content) {
102
+ if (this.#script_needs_csp) {
103
+ if (this.#use_hashes) {
104
+ this.#script_src.push(`sha256-${sha256(content)}`);
105
+ } else if (this.#script_src.length === 0) {
106
+ this.#script_src.push(`nonce-${this.#nonce}`);
107
+ }
108
+ }
109
+ }
110
+
111
+ /** @param {string} content */
112
+ add_style(content) {
113
+ if (this.#style_needs_csp) {
114
+ if (this.#use_hashes) {
115
+ this.#style_src.push(`sha256-${sha256(content)}`);
116
+ } else if (this.#style_src.length === 0) {
117
+ this.#style_src.push(`nonce-${this.#nonce}`);
118
+ }
119
+ }
120
+ }
121
+
122
+ /**
123
+ * @param {boolean} [is_meta]
124
+ */
125
+ get_header(is_meta = false) {
126
+ const header = [];
127
+
128
+ // due to browser inconsistencies, we can't append sources to default-src
129
+ // (specifically, Firefox appears to not ignore nonce-{nonce} directives
130
+ // on default-src), so we ensure that script-src and style-src exist
131
+
132
+ const directives = { ...this.#directives };
133
+
134
+ if (this.#style_src.length > 0) {
135
+ directives['style-src'] = [
136
+ ...(directives['style-src'] || directives['default-src'] || []),
137
+ ...this.#style_src
138
+ ];
139
+ }
140
+
141
+ if (this.#script_src.length > 0) {
142
+ directives['script-src'] = [
143
+ ...(directives['script-src'] || directives['default-src'] || []),
144
+ ...this.#script_src
145
+ ];
146
+ }
147
+
148
+ for (const key in directives) {
149
+ if (is_meta && (key === 'frame-ancestors' || key === 'report-uri' || key === 'sandbox')) {
150
+ // these values cannot be used with a <meta> tag
151
+ // TODO warn?
152
+ continue;
153
+ }
154
+
155
+ // @ts-expect-error gimme a break typescript, `key` is obviously a member of internal_directives
156
+ const value = /** @type {string[] | true} */ (directives[key]);
157
+
158
+ if (!value) continue;
159
+
160
+ const directive = [key];
161
+ if (Array.isArray(value)) {
162
+ value.forEach((value) => {
163
+ if (quoted.has(value) || crypto_pattern.test(value)) {
164
+ directive.push(`'${value}'`);
165
+ } else {
166
+ directive.push(value);
167
+ }
168
+ });
169
+ }
170
+
171
+ header.push(directive.join(' '));
172
+ }
173
+
174
+ return header.join('; ');
175
+ }
176
+ }
177
+
178
+ class CspProvider extends BaseProvider {
179
+ get_meta() {
180
+ const content = escape_html_attr(this.get_header(true));
181
+ return `<meta http-equiv="content-security-policy" content=${content}>`;
182
+ }
183
+ }
184
+
185
+ class CspReportOnlyProvider extends BaseProvider {
186
+ /**
187
+ * @param {boolean} use_hashes
188
+ * @param {import('types').CspDirectives} directives
189
+ * @param {string} nonce
190
+ * @param {boolean} dev
191
+ */
192
+ constructor(use_hashes, directives, nonce, dev) {
193
+ super(use_hashes, directives, nonce, dev);
194
+
195
+ if (Object.values(directives).filter((v) => !!v).length > 0) {
196
+ // If we're generating content-security-policy-report-only,
197
+ // if there are any directives, we need a report-uri or report-to (or both)
198
+ // else it's just an expensive noop.
199
+ const has_report_to = directives['report-to']?.length ?? 0 > 0;
200
+ const has_report_uri = directives['report-uri']?.length ?? 0 > 0;
201
+ if (!has_report_to && !has_report_uri) {
202
+ throw Error(
203
+ '`content-security-policy-report-only` must be specified with either the `report-to` or `report-uri` directives, or both'
204
+ );
205
+ }
206
+ }
207
+ }
208
+ }
209
+
210
+ export class Csp {
211
+ /** @readonly */
212
+ nonce = generate_nonce();
213
+
214
+ /** @type {CspProvider} */
215
+ csp_provider;
216
+
217
+ /** @type {CspReportOnlyProvider} */
218
+ report_only_provider;
219
+
220
+ /**
221
+ * @param {import('./types').CspConfig} config
222
+ * @param {import('./types').CspOpts} opts
223
+ */
224
+ constructor({ mode, directives, reportOnly }, { prerender, dev }) {
225
+ const use_hashes = mode === 'hash' || (mode === 'auto' && prerender);
226
+ this.csp_provider = new CspProvider(use_hashes, directives, this.nonce, dev);
227
+ this.report_only_provider = new CspReportOnlyProvider(use_hashes, reportOnly, this.nonce, dev);
228
+ }
229
+
230
+ get script_needs_nonce() {
231
+ return this.csp_provider.script_needs_nonce || this.report_only_provider.script_needs_nonce;
232
+ }
233
+
234
+ get style_needs_nonce() {
235
+ return this.csp_provider.style_needs_nonce || this.report_only_provider.style_needs_nonce;
236
+ }
237
+
238
+ /** @param {string} content */
239
+ add_script(content) {
240
+ this.csp_provider.add_script(content);
241
+ this.report_only_provider.add_script(content);
242
+ }
243
+
244
+ /** @param {string} content */
245
+ add_style(content) {
246
+ this.csp_provider.add_style(content);
247
+ this.report_only_provider.add_style(content);
248
+ }
249
+ }
@@ -0,0 +1,265 @@
1
+ import * as cookie from 'cookie';
2
+ import * as set_cookie_parser from 'set-cookie-parser';
3
+ import { respond } from '../index.js';
4
+ import { is_root_relative, resolve } from '../../../utils/url.js';
5
+ import { domain_matches, path_matches } from './cookie.js';
6
+
7
+ /**
8
+ * @param {{
9
+ * event: import('types').RequestEvent;
10
+ * options: import('types').SSROptions;
11
+ * state: import('types').SSRState;
12
+ * route: import('types').SSRPage | import('types').SSRErrorPage;
13
+ * }} opts
14
+ */
15
+ export function create_fetch({ event, options, state, route }) {
16
+ /** @type {import('./types').Fetched[]} */
17
+ const fetched = [];
18
+
19
+ const initial_cookies = cookie.parse(event.request.headers.get('cookie') || '');
20
+
21
+ /** @type {import('set-cookie-parser').Cookie[]} */
22
+ const cookies = [];
23
+
24
+ /** @type {typeof fetch} */
25
+ const fetcher = async (resource, opts = {}) => {
26
+ /** @type {string} */
27
+ let requested;
28
+
29
+ if (typeof resource === 'string' || resource instanceof URL) {
30
+ requested = resource.toString();
31
+ } else {
32
+ requested = resource.url;
33
+
34
+ opts = {
35
+ method: resource.method,
36
+ headers: resource.headers,
37
+ body: resource.body,
38
+ mode: resource.mode,
39
+ credentials: resource.credentials,
40
+ cache: resource.cache,
41
+ redirect: resource.redirect,
42
+ referrer: resource.referrer,
43
+ integrity: resource.integrity,
44
+ ...opts
45
+ };
46
+ }
47
+
48
+ opts.headers = new Headers(opts.headers);
49
+
50
+ // merge headers from request
51
+ for (const [key, value] of event.request.headers) {
52
+ if (
53
+ key !== 'authorization' &&
54
+ key !== 'connection' &&
55
+ key !== 'cookie' &&
56
+ key !== 'host' &&
57
+ key !== 'if-none-match' &&
58
+ !opts.headers.has(key)
59
+ ) {
60
+ opts.headers.set(key, value);
61
+ }
62
+ }
63
+
64
+ const resolved = resolve(event.url.pathname, requested.split('?')[0]);
65
+
66
+ /** @type {Response} */
67
+ let response;
68
+
69
+ /** @type {import('types').PrerenderDependency} */
70
+ let dependency;
71
+
72
+ // handle fetch requests for static assets. e.g. prebaked data, etc.
73
+ // we need to support everything the browser's fetch supports
74
+ const prefix = options.paths.assets || options.paths.base;
75
+ const filename = decodeURIComponent(
76
+ resolved.startsWith(prefix) ? resolved.slice(prefix.length) : resolved
77
+ ).slice(1);
78
+ const filename_html = `${filename}/index.html`; // path may also match path/index.html
79
+
80
+ const is_asset = options.manifest.assets.has(filename);
81
+ const is_asset_html = options.manifest.assets.has(filename_html);
82
+
83
+ if (is_asset || is_asset_html) {
84
+ const file = is_asset ? filename : filename_html;
85
+
86
+ if (options.read) {
87
+ const type = is_asset
88
+ ? options.manifest.mimeTypes[filename.slice(filename.lastIndexOf('.'))]
89
+ : 'text/html';
90
+
91
+ response = new Response(options.read(file), {
92
+ headers: type ? { 'content-type': type } : {}
93
+ });
94
+ } else {
95
+ response = await fetch(`${event.url.origin}/${file}`, /** @type {RequestInit} */ (opts));
96
+ }
97
+ } else if (is_root_relative(resolved)) {
98
+ if (opts.credentials !== 'omit') {
99
+ const authorization = event.request.headers.get('authorization');
100
+
101
+ // combine cookies from the initiating request with any that were
102
+ // added via set-cookie
103
+ const combined_cookies = { ...initial_cookies };
104
+
105
+ for (const cookie of cookies) {
106
+ if (!domain_matches(event.url.hostname, cookie.domain)) continue;
107
+ if (!path_matches(resolved, cookie.path)) continue;
108
+
109
+ combined_cookies[cookie.name] = cookie.value;
110
+ }
111
+
112
+ const cookie = Object.entries(combined_cookies)
113
+ .map(([name, value]) => `${name}=${value}`)
114
+ .join('; ');
115
+
116
+ if (cookie) {
117
+ opts.headers.set('cookie', cookie);
118
+ }
119
+
120
+ if (authorization && !opts.headers.has('authorization')) {
121
+ opts.headers.set('authorization', authorization);
122
+ }
123
+ }
124
+
125
+ if (opts.body && typeof opts.body !== 'string') {
126
+ // per https://developer.mozilla.org/en-US/docs/Web/API/Request/Request, this can be a
127
+ // Blob, BufferSource, FormData, URLSearchParams, USVString, or ReadableStream object.
128
+ // non-string bodies are irksome to deal with, but luckily aren't particularly useful
129
+ // in this context anyway, so we take the easy route and ban them
130
+ throw new Error('Request body must be a string');
131
+ }
132
+
133
+ response = await respond(
134
+ new Request(new URL(requested, event.url).href, { ...opts }),
135
+ options,
136
+ {
137
+ ...state,
138
+ initiator: route
139
+ }
140
+ );
141
+
142
+ if (state.prerendering) {
143
+ dependency = { response, body: null };
144
+ state.prerendering.dependencies.set(resolved, dependency);
145
+ }
146
+ } else {
147
+ // external
148
+ if (resolved.startsWith('//')) {
149
+ requested = event.url.protocol + requested;
150
+ }
151
+
152
+ // external fetch
153
+ // allow cookie passthrough for "same-origin"
154
+ // if SvelteKit is serving my.domain.com:
155
+ // - domain.com WILL NOT receive cookies
156
+ // - my.domain.com WILL receive cookies
157
+ // - api.domain.dom WILL NOT receive cookies
158
+ // - sub.my.domain.com WILL receive cookies
159
+ // ports do not affect the resolution
160
+ // leading dot prevents mydomain.com matching domain.com
161
+ if (
162
+ `.${new URL(requested).hostname}`.endsWith(`.${event.url.hostname}`) &&
163
+ opts.credentials !== 'omit'
164
+ ) {
165
+ const cookie = event.request.headers.get('cookie');
166
+ if (cookie) opts.headers.set('cookie', cookie);
167
+ }
168
+
169
+ // we need to delete the connection header, as explained here:
170
+ // https://github.com/nodejs/undici/issues/1470#issuecomment-1140798467
171
+ // TODO this may be a case for being selective about which headers we let through
172
+ opts.headers.delete('connection');
173
+
174
+ const external_request = new Request(requested, /** @type {RequestInit} */ (opts));
175
+ response = await options.hooks.externalFetch.call(null, external_request);
176
+ }
177
+
178
+ const set_cookie = response.headers.get('set-cookie');
179
+ if (set_cookie) {
180
+ cookies.push(
181
+ ...set_cookie_parser
182
+ .splitCookiesString(set_cookie)
183
+ .map((str) => set_cookie_parser.parseString(str))
184
+ );
185
+ }
186
+
187
+ const proxy = new Proxy(response, {
188
+ get(response, key, _receiver) {
189
+ async function text() {
190
+ const body = await response.text();
191
+
192
+ /** @type {import('types').ResponseHeaders} */
193
+ const headers = {};
194
+ for (const [key, value] of response.headers) {
195
+ // TODO skip others besides set-cookie and etag?
196
+ if (key !== 'set-cookie' && key !== 'etag') {
197
+ headers[key] = value;
198
+ }
199
+ }
200
+
201
+ if (!opts.body || typeof opts.body === 'string') {
202
+ const status_number = Number(response.status);
203
+ if (isNaN(status_number)) {
204
+ throw new Error(
205
+ `response.status is not a number. value: "${
206
+ response.status
207
+ }" type: ${typeof response.status}`
208
+ );
209
+ }
210
+
211
+ fetched.push({
212
+ url: requested,
213
+ body: opts.body,
214
+ response: {
215
+ status: status_number,
216
+ statusText: response.statusText,
217
+ headers,
218
+ body
219
+ }
220
+ });
221
+ }
222
+
223
+ if (dependency) {
224
+ dependency.body = body;
225
+ }
226
+
227
+ return body;
228
+ }
229
+
230
+ if (key === 'arrayBuffer') {
231
+ return async () => {
232
+ const buffer = await response.arrayBuffer();
233
+
234
+ if (dependency) {
235
+ dependency.body = new Uint8Array(buffer);
236
+ }
237
+
238
+ // TODO should buffer be inlined into the page (albeit base64'd)?
239
+ // any conditions in which it shouldn't be?
240
+
241
+ return buffer;
242
+ };
243
+ }
244
+
245
+ if (key === 'text') {
246
+ return text;
247
+ }
248
+
249
+ if (key === 'json') {
250
+ return async () => {
251
+ return JSON.parse(await text());
252
+ };
253
+ }
254
+
255
+ // TODO arrayBuffer?
256
+
257
+ return Reflect.get(response, key, response);
258
+ }
259
+ });
260
+
261
+ return proxy;
262
+ };
263
+
264
+ return { fetcher, fetched, cookies };
265
+ }