@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 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
+ }