@skgo/sveltekit-adapter 0.5.0 → 0.9.5
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/package.json +2 -1
- package/skgo-adapter/entry.js +4 -3
- package/skgo-adapter/generated.js +118 -0
- package/skgo-adapter.js +17 -100
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skgo/sveltekit-adapter",
|
|
3
|
-
"version": "0.5
|
|
3
|
+
"version": "0.9.5",
|
|
4
4
|
"description": "SvelteKit adapter for skgo: builds a frontend that a Go binary renders and answers",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"adapter",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"skgo-adapter/app-server.js",
|
|
34
34
|
"skgo-adapter/entry.js",
|
|
35
35
|
"skgo-adapter/env.js",
|
|
36
|
+
"skgo-adapter/generated.js",
|
|
36
37
|
"skgo-adapter/identity.js",
|
|
37
38
|
"skgo-adapter/polyfill.js"
|
|
38
39
|
],
|
package/skgo-adapter/entry.js
CHANGED
|
@@ -219,6 +219,7 @@ function node_data(node) {
|
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
function build_props(req, url) {
|
|
222
|
+
const form = req.form ? devalue.parse(req.form, decoders) : null;
|
|
222
223
|
const page = {
|
|
223
224
|
error: req.error ?? null,
|
|
224
225
|
params: req.params ?? {},
|
|
@@ -226,7 +227,7 @@ function build_props(req, url) {
|
|
|
226
227
|
status: req.status ?? 200,
|
|
227
228
|
url,
|
|
228
229
|
data: {},
|
|
229
|
-
form
|
|
230
|
+
form,
|
|
230
231
|
shallow: null,
|
|
231
232
|
state: {}
|
|
232
233
|
};
|
|
@@ -239,7 +240,7 @@ function build_props(req, url) {
|
|
|
239
240
|
const props = new Props({
|
|
240
241
|
page,
|
|
241
242
|
tree: new RenderNode(components[branch[0].node], undefined),
|
|
242
|
-
form
|
|
243
|
+
form,
|
|
243
244
|
error: req.error ?? undefined
|
|
244
245
|
});
|
|
245
246
|
|
|
@@ -417,4 +418,4 @@ globalThis.__skgo_render = function (req_json) {
|
|
|
417
418
|
// A liveness check Go calls once, when it puts a fresh runtime into the pool.
|
|
418
419
|
globalThis.__skgo_ping = function () {
|
|
419
420
|
return 'ok';
|
|
420
|
-
};
|
|
421
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shape `skgo generate` promises in `skgo.remotes.json`, checked without a
|
|
3
|
+
* SvelteKit build around it.
|
|
4
|
+
*
|
|
5
|
+
* The adapter refuses a manifest it cannot read before it copies anything into
|
|
6
|
+
* the build, and the Go module refuses a build whose manifest does not match
|
|
7
|
+
* what it answers. The checks live here, apart from the adapter entry and its
|
|
8
|
+
* kit imports, so they can be exercised directly: the paths they validate are
|
|
9
|
+
* `.ts` in a TypeScript app and `.js` in a JavaScript one, and both are
|
|
10
|
+
* first-class.
|
|
11
|
+
*
|
|
12
|
+
* @param {any} parsed the parsed contents of `skgo.remotes.json`
|
|
13
|
+
* @returns {{ remotes: string[], loads: string[], actions: string[], endpoints: Record<string, string[]> }}
|
|
14
|
+
*/
|
|
15
|
+
export function validateGenerated(parsed) {
|
|
16
|
+
if (!Array.isArray(parsed.remotes)) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
'skgo: skgo.remotes.json has no `remotes` array. Run `go generate ./...` before building the frontend.'
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
for (const id of parsed.remotes) {
|
|
22
|
+
if (typeof id !== 'string' || !/^[^/]+\/[^/]+$/.test(id)) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`skgo: skgo.remotes.json lists ${JSON.stringify(id)}, which is not a <hash>/<name> id.`
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (!Array.isArray(parsed.loads)) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
'skgo: skgo.remotes.json has no `loads` array. Run `go generate ./...` before building the frontend.'
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
for (const module of parsed.loads) {
|
|
34
|
+
if (typeof module !== 'string' || !/\/\+(page|layout)\.server\.(ts|js)$/.test(module)) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`skgo: skgo.remotes.json lists ${JSON.stringify(module)}, which is not a +page.server.(ts|js) or +layout.server.(ts|js) path.`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (!Array.isArray(parsed.actions)) {
|
|
41
|
+
throw new Error('skgo: skgo.remotes.json has no `actions` array. Run `go generate ./...` before building the frontend.');
|
|
42
|
+
}
|
|
43
|
+
for (const module of parsed.actions) {
|
|
44
|
+
if (typeof module !== 'string' || !/\/\+page\.server\.(ts|js)$/.test(module)) {
|
|
45
|
+
throw new Error(`skgo: skgo.remotes.json lists ${JSON.stringify(module)}, which is not a +page.server.(ts|js) action path.`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (typeof parsed.endpoints !== 'object' || parsed.endpoints === null || Array.isArray(parsed.endpoints)) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
'skgo: skgo.remotes.json has no `endpoints` object. Run `go generate ./...` before building the frontend.'
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
for (const [id, methods] of Object.entries(parsed.endpoints)) {
|
|
54
|
+
if (!id.startsWith('/') || !Array.isArray(methods) || methods.length === 0) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`skgo: skgo.remotes.json maps ${JSON.stringify(id)} to ${JSON.stringify(methods)}, which is not a route id and its methods.`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return { remotes: parsed.remotes, loads: parsed.loads, actions: parsed.actions, endpoints: parsed.endpoints };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The check for server routes, against the one place kit reports what it
|
|
65
|
+
* compiled: `builder.routes[].api.methods`, which kit derives by importing each
|
|
66
|
+
* built `+server.js` and reading its exports
|
|
67
|
+
* (packages/kit/src/core/postbuild/analyse.js, `analyse_endpoint`). A `fallback`
|
|
68
|
+
* export travels there as `'*'`, and `skgo generate` writes the same spelling,
|
|
69
|
+
* so the two lists are compared literally.
|
|
70
|
+
*
|
|
71
|
+
* It lives here rather than in the adapter entry so it can be exercised without
|
|
72
|
+
* a SvelteKit build around it, exactly as `validateGenerated` is. That is what
|
|
73
|
+
* lets a test prove it accepts `QUERY` and `'*'` and still refuses a mismatch.
|
|
74
|
+
*
|
|
75
|
+
* This is the check that makes a hand-written `+server.ts` fail the build rather
|
|
76
|
+
* than 404 in the browser: kit would compile it, Go would never have been told
|
|
77
|
+
* about it, and the route would answer nothing.
|
|
78
|
+
*
|
|
79
|
+
* @param {{ routes: Array<{ id: string, api: { methods: string[] } }> }} builder
|
|
80
|
+
* @param {Record<string, string[]>} declared
|
|
81
|
+
* @returns {Map<string, string[]>} the methods kit compiled, per route id
|
|
82
|
+
*/
|
|
83
|
+
export function checkEndpoints(builder, declared) {
|
|
84
|
+
/** @type {Map<string, string[]>} */
|
|
85
|
+
const built = new Map();
|
|
86
|
+
for (const route of builder.routes) {
|
|
87
|
+
if (route.api.methods.length > 0) built.set(route.id, [...route.api.methods].sort());
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** @type {string[]} */
|
|
91
|
+
const problems = [];
|
|
92
|
+
for (const [id, methods] of Object.entries(declared)) {
|
|
93
|
+
const compiled = built.get(id);
|
|
94
|
+
if (!compiled) {
|
|
95
|
+
problems.push(` generated but not compiled: ${methods.join(', ')} ${id}`);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const want = [...methods].sort().join(', ');
|
|
99
|
+
const got = compiled.join(', ');
|
|
100
|
+
if (want !== got) {
|
|
101
|
+
problems.push(` ${id}: Go answers ${want}, the built +server module exports ${got}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
for (const [id, methods] of built) {
|
|
105
|
+
if (!(id in declared)) {
|
|
106
|
+
problems.push(` compiled but not generated: ${methods.join(', ')} ${id}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (problems.length) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
'skgo: skgo.remotes.json does not describe the server routes kit just compiled.\n' +
|
|
113
|
+
problems.join('\n') +
|
|
114
|
+
'\n Every server route is written in Go. Run `go generate ./...`.'
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
return built;
|
|
118
|
+
}
|
package/skgo-adapter.js
CHANGED
|
@@ -12,6 +12,7 @@ import { basename, dirname, join, relative, resolve } from 'node:path';
|
|
|
12
12
|
import { pathToFileURL } from 'node:url';
|
|
13
13
|
import { gojaDevEnvironment, gojaEnvironment, nodeTable, SSR_TARGET } from './skgo-adapter/env.js';
|
|
14
14
|
import { identity } from './skgo-adapter/identity.js';
|
|
15
|
+
import { checkEndpoints, validateGenerated } from './skgo-adapter/generated.js';
|
|
15
16
|
|
|
16
17
|
// Which skgo this adapter is: the version this package was published at, and a
|
|
17
18
|
// fingerprint taken over its own files. The Go that reads the manifest below
|
|
@@ -40,10 +41,11 @@ const KIT_COMPONENTS = join(
|
|
|
40
41
|
*
|
|
41
42
|
* It also carries the remote-function ids and the server-load module paths
|
|
42
43
|
* forward. `skgo generate` writes `skgo.remotes.json` beside this file when it
|
|
43
|
-
* emits the `.remote.ts` modules and the `+*.server.ts
|
|
44
|
-
* checks that kit really compiled those
|
|
45
|
-
* build, so the Go binary can refuse to
|
|
46
|
-
* different set of Go functions than it
|
|
44
|
+
* emits the `.remote.ts`/`.remote.js` modules and the `+*.server.ts`/
|
|
45
|
+
* `+*.server.js` stubs; the adapter checks that kit really compiled those
|
|
46
|
+
* modules and copies the lists into the build, so the Go binary can refuse to
|
|
47
|
+
* serve a frontend that was built from a different set of Go functions than it
|
|
48
|
+
* answers.
|
|
47
49
|
*
|
|
48
50
|
* @param {{ out?: string, precompress?: boolean }} [options]
|
|
49
51
|
* @returns {import('@sveltejs/kit').Adapter}
|
|
@@ -73,7 +75,7 @@ export default function skgo({ out = 'build', precompress = true } = {}) {
|
|
|
73
75
|
|
|
74
76
|
const nodes = await readNodes(builder, source, kit);
|
|
75
77
|
const serverIds = nodes.map((node) => node.server);
|
|
76
|
-
checkServerLoads(serverIds, generated.loads);
|
|
78
|
+
checkServerLoads(serverIds, generated.loads, generated.actions);
|
|
77
79
|
|
|
78
80
|
const endpoints = checkEndpoints(builder, generated.endpoints);
|
|
79
81
|
|
|
@@ -128,12 +130,15 @@ export default function skgo({ out = 'build', precompress = true } = {}) {
|
|
|
128
130
|
appDir: builder.config.appDir,
|
|
129
131
|
base: builder.config.paths.base,
|
|
130
132
|
version: builder.config.version.name,
|
|
133
|
+
trustedOrigins: builder.config.csrf.trustedOrigins,
|
|
131
134
|
// One entry per node, positionally: the vite-root-relative
|
|
132
135
|
// path of its `+*.server.ts`, or "" for a node that has
|
|
133
136
|
// none. It is how Go finds the load that answers a slot of
|
|
134
137
|
// a route's branch, and it is the same key kit itself
|
|
135
138
|
// records in the node module it builds.
|
|
136
139
|
nodes: serverIds,
|
|
140
|
+
loads: generated.loads,
|
|
141
|
+
actions: [...new Set(generated.actions)].sort(),
|
|
137
142
|
ssr: describeSSR(builder, kit, nodes),
|
|
138
143
|
routes: kit._.routes.map((/** @type {any} */ route) => ({
|
|
139
144
|
id: route.id,
|
|
@@ -197,9 +202,10 @@ export default function skgo({ out = 'build', precompress = true } = {}) {
|
|
|
197
202
|
*
|
|
198
203
|
* A missing or shapeless file is a failure, not an empty list: an adapter that
|
|
199
204
|
* quietly compares nothing to nothing reports success for an app whose two
|
|
200
|
-
* halves were never checked against each other.
|
|
205
|
+
* halves were never checked against each other. The shape checks themselves
|
|
206
|
+
* live in `skgo-adapter/generated.js`, beside the paths they validate.
|
|
201
207
|
*
|
|
202
|
-
* @returns {{ remotes: string[], loads: string[], endpoints: Record<string, string[]> }}
|
|
208
|
+
* @returns {{ remotes: string[], loads: string[], actions: string[], endpoints: Record<string, string[]> }}
|
|
203
209
|
*/
|
|
204
210
|
function readGenerated() {
|
|
205
211
|
let raw;
|
|
@@ -210,97 +216,7 @@ function readGenerated() {
|
|
|
210
216
|
'skgo: skgo.remotes.json is missing. Run `go generate ./...` before building the frontend.'
|
|
211
217
|
);
|
|
212
218
|
}
|
|
213
|
-
|
|
214
|
-
if (!Array.isArray(parsed.remotes)) {
|
|
215
|
-
throw new Error(
|
|
216
|
-
'skgo: skgo.remotes.json has no `remotes` array. Run `go generate ./...` before building the frontend.'
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
for (const id of parsed.remotes) {
|
|
220
|
-
if (typeof id !== 'string' || !/^[^/]+\/[^/]+$/.test(id)) {
|
|
221
|
-
throw new Error(
|
|
222
|
-
`skgo: skgo.remotes.json lists ${JSON.stringify(id)}, which is not a <hash>/<name> id.`
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
if (!Array.isArray(parsed.loads)) {
|
|
227
|
-
throw new Error(
|
|
228
|
-
'skgo: skgo.remotes.json has no `loads` array. Run `go generate ./...` before building the frontend.'
|
|
229
|
-
);
|
|
230
|
-
}
|
|
231
|
-
for (const module of parsed.loads) {
|
|
232
|
-
if (typeof module !== 'string' || !/\/\+(page|layout)\.server\.ts$/.test(module)) {
|
|
233
|
-
throw new Error(
|
|
234
|
-
`skgo: skgo.remotes.json lists ${JSON.stringify(module)}, which is not a +page.server.ts or +layout.server.ts path.`
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
if (typeof parsed.endpoints !== 'object' || parsed.endpoints === null || Array.isArray(parsed.endpoints)) {
|
|
239
|
-
throw new Error(
|
|
240
|
-
'skgo: skgo.remotes.json has no `endpoints` object. Run `go generate ./...` before building the frontend.'
|
|
241
|
-
);
|
|
242
|
-
}
|
|
243
|
-
for (const [id, methods] of Object.entries(parsed.endpoints)) {
|
|
244
|
-
if (!id.startsWith('/') || !Array.isArray(methods) || methods.length === 0) {
|
|
245
|
-
throw new Error(
|
|
246
|
-
`skgo: skgo.remotes.json maps ${JSON.stringify(id)} to ${JSON.stringify(methods)}, which is not a route id and its methods.`
|
|
247
|
-
);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
return { remotes: parsed.remotes, loads: parsed.loads, endpoints: parsed.endpoints };
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* The same check for server routes, against the one place kit reports what it
|
|
255
|
-
* compiled: `builder.routes[].api.methods`, which kit derives by importing each
|
|
256
|
-
* built `+server.js` and reading its exports
|
|
257
|
-
* (packages/kit/src/core/postbuild/analyse.js, `analyse_endpoint`). A `fallback`
|
|
258
|
-
* export travels there as `'*'`, and `skgo generate` writes the same spelling,
|
|
259
|
-
* so the two lists are compared literally.
|
|
260
|
-
*
|
|
261
|
-
* This is the check that makes a hand-written `+server.ts` fail the build rather
|
|
262
|
-
* than 404 in the browser: kit would compile it, Go would never have been told
|
|
263
|
-
* about it, and the route would answer nothing.
|
|
264
|
-
*
|
|
265
|
-
* @param {import('@sveltejs/kit').Builder} builder
|
|
266
|
-
* @param {Record<string, string[]>} declared
|
|
267
|
-
* @returns {Map<string, string[]>} the methods kit compiled, per route id
|
|
268
|
-
*/
|
|
269
|
-
function checkEndpoints(builder, declared) {
|
|
270
|
-
/** @type {Map<string, string[]>} */
|
|
271
|
-
const built = new Map();
|
|
272
|
-
for (const route of builder.routes) {
|
|
273
|
-
if (route.api.methods.length > 0) built.set(route.id, [...route.api.methods].sort());
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/** @type {string[]} */
|
|
277
|
-
const problems = [];
|
|
278
|
-
for (const [id, methods] of Object.entries(declared)) {
|
|
279
|
-
const compiled = built.get(id);
|
|
280
|
-
if (!compiled) {
|
|
281
|
-
problems.push(` generated but not compiled: ${methods.join(', ')} ${id}`);
|
|
282
|
-
continue;
|
|
283
|
-
}
|
|
284
|
-
const want = [...methods].sort().join(', ');
|
|
285
|
-
const got = compiled.join(', ');
|
|
286
|
-
if (want !== got) {
|
|
287
|
-
problems.push(` ${id}: Go answers ${want}, the built +server.ts exports ${got}`);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
for (const [id, methods] of built) {
|
|
291
|
-
if (!(id in declared)) {
|
|
292
|
-
problems.push(` compiled but not generated: ${methods.join(', ')} ${id}`);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
if (problems.length) {
|
|
297
|
-
throw new Error(
|
|
298
|
-
'skgo: skgo.remotes.json does not describe the server routes kit just compiled.\n' +
|
|
299
|
-
problems.join('\n') +
|
|
300
|
-
'\n Every server route is written in Go. Run `go generate ./...`.'
|
|
301
|
-
);
|
|
302
|
-
}
|
|
303
|
-
return built;
|
|
219
|
+
return validateGenerated(JSON.parse(raw));
|
|
304
220
|
}
|
|
305
221
|
|
|
306
222
|
/**
|
|
@@ -740,10 +656,11 @@ function* walk(dir) {
|
|
|
740
656
|
*
|
|
741
657
|
* @param {string[]} nodes
|
|
742
658
|
* @param {string[]} loads
|
|
659
|
+
* @param {string[]} actions
|
|
743
660
|
*/
|
|
744
|
-
function checkServerLoads(nodes, loads) {
|
|
661
|
+
function checkServerLoads(nodes, loads, actions) {
|
|
745
662
|
const built = new Set(nodes.filter(Boolean));
|
|
746
|
-
const declared = new Set(loads);
|
|
663
|
+
const declared = new Set([...loads, ...actions]);
|
|
747
664
|
|
|
748
665
|
const missing = [...declared].filter((id) => !built.has(id));
|
|
749
666
|
const extra = [...built].filter((id) => !declared.has(id));
|