@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tyler Gannon
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# @skgo/sveltekit-adapter
|
|
2
|
+
|
|
3
|
+
The SvelteKit adapter for [skgo](https://github.com/tylergannon/skgo), a
|
|
4
|
+
SvelteKit application server written in Go.
|
|
5
|
+
|
|
6
|
+
The app stays plain SvelteKit. This adapter builds it into the shape a skgo
|
|
7
|
+
binary embeds: the client bundle, kit's SPA fallback document, the SSR bundle
|
|
8
|
+
the Go process renders pages with, and a manifest naming the routes, remote
|
|
9
|
+
functions and server loads Go must answer.
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
pnpm add -D "github:tylergannon/skgo#v0.2.0&path:internal/adapter"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The tag is the skgo version your `go.mod` requires; pnpm installs the package
|
|
16
|
+
straight from that tag's `internal/adapter`. A project made by `skgo new` has
|
|
17
|
+
this line already. When the package is on npm the spec becomes
|
|
18
|
+
`pnpm add -D @skgo/sveltekit-adapter`.
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
// vite.config.ts
|
|
22
|
+
import { sveltekit } from '@sveltejs/kit/vite';
|
|
23
|
+
import { defineConfig } from 'vite';
|
|
24
|
+
import skgo from '@skgo/sveltekit-adapter';
|
|
25
|
+
|
|
26
|
+
export default defineConfig({
|
|
27
|
+
plugins: [sveltekit({ adapter: skgo(), experimental: { remoteFunctions: true } })]
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The adapter and the Go module are one contract in two languages, released
|
|
32
|
+
together under one version. Every build records which adapter wrote it, and a
|
|
33
|
+
skgo binary refuses a build written by a different one, naming both — so an
|
|
34
|
+
install that has fallen behind fails at startup with something a developer can
|
|
35
|
+
act on rather than as an unrelated error later.
|
|
36
|
+
|
|
37
|
+
## License
|
|
38
|
+
|
|
39
|
+
[MIT](LICENSE)
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Adapter } from '@sveltejs/kit';
|
|
2
|
+
|
|
3
|
+
interface AdapterOptions {
|
|
4
|
+
/**
|
|
5
|
+
* The directory the build is written to, relative to the vite root. It
|
|
6
|
+
* defaults to `build`, which is what the app's Go embeds.
|
|
7
|
+
* @default 'build'
|
|
8
|
+
*/
|
|
9
|
+
out?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Whether to write gzip and brotli copies of the assets and the prerendered
|
|
12
|
+
* pages beside them. The Go server serves whichever the browser asked for.
|
|
13
|
+
* @default true
|
|
14
|
+
*/
|
|
15
|
+
precompress?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The skgo adapter. It emits the client bundle, kit's SPA fallback document,
|
|
20
|
+
* the SSR bundle the Go process renders pages with, and the manifest naming
|
|
21
|
+
* what Go must answer.
|
|
22
|
+
*
|
|
23
|
+
* The manifest records which skgo this package is, and the Go that reads it
|
|
24
|
+
* refuses a build written by a different one, so the two halves cannot drift.
|
|
25
|
+
*/
|
|
26
|
+
export default function skgo(options?: AdapterOptions): Adapter;
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@skgo/sveltekit-adapter",
|
|
3
|
+
"version": "0.2.7",
|
|
4
|
+
"description": "SvelteKit adapter for skgo: builds a frontend that a Go binary renders and answers",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"adapter",
|
|
7
|
+
"go",
|
|
8
|
+
"golang",
|
|
9
|
+
"skgo",
|
|
10
|
+
"svelte",
|
|
11
|
+
"sveltekit"
|
|
12
|
+
],
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/tylergannon/skgo.git",
|
|
16
|
+
"directory": "internal/adapter"
|
|
17
|
+
},
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"homepage": "https://github.com/tylergannon/skgo",
|
|
20
|
+
"type": "module",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./index.d.ts",
|
|
24
|
+
"import": "./skgo-adapter.js"
|
|
25
|
+
},
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"types": "index.d.ts",
|
|
29
|
+
"files": [
|
|
30
|
+
"index.d.ts",
|
|
31
|
+
"skgo-adapter.js",
|
|
32
|
+
"skgo-adapter/app-paths.js",
|
|
33
|
+
"skgo-adapter/app-server.js",
|
|
34
|
+
"skgo-adapter/entry.js",
|
|
35
|
+
"skgo-adapter/env.js",
|
|
36
|
+
"skgo-adapter/identity.js",
|
|
37
|
+
"skgo-adapter/polyfill.js"
|
|
38
|
+
],
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"@sveltejs/kit": "^3.0.0-next.0"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// The `$app/paths` the SSR bundle sees.
|
|
2
|
+
//
|
|
3
|
+
// Like app-server.js, it is a module of the `goja` environment and of no
|
|
4
|
+
// other: the adapter's plugin resolves `$app/paths` to this file only while
|
|
5
|
+
// that environment is building, so kit's own `ssr` build keeps kit's module.
|
|
6
|
+
//
|
|
7
|
+
// `resolve` and `asset` are kit's own (runtime/app/paths/server.js),
|
|
8
|
+
// unchanged — they are pure string logic over the app's compiled-in base and
|
|
9
|
+
// assets path, needing nothing this engine lacks — and they arrive here by
|
|
10
|
+
// re-export rather than by copy.
|
|
11
|
+
//
|
|
12
|
+
// `match` is the one substitution. Kit's own asks a manifest for the route,
|
|
13
|
+
// through `get_hooks().reroute` and `manifest._.matchers()`/`find_route`
|
|
14
|
+
// (utils/routing.js). Neither exists here: skgo does its own routing in Go,
|
|
15
|
+
// and Go already owns the exact table every page, load and endpoint request
|
|
16
|
+
// matches against, so building a second one for the engine would be the
|
|
17
|
+
// reimplementation this project's own rules forbid. What is below mirrors
|
|
18
|
+
// kit's own preparation of the pathname — decode it, strip the base — and
|
|
19
|
+
// then asks Go for the match, the same way a query or a command asks Go to
|
|
20
|
+
// run it.
|
|
21
|
+
//
|
|
22
|
+
// skgo has no reroute hook (Go does the routing, so `get_hooks` is always
|
|
23
|
+
// `{}`) and no param matchers (a matcher is a JavaScript function and skgo
|
|
24
|
+
// runs none — Loads.match and Endpoints.match both already match on the
|
|
25
|
+
// pattern alone), so both of kit's other inputs to match() are empty in this
|
|
26
|
+
// engine too, not just unavailable.
|
|
27
|
+
import { asset, resolve, base } from 'skgo:kit/paths-server';
|
|
28
|
+
import { decode_pathname } from 'skgo:kit/url';
|
|
29
|
+
|
|
30
|
+
export { asset, resolve };
|
|
31
|
+
|
|
32
|
+
export async function match(url) {
|
|
33
|
+
const target = typeof url === 'string' ? new URL(url, 'https://skgo.internal/') : url;
|
|
34
|
+
let pathname = decode_pathname(target.pathname);
|
|
35
|
+
if (base && pathname.startsWith(base)) {
|
|
36
|
+
pathname = pathname.slice(base.length) || '/';
|
|
37
|
+
}
|
|
38
|
+
const raw = globalThis.__skgo_match(pathname);
|
|
39
|
+
return JSON.parse(raw);
|
|
40
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// The `$app/server` the SSR bundle sees.
|
|
2
|
+
//
|
|
3
|
+
// It is a module of the `goja` environment and of no other: the adapter's
|
|
4
|
+
// plugin resolves `$app/server` to this file only while that environment is
|
|
5
|
+
// building, so kit's `ssr` build — the one that prerenders — keeps kit's real
|
|
6
|
+
// module. Substituting at source level is the whole reason the bundle is an
|
|
7
|
+
// environment of kit's build rather than a pass over its output: once kit's
|
|
8
|
+
// graph is linked, the boundary this file replaces no longer exists.
|
|
9
|
+
//
|
|
10
|
+
// Kit's own `query`/`command`/`form` wrappers are kept: the argument-keyed
|
|
11
|
+
// cache, `state.remote.implicit`, the `<id>/<payload>` key, the rule that an
|
|
12
|
+
// argument needs a validator, the refusal to run a command during a render and
|
|
13
|
+
// the derived event whose `url`, `params` and `route` throw inside a query are
|
|
14
|
+
// all kit's, unchanged. The only substitution is the user function body —
|
|
15
|
+
// the generated stub that throws — which becomes a call into Go.
|
|
16
|
+
//
|
|
17
|
+
// That inversion is the proof: the generated `.remote.ts` still throws, and the
|
|
18
|
+
// host binding is the only path by which a value can reach the engine.
|
|
19
|
+
//
|
|
20
|
+
// Everything this file does not name is kit's, by `export *`: `read`,
|
|
21
|
+
// `getRequestEvent`, `prerender` and `requested` come from kit's own module,
|
|
22
|
+
// and an export kit adds arrives without an edit here. An explicit export
|
|
23
|
+
// below shadows the star, which is how the four substitutions take effect.
|
|
24
|
+
import * as real from 'skgo:kit/remote';
|
|
25
|
+
import { stringify_remote_arg } from 'skgo:kit/shared';
|
|
26
|
+
import { HttpError, Redirect } from '@sveltejs/kit/internal/server';
|
|
27
|
+
import { parse } from 'skgo:kit/transport';
|
|
28
|
+
|
|
29
|
+
export * from 'skgo:kit/app-server';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Calls Go asynchronously. Its I/O runs outside the runtime while other
|
|
33
|
+
* independent queries start; the runtime resumes when Go settles the promise.
|
|
34
|
+
*
|
|
35
|
+
* A refusal comes back as one of kit's own control objects rather than a bare
|
|
36
|
+
* Error, because everything downstream classifies by type: handle_error
|
|
37
|
+
* keeps an HttpError's body and replaces anything else with Internal Error,
|
|
38
|
+
* and transformError rethrows a Redirect so that the whole document becomes
|
|
39
|
+
* the 3xx kit answers with.
|
|
40
|
+
*
|
|
41
|
+
* The answer is devalue's flat form, the same bytes Go would have sent the
|
|
42
|
+
* browser from /_app/remote/..., and it is read back with kit's own parse —
|
|
43
|
+
* the app's transport decoders. So a query answering with a custom type hands
|
|
44
|
+
* the component an instance of the app's class, and a method call on it during
|
|
45
|
+
* a render works for the same reason it works in the browser.
|
|
46
|
+
*/
|
|
47
|
+
async function host(id, payload) {
|
|
48
|
+
const raw = await globalThis.__skgo_remote(id, payload);
|
|
49
|
+
const res = JSON.parse(raw);
|
|
50
|
+
if (res.r) {
|
|
51
|
+
throw new Redirect(res.r.status, res.r.location);
|
|
52
|
+
}
|
|
53
|
+
if (res.e) {
|
|
54
|
+
throw new HttpError({ status: res.e.status ?? 500, message: res.e.message });
|
|
55
|
+
}
|
|
56
|
+
return parse(res.v);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function query(validate_or_fn, maybe_fn) {
|
|
60
|
+
const fn = (arg) => host(wrapper.__.id, stringify_remote_arg(arg));
|
|
61
|
+
const wrapper = maybe_fn ? real.query(validate_or_fn, fn) : real.query(fn);
|
|
62
|
+
return wrapper;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Calls Go once for a whole batch. Kit's own enqueue has already collected
|
|
67
|
+
* every call made to this function in one macrotask and deduplicated them by
|
|
68
|
+
* payload, so args is the batch — and the app's Go function is invoked once
|
|
69
|
+
* for all of it, which is the only thing that makes a batch query different
|
|
70
|
+
* from a query.
|
|
71
|
+
*
|
|
72
|
+
* The payloads travel as a JSON array in the place a single payload would
|
|
73
|
+
* occupy, and the answer is {n: [...]} — one envelope per payload, in the
|
|
74
|
+
* order they were sent, which is the order kit's client matches results to
|
|
75
|
+
* arguments by.
|
|
76
|
+
*/
|
|
77
|
+
async function host_batch(id, args) {
|
|
78
|
+
const payloads = args.map((arg) => stringify_remote_arg(arg));
|
|
79
|
+
const raw = await globalThis.__skgo_remote(id, JSON.stringify(payloads));
|
|
80
|
+
const res = JSON.parse(raw);
|
|
81
|
+
if (res.r) {
|
|
82
|
+
throw new Redirect(res.r.status, res.r.location);
|
|
83
|
+
}
|
|
84
|
+
if (res.e) {
|
|
85
|
+
throw new HttpError({ status: res.e.status ?? 500, message: res.e.message });
|
|
86
|
+
}
|
|
87
|
+
const nodes = res.n ?? [];
|
|
88
|
+
return (_arg, i) => {
|
|
89
|
+
const node = nodes[i];
|
|
90
|
+
if (!node) {
|
|
91
|
+
throw new HttpError({ status: 500, message: 'Internal Error' });
|
|
92
|
+
}
|
|
93
|
+
if (node.e) {
|
|
94
|
+
throw new HttpError({ status: node.e.status ?? 500, message: node.e.message });
|
|
95
|
+
}
|
|
96
|
+
return parse(node.v);
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
query.batch = (validate_or_fn, maybe_fn) => {
|
|
101
|
+
const fn = (args) => host_batch(wrapper.__.id, args);
|
|
102
|
+
const wrapper = maybe_fn ? real.query.batch(validate_or_fn, fn) : real.query.batch(fn);
|
|
103
|
+
return wrapper;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
query.live = (validate_or_fn, maybe_fn) => {
|
|
107
|
+
// A live query awaited during a render resolves to its first value, which is
|
|
108
|
+
// what kit's get_first_value takes from the generator before closing it.
|
|
109
|
+
// Go drives the producer far enough to yield once and answers with that, so
|
|
110
|
+
// the value is in the markup before any script runs; the stream itself is
|
|
111
|
+
// the browser's, and it opens after hydration against the same endpoint.
|
|
112
|
+
//
|
|
113
|
+
// An asynchronous iterator: the first value waits for Go, and Kit closes
|
|
114
|
+
// the iterator after consuming it during SSR.
|
|
115
|
+
const fn = (arg) => {
|
|
116
|
+
const payload = stringify_remote_arg(arg);
|
|
117
|
+
let taken = false;
|
|
118
|
+
return {
|
|
119
|
+
next: async () => {
|
|
120
|
+
if (taken) return { value: undefined, done: true };
|
|
121
|
+
taken = true;
|
|
122
|
+
return { value: await host(wrapper.__.id, payload), done: false };
|
|
123
|
+
},
|
|
124
|
+
return: () => ({ value: undefined, done: true })
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
const wrapper = maybe_fn ? real.query.live(validate_or_fn, fn) : real.query.live(fn);
|
|
128
|
+
return wrapper;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export function command(validate_or_fn, maybe_fn) {
|
|
132
|
+
const fn = (arg) => host(wrapper.__.id, stringify_remote_arg(arg));
|
|
133
|
+
const wrapper = maybe_fn ? real.command(validate_or_fn, fn) : real.command(fn);
|
|
134
|
+
return wrapper;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Kit's own form wrapper, with the instance kept so that a submission the
|
|
139
|
+
* browser posted without JavaScript can be put back where the instance reads
|
|
140
|
+
* it.
|
|
141
|
+
*
|
|
142
|
+
* The generated stub still throws — Go runs the handler, not this module — so
|
|
143
|
+
* the register below never runs a form body. It only records which object is
|
|
144
|
+
* which, because the internals' id is assigned by the epilogue the bundler
|
|
145
|
+
* appends after this module has been evaluated, and that id is the only handle
|
|
146
|
+
* Go has.
|
|
147
|
+
*/
|
|
148
|
+
export function form(validate_or_fn, maybe_fn) {
|
|
149
|
+
const wrapper = maybe_fn === undefined ? real.form(validate_or_fn) : real.form(validate_or_fn, maybe_fn);
|
|
150
|
+
(globalThis.__skgo_forms ??= []).push(wrapper);
|
|
151
|
+
return wrapper;
|
|
152
|
+
}
|