@skgo/sveltekit-adapter 0.2.7
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/LICENSE +21 -0
- package/README.md +39 -0
- package/index.d.ts +26 -0
- package/package.json +42 -0
- package/skgo-adapter/app-paths.js +40 -0
- package/skgo-adapter/app-server.js +152 -0
- package/skgo-adapter/entry.js +420 -0
- package/skgo-adapter/env.js +1044 -0
- package/skgo-adapter/identity.js +75 -0
- package/skgo-adapter/polyfill.js +157 -0
- package/skgo-adapter.js +793 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which skgo this adapter is.
|
|
3
|
+
*
|
|
4
|
+
* The adapter writes `skgo.manifest.json` and the Go binary reads it: one
|
|
5
|
+
* contract in two languages, published as one npm package and one Go module at
|
|
6
|
+
* one version. The Go half refuses a manifest written by any adapter but its
|
|
7
|
+
* own, so the manifest has to carry a name for this one.
|
|
8
|
+
*
|
|
9
|
+
* The name is not written down anywhere, because a written-down name is a thing
|
|
10
|
+
* that can be wrong. It is taken from the package's own bytes — the entry vite
|
|
11
|
+
* imports, and every runtime file beside it — the same way, in the same order,
|
|
12
|
+
* as the Go module takes it from the copy it embeds. Two identical fingerprints
|
|
13
|
+
* therefore mean the files really are identical, byte for byte, and a package
|
|
14
|
+
* that lost or gained a file cannot claim to be the one the binary expects.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { createHash } from 'node:crypto';
|
|
18
|
+
import { readdirSync, readFileSync } from 'node:fs';
|
|
19
|
+
import { basename, dirname, join } from 'node:path';
|
|
20
|
+
import { fileURLToPath } from 'node:url';
|
|
21
|
+
|
|
22
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
|
|
24
|
+
/** The package root: the directory holding package.json and the entry. */
|
|
25
|
+
const root = dirname(here);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The runtime directory's name, which is also the entry's name plus `.js` —
|
|
29
|
+
* `skgo-adapter/` beside `skgo-adapter.js`. Reading it off this file's own
|
|
30
|
+
* location rather than writing it twice keeps the two spellings from drifting.
|
|
31
|
+
*/
|
|
32
|
+
const dir = basename(here);
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Every runtime file, as a slash-separated path relative to the package root,
|
|
36
|
+
* sorted. Sorted rather than as-read, so the fingerprint is a fact about the
|
|
37
|
+
* bytes and not about the order a directory happened to be listed in.
|
|
38
|
+
*
|
|
39
|
+
* @param {string} at
|
|
40
|
+
* @param {string} prefix
|
|
41
|
+
* @returns {string[]}
|
|
42
|
+
*/
|
|
43
|
+
function runtimeFiles(at, prefix) {
|
|
44
|
+
/** @type {string[]} */
|
|
45
|
+
const names = [];
|
|
46
|
+
for (const entry of readdirSync(at, { withFileTypes: true })) {
|
|
47
|
+
const name = prefix + '/' + entry.name;
|
|
48
|
+
if (entry.isDirectory()) {
|
|
49
|
+
names.push(...runtimeFiles(join(at, entry.name), name));
|
|
50
|
+
} else {
|
|
51
|
+
names.push(name);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return names.sort();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The identity the manifest carries: the published version of this package, and
|
|
59
|
+
* the fingerprint of the files it is made of.
|
|
60
|
+
*
|
|
61
|
+
* @returns {{ version: string, adapter: string }}
|
|
62
|
+
*/
|
|
63
|
+
export function identity() {
|
|
64
|
+
const sum = createHash('sha256');
|
|
65
|
+
sum.update(readFileSync(join(root, dir + '.js')));
|
|
66
|
+
for (const name of runtimeFiles(here, dir)) {
|
|
67
|
+
// The name goes in too, so that moving a file between paths changes the
|
|
68
|
+
// fingerprint even when no byte of it does.
|
|
69
|
+
sum.update(Buffer.from(name + '\0', 'utf-8'));
|
|
70
|
+
sum.update(readFileSync(join(root, name)));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf-8'));
|
|
74
|
+
return { version: pkg.version, adapter: sum.digest('hex').slice(0, 12) };
|
|
75
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// The globals kit's runtime and Svelte's renderer reach that a bare
|
|
2
|
+
// ECMAScript engine does not have and Go does not bind.
|
|
3
|
+
//
|
|
4
|
+
// This file is the SSR bundle's banner rather than a module of it. The fold
|
|
5
|
+
// that makes the bundle evaluates kit's shared chunk before the entry, so a
|
|
6
|
+
// polyfill imported as a module would arrive after the code that needs it —
|
|
7
|
+
// which the single-pass build only got away with by module order.
|
|
8
|
+
//
|
|
9
|
+
// URL, URLSearchParams, TextEncoder, TextDecoder, btoa and atob are not here:
|
|
10
|
+
// Go binds those natively when it creates a runtime, before this text runs.
|
|
11
|
+
// What is left is what a Go-backed constructor could not stand in for: the
|
|
12
|
+
// classes kit compares against with `instanceof` (Headers, Blob, File) and the
|
|
13
|
+
// two a render's own `event.fetch` builds and hands back (Request, Response),
|
|
14
|
+
// which are string and Map bookkeeping over a value that never leaves the
|
|
15
|
+
// engine except through `__skgo_fetch`.
|
|
16
|
+
|
|
17
|
+
// Svelte's no-AsyncLocalStorage fallback is gated on exactly this check
|
|
18
|
+
// (svelte/src/internal/server/render-context.js), and kit reads the same flag
|
|
19
|
+
// (kit/src/constants.js) to stop nulling its synchronous request store. It is
|
|
20
|
+
// Svelte's own supported path; the cost is one render per runtime at a time,
|
|
21
|
+
// which the pool in Go pays.
|
|
22
|
+
globalThis.process = { versions: { webcontainer: 'skgo' } };
|
|
23
|
+
|
|
24
|
+
// Headers is reached by kit's own Redirect constructor
|
|
25
|
+
// (exports/internal/shared.js), which builds one to reject a location that
|
|
26
|
+
// could not survive an HTTP header — and does it inside a try/catch, so an
|
|
27
|
+
// absent Headers would be reported as an invalid location rather than as a
|
|
28
|
+
// missing global. Only the construction and the validation it performs are
|
|
29
|
+
// needed; nothing in the engine sends a request.
|
|
30
|
+
if (typeof globalThis.Headers === 'undefined') {
|
|
31
|
+
// RFC 9110 field-name and field-value rules, which is all the real
|
|
32
|
+
// constructor checks that matters here.
|
|
33
|
+
const NAME = /^[A-Za-z0-9!#$%&'*+.^_|~-]+$/;
|
|
34
|
+
const BAD_VALUE = /[\u0000\r\n]/;
|
|
35
|
+
|
|
36
|
+
globalThis.Headers = class Headers {
|
|
37
|
+
constructor(init) {
|
|
38
|
+
this._ = new Map();
|
|
39
|
+
if (init instanceof Headers) {
|
|
40
|
+
for (const [k, v] of init._) this._.set(k, v);
|
|
41
|
+
} else if (Array.isArray(init)) {
|
|
42
|
+
for (const [k, v] of init) this.set(k, v);
|
|
43
|
+
} else if (init && typeof init === 'object') {
|
|
44
|
+
for (const k of Object.keys(init)) this.set(k, init[k]);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
set(name, value) {
|
|
48
|
+
const n = String(name);
|
|
49
|
+
const v = String(value).trim();
|
|
50
|
+
if (!NAME.test(n)) throw new TypeError('Invalid header name: ' + n);
|
|
51
|
+
if (BAD_VALUE.test(v)) throw new TypeError('Invalid header value');
|
|
52
|
+
this._.set(n.toLowerCase(), v);
|
|
53
|
+
}
|
|
54
|
+
append(name, value) {
|
|
55
|
+
const existing = this.get(name);
|
|
56
|
+
this.set(name, existing === null ? value : existing + ', ' + value);
|
|
57
|
+
}
|
|
58
|
+
get(name) {
|
|
59
|
+
const k = String(name).toLowerCase();
|
|
60
|
+
return this._.has(k) ? this._.get(k) : null;
|
|
61
|
+
}
|
|
62
|
+
has(name) {
|
|
63
|
+
return this._.has(String(name).toLowerCase());
|
|
64
|
+
}
|
|
65
|
+
delete(name) {
|
|
66
|
+
this._.delete(String(name).toLowerCase());
|
|
67
|
+
}
|
|
68
|
+
forEach(fn, thisArg) {
|
|
69
|
+
for (const [k, v] of this._) fn.call(thisArg, v, k, this);
|
|
70
|
+
}
|
|
71
|
+
keys() {
|
|
72
|
+
return this._.keys();
|
|
73
|
+
}
|
|
74
|
+
values() {
|
|
75
|
+
return this._.values();
|
|
76
|
+
}
|
|
77
|
+
entries() {
|
|
78
|
+
return this._.entries();
|
|
79
|
+
}
|
|
80
|
+
[Symbol.iterator]() {
|
|
81
|
+
return this._.entries();
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Request and Response back a render-time `event.fetch`. Kit's own
|
|
87
|
+
// `normalize_fetch_input` (runtime/server/fetch.js) turns whatever a
|
|
88
|
+
// component passed into a real Request before deciding what to do with it, and
|
|
89
|
+
// the answer a render's fetch gets back has to be a real Response — `await
|
|
90
|
+
// (await event.fetch(...)).json()` is what a page actually writes. Nothing
|
|
91
|
+
// here sends bytes anywhere: building one is string and Map bookkeeping, and
|
|
92
|
+
// the one call that leaves the engine is `__skgo_fetch` itself.
|
|
93
|
+
if (typeof globalThis.Request === 'undefined') {
|
|
94
|
+
globalThis.Request = class Request {
|
|
95
|
+
constructor(input, init = {}) {
|
|
96
|
+
if (input instanceof Request) {
|
|
97
|
+
this.url = input.url;
|
|
98
|
+
this.method = (init.method ?? input.method ?? 'GET').toUpperCase();
|
|
99
|
+
this.headers = init.headers ? new Headers(init.headers) : new Headers(input.headers);
|
|
100
|
+
this._body = init.body !== undefined ? init.body : input._body;
|
|
101
|
+
this.credentials = init.credentials ?? input.credentials ?? 'same-origin';
|
|
102
|
+
this.mode = init.mode ?? input.mode ?? 'cors';
|
|
103
|
+
} else {
|
|
104
|
+
this.url = String(input);
|
|
105
|
+
this.method = (init.method ?? 'GET').toUpperCase();
|
|
106
|
+
this.headers = new Headers(init.headers);
|
|
107
|
+
this._body = init.body;
|
|
108
|
+
this.credentials = init.credentials ?? 'same-origin';
|
|
109
|
+
this.mode = init.mode ?? 'cors';
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async text() { return this._body ?? ''; }
|
|
113
|
+
async json() { return JSON.parse(this._body ?? 'null'); }
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (typeof globalThis.Response === 'undefined') {
|
|
118
|
+
globalThis.Response = class Response {
|
|
119
|
+
constructor(body, init = {}) {
|
|
120
|
+
this._body = body ?? '';
|
|
121
|
+
this.status = init.status ?? 200;
|
|
122
|
+
this.statusText = init.statusText ?? '';
|
|
123
|
+
this.headers = init.headers instanceof Headers ? init.headers : new Headers(init.headers);
|
|
124
|
+
this.ok = this.status >= 200 && this.status < 300;
|
|
125
|
+
}
|
|
126
|
+
async text() { return this._body; }
|
|
127
|
+
async json() { return JSON.parse(this._body); }
|
|
128
|
+
async arrayBuffer() { return new TextEncoder().encode(this._body).buffer; }
|
|
129
|
+
clone() {
|
|
130
|
+
return new Response(this._body, { status: this.status, statusText: this.statusText, headers: this.headers });
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Blob and File are named by kit's form-field proxy, which asks whether a
|
|
136
|
+
// field's value is a File before it decides how to describe it to the markup.
|
|
137
|
+
// Nothing here ever holds one — an uploaded file's bytes are Go's, and they
|
|
138
|
+
// never enter the engine — so these exist to be compared against.
|
|
139
|
+
if (typeof globalThis.Blob === 'undefined') {
|
|
140
|
+
globalThis.Blob = class Blob {
|
|
141
|
+
constructor(parts = [], options = {}) {
|
|
142
|
+
this._parts = parts;
|
|
143
|
+
this.type = options.type ?? '';
|
|
144
|
+
this.size = 0;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (typeof globalThis.File === 'undefined') {
|
|
150
|
+
globalThis.File = class File extends globalThis.Blob {
|
|
151
|
+
constructor(parts = [], name = '', options = {}) {
|
|
152
|
+
super(parts, options);
|
|
153
|
+
this.name = String(name);
|
|
154
|
+
this.lastModified = options.lastModified ?? 0;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}
|