@skgo/sveltekit-adapter 0.9.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skgo/sveltekit-adapter",
3
- "version": "0.9.0",
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
  ],
@@ -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` stubs; the adapter
44
- * checks that kit really compiled those modules and copies the lists into the
45
- * build, so the Go binary can refuse to serve a frontend that was built from a
46
- * different set of Go functions than it answers.
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
- const parsed = JSON.parse(raw);
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
  /**