@sveltejs/adapter-netlify 1.0.0-next.9 → 1.0.1
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.
- package/README.md +104 -7
- package/files/edge.js +59 -0
- package/files/esm/serverless.js +366 -0
- package/files/esm/shims.js +20627 -0
- package/index.d.ts +4 -0
- package/index.js +280 -48
- package/package.json +53 -20
- package/CHANGELOG.md +0 -98
- package/files/_redirects +0 -1
- package/files/entry.js +0 -45
package/README.md
CHANGED
|
@@ -1,18 +1,115 @@
|
|
|
1
1
|
# adapter-netlify
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A SvelteKit adapter that creates a Netlify app.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
If you're using [adapter-auto](../adapter-auto), you don't need to install this unless you need to specify Netlify-specific options, since it's already included.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Installation
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
```bash
|
|
10
|
+
npm i -D @sveltejs/adapter-netlify
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
You can then configure it inside of `svelte.config.js`:
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
import adapter from '@sveltejs/adapter-netlify';
|
|
17
|
+
|
|
18
|
+
export default {
|
|
19
|
+
kit: {
|
|
20
|
+
// default options are shown
|
|
21
|
+
adapter: adapter({
|
|
22
|
+
// if true, will create a Netlify Edge Function rather
|
|
23
|
+
// than using standard Node-based functions
|
|
24
|
+
edge: false,
|
|
25
|
+
|
|
26
|
+
// if true, will split your app into multiple functions
|
|
27
|
+
// instead of creating a single one for the entire app.
|
|
28
|
+
// if `edge` is true, this option cannot be used
|
|
29
|
+
split: false
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Then, make sure you have a [netlify.toml](https://docs.netlify.com/configure-builds/file-based-configuration) file in the project root. This will determine where to write static assets based on the `build.publish` settings, as per this sample configuration:
|
|
10
36
|
|
|
11
37
|
```toml
|
|
12
38
|
[build]
|
|
13
39
|
command = "npm run build"
|
|
14
|
-
publish = "build
|
|
15
|
-
functions = "functions/"
|
|
40
|
+
publish = "build"
|
|
16
41
|
```
|
|
17
42
|
|
|
18
|
-
|
|
43
|
+
If the `netlify.toml` file or the `build.publish` value is missing, a default value of `"build"` will be used. Note that if you have set the publish directory in the Netlify UI to something else then you will need to set it in `netlify.toml` too, or use the default value of `"build"`.
|
|
44
|
+
|
|
45
|
+
### Node version
|
|
46
|
+
|
|
47
|
+
New projects will use Node 16 by default. However, if you're upgrading a project you created a while ago it may be stuck on an older version. See [the Netlify docs](https://docs.netlify.com/configure-builds/manage-dependencies/#node-js-and-javascript) for details on manually specifying Node 16 or newer.
|
|
48
|
+
|
|
49
|
+
## Netlify Edge Functions (beta)
|
|
50
|
+
|
|
51
|
+
SvelteKit supports the beta release of [Netlify Edge Functions](https://docs.netlify.com/netlify-labs/experimental-features/edge-functions/). If you pass the option `edge: true` to the `adapter` function, server-side rendering will happen in a Deno-based edge function that's deployed close to the site visitor. If set to `false` (the default), the site will deploy to standard Node-based Netlify Functions.
|
|
52
|
+
|
|
53
|
+
```js
|
|
54
|
+
import adapter from '@sveltejs/adapter-netlify';
|
|
55
|
+
|
|
56
|
+
export default {
|
|
57
|
+
kit: {
|
|
58
|
+
adapter: adapter({
|
|
59
|
+
// will create a Netlify Edge Function using Deno-based
|
|
60
|
+
// rather than using standard Node-based functions
|
|
61
|
+
edge: true
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Netlify alternatives to SvelteKit functionality
|
|
68
|
+
|
|
69
|
+
You may build your app using functionality provided directly by SvelteKit without relying on any Netlify functionality. Using the SvelteKit versions of these features will allow them to be used in dev mode, tested with integration tests, and to work with other adapters should you ever decide to switch away from Netlify. However, in some scenarios you may find it beneficial to use the Netlify versions of these features. One example would be if you're migrating an app that's already hosted on Netlify to SvelteKit.
|
|
70
|
+
|
|
71
|
+
### Using Netlify Redirect Rules
|
|
72
|
+
|
|
73
|
+
During compilation, redirect rules are automatically appended to your `_redirects` file. (If it doesn't exist yet, it will be created.) That means:
|
|
74
|
+
|
|
75
|
+
- `[[redirects]]` in `netlify.toml` will never match as `_redirects` has a [higher priority](https://docs.netlify.com/routing/redirects/#rule-processing-order). So always put your rules in the [`_redirects` file](https://docs.netlify.com/routing/redirects/#syntax-for-the-redirects-file).
|
|
76
|
+
- `_redirects` shouldn't have any custom "catch all" rules such as `/* /foobar/:splat`. Otherwise the automatically appended rule will never be applied as Netlify is only processing [the first matching rule](https://docs.netlify.com/routing/redirects/#rule-processing-order).
|
|
77
|
+
|
|
78
|
+
### Using Netlify Forms
|
|
79
|
+
|
|
80
|
+
1. Create your Netlify HTML form as described [here](https://docs.netlify.com/forms/setup/#html-forms), e.g. as `/routes/contact.svelte`. (Don't forget to add the hidden `form-name` input element!)
|
|
81
|
+
2. Netlify's build bot parses your HTML files at deploy time, which means your form must be [prerendered](https://kit.svelte.dev/docs/page-options#prerender) as HTML. You can either add `export const prerender = true` to your `contact.svelte` to prerender just that page or set the `kit.prerender.force: true` option to prerender all pages.
|
|
82
|
+
3. If your Netlify form has a [custom success message](https://docs.netlify.com/forms/setup/#success-messages) like `<form netlify ... action="/success">` then ensure the corresponding `/routes/success.svelte` exists and is prerendered.
|
|
83
|
+
|
|
84
|
+
### Using Netlify Functions
|
|
85
|
+
|
|
86
|
+
With this adapter, SvelteKit endpoints are hosted as [Netlify Functions](https://docs.netlify.com/functions/overview/). Netlify function handlers have additional context, including [Netlify Identity](https://docs.netlify.com/visitor-access/identity/) information. You can access this context via the `event.platform.context` field inside your hooks and `+page.server` or `+layout.server` endpoints. These are [serverless functions](https://docs.netlify.com/functions/overview/) when the `edge` property is `false` in the adapter config or [edge functions](https://docs.netlify.com/edge-functions/overview/#app) when it is `true`.
|
|
87
|
+
|
|
88
|
+
```js
|
|
89
|
+
// +page.server.js
|
|
90
|
+
export const load = async (event) => {
|
|
91
|
+
const context = event.platform.context;
|
|
92
|
+
console.log(context); // shows up in your functions log in the Netlify app
|
|
93
|
+
};
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Additionally, you can add your own Netlify functions by creating a directory for them and adding the configuration to your `netlify.toml` file. For example:
|
|
97
|
+
|
|
98
|
+
```toml
|
|
99
|
+
[build]
|
|
100
|
+
command = "npm run build"
|
|
101
|
+
publish = "build"
|
|
102
|
+
|
|
103
|
+
[functions]
|
|
104
|
+
directory = "functions"
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Troubleshooting
|
|
108
|
+
|
|
109
|
+
### Accessing the file system
|
|
110
|
+
|
|
111
|
+
You can't access the file system through methods like `fs.readFileSync` in Serverless/Edge environments. If you need to access files that way, do that during building the app through [prerendering](https://kit.svelte.dev/docs/page-options#prerender). If you have a blog for example and don't want to manage your content through a CMS, then you need to prerender the content (or prerender the endpoint from which you get it) and redeploy your blog everytime you add new content.
|
|
112
|
+
|
|
113
|
+
## Changelog
|
|
114
|
+
|
|
115
|
+
[The Changelog for this package is available on GitHub](https://github.com/sveltejs/kit/blob/master/packages/adapter-netlify/CHANGELOG.md).
|
package/files/edge.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Server } from '0SERVER';
|
|
2
|
+
import { manifest, prerendered } from 'MANIFEST';
|
|
3
|
+
|
|
4
|
+
const server = new Server(manifest);
|
|
5
|
+
const prefix = `/${manifest.appPath}/`;
|
|
6
|
+
|
|
7
|
+
const initialized = server.init({
|
|
8
|
+
// @ts-ignore
|
|
9
|
+
env: Deno.env.toObject()
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param { Request } request
|
|
14
|
+
* @param { any } context
|
|
15
|
+
* @returns { Promise<Response> }
|
|
16
|
+
*/
|
|
17
|
+
export default async function handler(request, context) {
|
|
18
|
+
if (is_static_file(request)) {
|
|
19
|
+
// Static files can skip the handler
|
|
20
|
+
// TODO can we serve _app/immutable files with an immutable cache header?
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
await initialized;
|
|
25
|
+
return server.respond(request, {
|
|
26
|
+
platform: { context },
|
|
27
|
+
getClientAddress() {
|
|
28
|
+
return context.ip;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {Request} request
|
|
35
|
+
*/
|
|
36
|
+
function is_static_file(request) {
|
|
37
|
+
const url = new URL(request.url);
|
|
38
|
+
|
|
39
|
+
// Assets in the app dir
|
|
40
|
+
if (url.pathname.startsWith(prefix)) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// prerendered pages and index.html files
|
|
45
|
+
const pathname = url.pathname.replace(/\/$/, '');
|
|
46
|
+
let file = pathname.substring(1);
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
file = decodeURIComponent(file);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
// ignore
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
manifest.assets.has(file) ||
|
|
56
|
+
manifest.assets.has(file + '/index.html') ||
|
|
57
|
+
prerendered.has(pathname || '/')
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import './shims.js';
|
|
2
|
+
import { Server } from '0SERVER';
|
|
3
|
+
import 'assert';
|
|
4
|
+
import 'net';
|
|
5
|
+
import 'http';
|
|
6
|
+
import 'stream';
|
|
7
|
+
import 'buffer';
|
|
8
|
+
import 'util';
|
|
9
|
+
import 'querystring';
|
|
10
|
+
import 'stream/web';
|
|
11
|
+
import 'worker_threads';
|
|
12
|
+
import 'perf_hooks';
|
|
13
|
+
import 'util/types';
|
|
14
|
+
import 'url';
|
|
15
|
+
import 'events';
|
|
16
|
+
import 'tls';
|
|
17
|
+
import 'async_hooks';
|
|
18
|
+
import 'console';
|
|
19
|
+
import 'zlib';
|
|
20
|
+
import 'string_decoder';
|
|
21
|
+
import 'crypto';
|
|
22
|
+
import 'diagnostics_channel';
|
|
23
|
+
|
|
24
|
+
var setCookieExports = {};
|
|
25
|
+
var setCookie = {
|
|
26
|
+
get exports(){ return setCookieExports; },
|
|
27
|
+
set exports(v){ setCookieExports = v; },
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
var defaultParseOptions = {
|
|
31
|
+
decodeValues: true,
|
|
32
|
+
map: false,
|
|
33
|
+
silent: false,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function isNonEmptyString(str) {
|
|
37
|
+
return typeof str === "string" && !!str.trim();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseString(setCookieValue, options) {
|
|
41
|
+
var parts = setCookieValue.split(";").filter(isNonEmptyString);
|
|
42
|
+
|
|
43
|
+
var nameValuePairStr = parts.shift();
|
|
44
|
+
var parsed = parseNameValuePair(nameValuePairStr);
|
|
45
|
+
var name = parsed.name;
|
|
46
|
+
var value = parsed.value;
|
|
47
|
+
|
|
48
|
+
options = options
|
|
49
|
+
? Object.assign({}, defaultParseOptions, options)
|
|
50
|
+
: defaultParseOptions;
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
value = options.decodeValues ? decodeURIComponent(value) : value; // decode cookie value
|
|
54
|
+
} catch (e) {
|
|
55
|
+
console.error(
|
|
56
|
+
"set-cookie-parser encountered an error while decoding a cookie with value '" +
|
|
57
|
+
value +
|
|
58
|
+
"'. Set options.decodeValues to false to disable this feature.",
|
|
59
|
+
e
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
var cookie = {
|
|
64
|
+
name: name,
|
|
65
|
+
value: value,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
parts.forEach(function (part) {
|
|
69
|
+
var sides = part.split("=");
|
|
70
|
+
var key = sides.shift().trimLeft().toLowerCase();
|
|
71
|
+
var value = sides.join("=");
|
|
72
|
+
if (key === "expires") {
|
|
73
|
+
cookie.expires = new Date(value);
|
|
74
|
+
} else if (key === "max-age") {
|
|
75
|
+
cookie.maxAge = parseInt(value, 10);
|
|
76
|
+
} else if (key === "secure") {
|
|
77
|
+
cookie.secure = true;
|
|
78
|
+
} else if (key === "httponly") {
|
|
79
|
+
cookie.httpOnly = true;
|
|
80
|
+
} else if (key === "samesite") {
|
|
81
|
+
cookie.sameSite = value;
|
|
82
|
+
} else {
|
|
83
|
+
cookie[key] = value;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return cookie;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function parseNameValuePair(nameValuePairStr) {
|
|
91
|
+
// Parses name-value-pair according to rfc6265bis draft
|
|
92
|
+
|
|
93
|
+
var name = "";
|
|
94
|
+
var value = "";
|
|
95
|
+
var nameValueArr = nameValuePairStr.split("=");
|
|
96
|
+
if (nameValueArr.length > 1) {
|
|
97
|
+
name = nameValueArr.shift();
|
|
98
|
+
value = nameValueArr.join("="); // everything after the first =, joined by a "=" if there was more than one part
|
|
99
|
+
} else {
|
|
100
|
+
value = nameValuePairStr;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return { name: name, value: value };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function parse(input, options) {
|
|
107
|
+
options = options
|
|
108
|
+
? Object.assign({}, defaultParseOptions, options)
|
|
109
|
+
: defaultParseOptions;
|
|
110
|
+
|
|
111
|
+
if (!input) {
|
|
112
|
+
if (!options.map) {
|
|
113
|
+
return [];
|
|
114
|
+
} else {
|
|
115
|
+
return {};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (input.headers && input.headers["set-cookie"]) {
|
|
120
|
+
// fast-path for node.js (which automatically normalizes header names to lower-case
|
|
121
|
+
input = input.headers["set-cookie"];
|
|
122
|
+
} else if (input.headers) {
|
|
123
|
+
// slow-path for other environments - see #25
|
|
124
|
+
var sch =
|
|
125
|
+
input.headers[
|
|
126
|
+
Object.keys(input.headers).find(function (key) {
|
|
127
|
+
return key.toLowerCase() === "set-cookie";
|
|
128
|
+
})
|
|
129
|
+
];
|
|
130
|
+
// warn if called on a request-like object with a cookie header rather than a set-cookie header - see #34, 36
|
|
131
|
+
if (!sch && input.headers.cookie && !options.silent) {
|
|
132
|
+
console.warn(
|
|
133
|
+
"Warning: set-cookie-parser appears to have been called on a request object. It is designed to parse Set-Cookie headers from responses, not Cookie headers from requests. Set the option {silent: true} to suppress this warning."
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
input = sch;
|
|
137
|
+
}
|
|
138
|
+
if (!Array.isArray(input)) {
|
|
139
|
+
input = [input];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
options = options
|
|
143
|
+
? Object.assign({}, defaultParseOptions, options)
|
|
144
|
+
: defaultParseOptions;
|
|
145
|
+
|
|
146
|
+
if (!options.map) {
|
|
147
|
+
return input.filter(isNonEmptyString).map(function (str) {
|
|
148
|
+
return parseString(str, options);
|
|
149
|
+
});
|
|
150
|
+
} else {
|
|
151
|
+
var cookies = {};
|
|
152
|
+
return input.filter(isNonEmptyString).reduce(function (cookies, str) {
|
|
153
|
+
var cookie = parseString(str, options);
|
|
154
|
+
cookies[cookie.name] = cookie;
|
|
155
|
+
return cookies;
|
|
156
|
+
}, cookies);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/*
|
|
161
|
+
Set-Cookie header field-values are sometimes comma joined in one string. This splits them without choking on commas
|
|
162
|
+
that are within a single set-cookie field-value, such as in the Expires portion.
|
|
163
|
+
|
|
164
|
+
This is uncommon, but explicitly allowed - see https://tools.ietf.org/html/rfc2616#section-4.2
|
|
165
|
+
Node.js does this for every header *except* set-cookie - see https://github.com/nodejs/node/blob/d5e363b77ebaf1caf67cd7528224b651c86815c1/lib/_http_incoming.js#L128
|
|
166
|
+
React Native's fetch does this for *every* header, including set-cookie.
|
|
167
|
+
|
|
168
|
+
Based on: https://github.com/google/j2objc/commit/16820fdbc8f76ca0c33472810ce0cb03d20efe25
|
|
169
|
+
Credits to: https://github.com/tomball for original and https://github.com/chrusart for JavaScript implementation
|
|
170
|
+
*/
|
|
171
|
+
function splitCookiesString(cookiesString) {
|
|
172
|
+
if (Array.isArray(cookiesString)) {
|
|
173
|
+
return cookiesString;
|
|
174
|
+
}
|
|
175
|
+
if (typeof cookiesString !== "string") {
|
|
176
|
+
return [];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
var cookiesStrings = [];
|
|
180
|
+
var pos = 0;
|
|
181
|
+
var start;
|
|
182
|
+
var ch;
|
|
183
|
+
var lastComma;
|
|
184
|
+
var nextStart;
|
|
185
|
+
var cookiesSeparatorFound;
|
|
186
|
+
|
|
187
|
+
function skipWhitespace() {
|
|
188
|
+
while (pos < cookiesString.length && /\s/.test(cookiesString.charAt(pos))) {
|
|
189
|
+
pos += 1;
|
|
190
|
+
}
|
|
191
|
+
return pos < cookiesString.length;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function notSpecialChar() {
|
|
195
|
+
ch = cookiesString.charAt(pos);
|
|
196
|
+
|
|
197
|
+
return ch !== "=" && ch !== ";" && ch !== ",";
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
while (pos < cookiesString.length) {
|
|
201
|
+
start = pos;
|
|
202
|
+
cookiesSeparatorFound = false;
|
|
203
|
+
|
|
204
|
+
while (skipWhitespace()) {
|
|
205
|
+
ch = cookiesString.charAt(pos);
|
|
206
|
+
if (ch === ",") {
|
|
207
|
+
// ',' is a cookie separator if we have later first '=', not ';' or ','
|
|
208
|
+
lastComma = pos;
|
|
209
|
+
pos += 1;
|
|
210
|
+
|
|
211
|
+
skipWhitespace();
|
|
212
|
+
nextStart = pos;
|
|
213
|
+
|
|
214
|
+
while (pos < cookiesString.length && notSpecialChar()) {
|
|
215
|
+
pos += 1;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// currently special character
|
|
219
|
+
if (pos < cookiesString.length && cookiesString.charAt(pos) === "=") {
|
|
220
|
+
// we found cookies separator
|
|
221
|
+
cookiesSeparatorFound = true;
|
|
222
|
+
// pos is inside the next cookie, so back up and return it.
|
|
223
|
+
pos = nextStart;
|
|
224
|
+
cookiesStrings.push(cookiesString.substring(start, lastComma));
|
|
225
|
+
start = pos;
|
|
226
|
+
} else {
|
|
227
|
+
// in param ',' or param separator ';',
|
|
228
|
+
// we continue from that comma
|
|
229
|
+
pos = lastComma + 1;
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
pos += 1;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (!cookiesSeparatorFound || pos >= cookiesString.length) {
|
|
237
|
+
cookiesStrings.push(cookiesString.substring(start, cookiesString.length));
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return cookiesStrings;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
setCookie.exports = parse;
|
|
245
|
+
setCookieExports.parse = parse;
|
|
246
|
+
setCookieExports.parseString = parseString;
|
|
247
|
+
var splitCookiesString_1 = setCookieExports.splitCookiesString = splitCookiesString;
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Splits headers into two categories: single value and multi value
|
|
251
|
+
* @param {Headers} headers
|
|
252
|
+
* @returns {{
|
|
253
|
+
* headers: Record<string, string>,
|
|
254
|
+
* multiValueHeaders: Record<string, string[]>
|
|
255
|
+
* }}
|
|
256
|
+
*/
|
|
257
|
+
function split_headers(headers) {
|
|
258
|
+
/** @type {Record<string, string>} */
|
|
259
|
+
const h = {};
|
|
260
|
+
|
|
261
|
+
/** @type {Record<string, string[]>} */
|
|
262
|
+
const m = {};
|
|
263
|
+
|
|
264
|
+
headers.forEach((value, key) => {
|
|
265
|
+
if (key === 'set-cookie') {
|
|
266
|
+
m[key] = splitCookiesString_1(value);
|
|
267
|
+
} else {
|
|
268
|
+
h[key] = value;
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
headers: h,
|
|
274
|
+
multiValueHeaders: m
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* @param {import('@sveltejs/kit').SSRManifest} manifest
|
|
280
|
+
* @returns {import('@netlify/functions').Handler}
|
|
281
|
+
*/
|
|
282
|
+
function init(manifest) {
|
|
283
|
+
const server = new Server(manifest);
|
|
284
|
+
|
|
285
|
+
let init_promise = server.init({
|
|
286
|
+
env: process.env
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
return async (event, context) => {
|
|
290
|
+
if (init_promise !== null) {
|
|
291
|
+
await init_promise;
|
|
292
|
+
init_promise = null;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const response = await server.respond(to_request(event), {
|
|
296
|
+
platform: { context },
|
|
297
|
+
getClientAddress() {
|
|
298
|
+
return event.headers['x-nf-client-connection-ip'];
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
const partial_response = {
|
|
303
|
+
statusCode: response.status,
|
|
304
|
+
...split_headers(response.headers)
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
if (!is_text(response.headers.get('content-type'))) {
|
|
308
|
+
// Function responses should be strings (or undefined), and responses with binary
|
|
309
|
+
// content should be base64 encoded and set isBase64Encoded to true.
|
|
310
|
+
// https://github.com/netlify/functions/blob/main/src/function/response.ts
|
|
311
|
+
return {
|
|
312
|
+
...partial_response,
|
|
313
|
+
isBase64Encoded: true,
|
|
314
|
+
body: Buffer.from(await response.arrayBuffer()).toString('base64')
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
...partial_response,
|
|
320
|
+
body: await response.text()
|
|
321
|
+
};
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* @param {import('@netlify/functions').HandlerEvent} event
|
|
327
|
+
* @returns {Request}
|
|
328
|
+
*/
|
|
329
|
+
function to_request(event) {
|
|
330
|
+
const { httpMethod, headers, rawUrl, body, isBase64Encoded } = event;
|
|
331
|
+
|
|
332
|
+
/** @type {RequestInit} */
|
|
333
|
+
const init = {
|
|
334
|
+
method: httpMethod,
|
|
335
|
+
headers: new Headers(headers)
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
if (httpMethod !== 'GET' && httpMethod !== 'HEAD') {
|
|
339
|
+
const encoding = isBase64Encoded ? 'base64' : 'utf-8';
|
|
340
|
+
init.body = typeof body === 'string' ? Buffer.from(body, encoding) : body;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return new Request(rawUrl, init);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const text_types = new Set([
|
|
347
|
+
'application/xml',
|
|
348
|
+
'application/json',
|
|
349
|
+
'application/x-www-form-urlencoded',
|
|
350
|
+
'multipart/form-data'
|
|
351
|
+
]);
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Decides how the body should be parsed based on its mime type
|
|
355
|
+
*
|
|
356
|
+
* @param {string | undefined | null} content_type The `content-type` header of a request/response.
|
|
357
|
+
* @returns {boolean}
|
|
358
|
+
*/
|
|
359
|
+
function is_text(content_type) {
|
|
360
|
+
if (!content_type) return true; // defaults to json
|
|
361
|
+
const type = content_type.split(';')[0].toLowerCase(); // get the mime type
|
|
362
|
+
|
|
363
|
+
return type.startsWith('text/') || type.endsWith('+xml') || text_types.has(type);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export { init };
|