@skgo/sveltekit-adapter 0.9.0 → 0.10.0
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 +66 -43
- package/skgo-adapter/env.js +17 -3
- package/skgo-adapter/generated.js +118 -0
- package/skgo-adapter.js +11 -111
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skgo/sveltekit-adapter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
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
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
} from '@sveltejs/kit/internal/server';
|
|
23
23
|
import * as devalue from 'skgo:devalue';
|
|
24
24
|
import { decoders, init_transport, parse } from 'skgo:kit/transport';
|
|
25
|
-
import { components } from 'skgo:nodes';
|
|
25
|
+
import { components, universalLoads } from 'skgo:nodes';
|
|
26
26
|
import { transport } from 'skgo:hooks';
|
|
27
27
|
|
|
28
28
|
/**
|
|
@@ -218,7 +218,7 @@ function node_data(node) {
|
|
|
218
218
|
});
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
-
function build_props(req, url) {
|
|
221
|
+
async function build_props(req, url) {
|
|
222
222
|
const form = req.form ? devalue.parse(req.form, decoders) : null;
|
|
223
223
|
const page = {
|
|
224
224
|
error: req.error ?? null,
|
|
@@ -248,7 +248,30 @@ function build_props(req, url) {
|
|
|
248
248
|
let data = props.page.data;
|
|
249
249
|
|
|
250
250
|
for (let i = 0; i < branch.length; i += 1) {
|
|
251
|
-
|
|
251
|
+
const server_data = node_data(branch[i]);
|
|
252
|
+
const load = universalLoads[branch[i].node];
|
|
253
|
+
const parent_data = data;
|
|
254
|
+
const result = load
|
|
255
|
+
? await load.call(null, {
|
|
256
|
+
url,
|
|
257
|
+
params: req.params ?? {},
|
|
258
|
+
data: server_data,
|
|
259
|
+
route: { id: req.route_id ?? null },
|
|
260
|
+
parent: async () => parent_data,
|
|
261
|
+
depends: () => {},
|
|
262
|
+
untrack: (fn) => fn(),
|
|
263
|
+
fetch: () => {
|
|
264
|
+
throw new Error('skgo: universal load fetch is not supported during rendering');
|
|
265
|
+
},
|
|
266
|
+
setHeaders: () => {
|
|
267
|
+
throw new Error('skgo: universal load setHeaders is not supported during rendering');
|
|
268
|
+
}
|
|
269
|
+
})
|
|
270
|
+
: server_data;
|
|
271
|
+
if (result != null && Object.getPrototypeOf(result) !== Object.prototype) {
|
|
272
|
+
throw new Error('skgo: universal load must return a plain object');
|
|
273
|
+
}
|
|
274
|
+
data = { ...data, ...result };
|
|
252
275
|
current_node.data = data;
|
|
253
276
|
|
|
254
277
|
if (i < branch.length - 1) {
|
|
@@ -344,48 +367,48 @@ globalThis.__skgo_render = function (req_json) {
|
|
|
344
367
|
try {
|
|
345
368
|
const req = JSON.parse(req_json);
|
|
346
369
|
const url = new URL(req.url);
|
|
347
|
-
const
|
|
370
|
+
const props_promise = build_props(req, url);
|
|
348
371
|
const state = make_state();
|
|
349
372
|
const event = make_event(req, url);
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
373
|
+
const promise = props_promise.then((props) => {
|
|
374
|
+
result.status = props.page.status;
|
|
375
|
+
result.error = req.error ?? null;
|
|
376
|
+
const options = {
|
|
377
|
+
context: new Map([['__request__', { page: props.page }]]),
|
|
378
|
+
// kit's own \`csp.script_needs_nonce ? { nonce: csp.nonce } : {
|
|
379
|
+
// hash: csp.script_needs_hash }\` (page/render.js:198), passed to
|
|
380
|
+
// Svelte's own renderer so the one inline script Svelte can still
|
|
381
|
+
// emit on its own — its hydratable-async-block script
|
|
382
|
+
// (internal/server/renderer.js's #hydratable_block, for a
|
|
383
|
+
// component's own top-level await) — carries the same nonce or
|
|
384
|
+
// hash decision the boot script gets from Go (csp.go). Go decided
|
|
385
|
+
// which branch this request is in before the engine ever ran;
|
|
386
|
+
// req.csp carries only the answer.
|
|
387
|
+
csp: req.csp.nonce ? { nonce: req.csp.nonce } : { hash: !!req.csp.hash },
|
|
388
|
+
// kit's own (page/render.js): the transform every error boundary's
|
|
389
|
+
// error passes through on its way to the failed snippet. It is
|
|
390
|
+
// what makes page.status and page.error inside a rendering
|
|
391
|
+
// component the values kit would give, and what turns an
|
|
392
|
+
// unexpected throw into Internal Error rather than a stack trace on
|
|
393
|
+
// the page. A redirect is rethrown, because a redirect is an answer
|
|
394
|
+
// for the whole document rather than for one boundary.
|
|
395
|
+
transformError: (e) => {
|
|
396
|
+
if (e instanceof Redirect) throw e;
|
|
397
|
+
const handled = handle_error(e);
|
|
398
|
+
result.error = handled;
|
|
399
|
+
result.status = handled.status;
|
|
400
|
+
props.page.error = handled;
|
|
401
|
+
props.page.status = handled.status;
|
|
402
|
+
return handled;
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
// seed_form runs inside the request store rather than before it: a
|
|
406
|
+
// keyed submission's call to for(key) needs the request store, the
|
|
407
|
+
// same as the page component's own call to it does.
|
|
408
|
+
return with_request_store({ event, state }, () => {
|
|
409
|
+
seed_form(req, state);
|
|
410
|
+
return render(Root, { ...options, props });
|
|
411
|
+
});
|
|
389
412
|
});
|
|
390
413
|
|
|
391
414
|
Promise.resolve(promise).then(
|
package/skgo-adapter/env.js
CHANGED
|
@@ -428,14 +428,21 @@ function basenameOf(file) {
|
|
|
428
428
|
* kit's build produced, in the manifest's own node order.
|
|
429
429
|
*
|
|
430
430
|
* @param {string} root
|
|
431
|
-
* @param {Array<{ component: string | null }>} nodes
|
|
431
|
+
* @param {Array<{ component: string | null, universal: string | null }>} nodes
|
|
432
432
|
*/
|
|
433
433
|
export function nodeTable(root, nodes) {
|
|
434
434
|
/** @type {string[]} */
|
|
435
435
|
const imports = [];
|
|
436
436
|
/** @type {string[]} */
|
|
437
437
|
const table = [];
|
|
438
|
+
const loads = [];
|
|
438
439
|
nodes.forEach((node, i) => {
|
|
440
|
+
if (node.universal) {
|
|
441
|
+
imports.push(`import * as U${i} from ${JSON.stringify(join(root, node.universal))};`);
|
|
442
|
+
loads.push(`U${i}.load`);
|
|
443
|
+
} else {
|
|
444
|
+
loads.push('undefined');
|
|
445
|
+
}
|
|
439
446
|
if (!node.component) {
|
|
440
447
|
table.push('undefined');
|
|
441
448
|
return;
|
|
@@ -443,7 +450,7 @@ export function nodeTable(root, nodes) {
|
|
|
443
450
|
imports.push(`import N${i} from ${JSON.stringify(join(root, node.component))};`);
|
|
444
451
|
table.push(`N${i}`);
|
|
445
452
|
});
|
|
446
|
-
return `${imports.join('\n')}\nexport const components = [${table.join(', ')}];\n`;
|
|
453
|
+
return `${imports.join('\n')}\nexport const components = [${table.join(', ')}];\nexport const universalLoads = [${loads.join(', ')}];\n`;
|
|
447
454
|
}
|
|
448
455
|
|
|
449
456
|
/**
|
|
@@ -565,7 +572,14 @@ function devNodeTable(manifest, root) {
|
|
|
565
572
|
const imports = [];
|
|
566
573
|
/** @type {string[]} */
|
|
567
574
|
const table = [];
|
|
575
|
+
const loads = [];
|
|
568
576
|
for (const [index, node] of manifest.nodes.entries()) {
|
|
577
|
+
if (node.universal) {
|
|
578
|
+
imports.push(`import * as U${index} from ${JSON.stringify(resolve(root, node.universal))};`);
|
|
579
|
+
loads.push(`U${index}.load`);
|
|
580
|
+
} else {
|
|
581
|
+
loads.push('undefined');
|
|
582
|
+
}
|
|
569
583
|
if (!node.component) {
|
|
570
584
|
table.push('undefined');
|
|
571
585
|
continue;
|
|
@@ -573,7 +587,7 @@ function devNodeTable(manifest, root) {
|
|
|
573
587
|
imports.push(`import N${index} from ${JSON.stringify(resolve(root, node.component))};`);
|
|
574
588
|
table.push(`N${index}`);
|
|
575
589
|
}
|
|
576
|
-
return `${imports.join('\n')}\nexport const components = [${table.join(', ')}];\n`;
|
|
590
|
+
return `${imports.join('\n')}\nexport const components = [${table.join(', ')}];\nexport const universalLoads = [${loads.join(', ')}];\n`;
|
|
577
591
|
}
|
|
578
592
|
|
|
579
593
|
/**
|
|
@@ -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}
|
|
@@ -200,7 +202,8 @@ export default function skgo({ out = 'build', precompress = true } = {}) {
|
|
|
200
202
|
*
|
|
201
203
|
* A missing or shapeless file is a failure, not an empty list: an adapter that
|
|
202
204
|
* quietly compares nothing to nothing reports success for an app whose two
|
|
203
|
-
* 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.
|
|
204
207
|
*
|
|
205
208
|
* @returns {{ remotes: string[], loads: string[], actions: string[], endpoints: Record<string, string[]> }}
|
|
206
209
|
*/
|
|
@@ -213,105 +216,7 @@ function readGenerated() {
|
|
|
213
216
|
'skgo: skgo.remotes.json is missing. Run `go generate ./...` before building the frontend.'
|
|
214
217
|
);
|
|
215
218
|
}
|
|
216
|
-
|
|
217
|
-
if (!Array.isArray(parsed.remotes)) {
|
|
218
|
-
throw new Error(
|
|
219
|
-
'skgo: skgo.remotes.json has no `remotes` array. Run `go generate ./...` before building the frontend.'
|
|
220
|
-
);
|
|
221
|
-
}
|
|
222
|
-
for (const id of parsed.remotes) {
|
|
223
|
-
if (typeof id !== 'string' || !/^[^/]+\/[^/]+$/.test(id)) {
|
|
224
|
-
throw new Error(
|
|
225
|
-
`skgo: skgo.remotes.json lists ${JSON.stringify(id)}, which is not a <hash>/<name> id.`
|
|
226
|
-
);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
if (!Array.isArray(parsed.loads)) {
|
|
230
|
-
throw new Error(
|
|
231
|
-
'skgo: skgo.remotes.json has no `loads` array. Run `go generate ./...` before building the frontend.'
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
|
-
for (const module of parsed.loads) {
|
|
235
|
-
if (typeof module !== 'string' || !/\/\+(page|layout)\.server\.ts$/.test(module)) {
|
|
236
|
-
throw new Error(
|
|
237
|
-
`skgo: skgo.remotes.json lists ${JSON.stringify(module)}, which is not a +page.server.ts or +layout.server.ts path.`
|
|
238
|
-
);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
if (!Array.isArray(parsed.actions)) {
|
|
242
|
-
throw new Error('skgo: skgo.remotes.json has no `actions` array. Run `go generate ./...` before building the frontend.');
|
|
243
|
-
}
|
|
244
|
-
for (const module of parsed.actions) {
|
|
245
|
-
if (typeof module !== 'string' || !/\/\+page\.server\.ts$/.test(module)) {
|
|
246
|
-
throw new Error(`skgo: skgo.remotes.json lists ${JSON.stringify(module)}, which is not a +page.server.ts action path.`);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
if (typeof parsed.endpoints !== 'object' || parsed.endpoints === null || Array.isArray(parsed.endpoints)) {
|
|
250
|
-
throw new Error(
|
|
251
|
-
'skgo: skgo.remotes.json has no `endpoints` object. Run `go generate ./...` before building the frontend.'
|
|
252
|
-
);
|
|
253
|
-
}
|
|
254
|
-
for (const [id, methods] of Object.entries(parsed.endpoints)) {
|
|
255
|
-
if (!id.startsWith('/') || !Array.isArray(methods) || methods.length === 0) {
|
|
256
|
-
throw new Error(
|
|
257
|
-
`skgo: skgo.remotes.json maps ${JSON.stringify(id)} to ${JSON.stringify(methods)}, which is not a route id and its methods.`
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
return { remotes: parsed.remotes, loads: parsed.loads, actions: parsed.actions, endpoints: parsed.endpoints };
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* The same check for server routes, against the one place kit reports what it
|
|
266
|
-
* compiled: `builder.routes[].api.methods`, which kit derives by importing each
|
|
267
|
-
* built `+server.js` and reading its exports
|
|
268
|
-
* (packages/kit/src/core/postbuild/analyse.js, `analyse_endpoint`). A `fallback`
|
|
269
|
-
* export travels there as `'*'`, and `skgo generate` writes the same spelling,
|
|
270
|
-
* so the two lists are compared literally.
|
|
271
|
-
*
|
|
272
|
-
* This is the check that makes a hand-written `+server.ts` fail the build rather
|
|
273
|
-
* than 404 in the browser: kit would compile it, Go would never have been told
|
|
274
|
-
* about it, and the route would answer nothing.
|
|
275
|
-
*
|
|
276
|
-
* @param {import('@sveltejs/kit').Builder} builder
|
|
277
|
-
* @param {Record<string, string[]>} declared
|
|
278
|
-
* @returns {Map<string, string[]>} the methods kit compiled, per route id
|
|
279
|
-
*/
|
|
280
|
-
function checkEndpoints(builder, declared) {
|
|
281
|
-
/** @type {Map<string, string[]>} */
|
|
282
|
-
const built = new Map();
|
|
283
|
-
for (const route of builder.routes) {
|
|
284
|
-
if (route.api.methods.length > 0) built.set(route.id, [...route.api.methods].sort());
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/** @type {string[]} */
|
|
288
|
-
const problems = [];
|
|
289
|
-
for (const [id, methods] of Object.entries(declared)) {
|
|
290
|
-
const compiled = built.get(id);
|
|
291
|
-
if (!compiled) {
|
|
292
|
-
problems.push(` generated but not compiled: ${methods.join(', ')} ${id}`);
|
|
293
|
-
continue;
|
|
294
|
-
}
|
|
295
|
-
const want = [...methods].sort().join(', ');
|
|
296
|
-
const got = compiled.join(', ');
|
|
297
|
-
if (want !== got) {
|
|
298
|
-
problems.push(` ${id}: Go answers ${want}, the built +server.ts exports ${got}`);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
for (const [id, methods] of built) {
|
|
302
|
-
if (!(id in declared)) {
|
|
303
|
-
problems.push(` compiled but not generated: ${methods.join(', ')} ${id}`);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
if (problems.length) {
|
|
308
|
-
throw new Error(
|
|
309
|
-
'skgo: skgo.remotes.json does not describe the server routes kit just compiled.\n' +
|
|
310
|
-
problems.join('\n') +
|
|
311
|
-
'\n Every server route is written in Go. Run `go generate ./...`.'
|
|
312
|
-
);
|
|
313
|
-
}
|
|
314
|
-
return built;
|
|
219
|
+
return validateGenerated(JSON.parse(raw));
|
|
315
220
|
}
|
|
316
221
|
|
|
317
222
|
/**
|
|
@@ -484,16 +389,10 @@ async function readNodes(builder, source, kit) {
|
|
|
484
389
|
}
|
|
485
390
|
const module = await import(pathToFileURL(file).href);
|
|
486
391
|
|
|
487
|
-
if (typeof module.universal?.load === 'function') {
|
|
488
|
-
throw new Error(
|
|
489
|
-
`skgo: ${module.universal_id} exports a \`load\`. Every load in a skgo app is written in Go; ` +
|
|
490
|
-
'a universal load would have to run in the browser and in the SSR engine, and skgo runs neither.'
|
|
491
|
-
);
|
|
492
|
-
}
|
|
493
|
-
|
|
494
392
|
nodes.push({
|
|
495
393
|
index,
|
|
496
394
|
server: module.server_id ?? '',
|
|
395
|
+
universal: typeof module.universal?.load === 'function' ? module.universal_id : null,
|
|
497
396
|
component: module.component ? componentSource(file, dir) : null,
|
|
498
397
|
imports: module.imports ?? [],
|
|
499
398
|
stylesheets: module.stylesheets ?? [],
|
|
@@ -510,6 +409,7 @@ async function readNodes(builder, source, kit) {
|
|
|
510
409
|
* @typedef {{
|
|
511
410
|
* index: number,
|
|
512
411
|
* server: string,
|
|
412
|
+
* universal: string | null,
|
|
513
413
|
* component: string | null,
|
|
514
414
|
* imports: string[],
|
|
515
415
|
* stylesheets: string[],
|