@torpor/build 1.0.0 → 1.0.1

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.
Files changed (174) hide show
  1. package/dist/{flattenHeaders-C_YYLdOq.mjs → Server-BnjLJleZ.mjs} +2 -12
  2. package/dist/Server-BnjLJleZ.mjs.map +1 -0
  3. package/dist/{Server-C_IKav_e.d.mts → Server-Cler2w5O.d.mts} +3 -48
  4. package/dist/Server-Cler2w5O.d.mts.map +1 -0
  5. package/dist/ServerEvent-CTx3Q-hk.d.mts +48 -0
  6. package/dist/ServerEvent-CTx3Q-hk.d.mts.map +1 -0
  7. package/dist/{Site-CMa153FA.d.mts → Site-CE0_o5Ci.d.mts} +3 -2
  8. package/dist/Site-CE0_o5Ci.d.mts.map +1 -0
  9. package/dist/bin/index.js +1 -1
  10. package/dist/clientEntry.d.mts +1 -0
  11. package/dist/clientEntry.mjs +59 -0
  12. package/dist/clientEntry.mjs.map +1 -0
  13. package/dist/clientEntryDev.d.mts +1 -0
  14. package/dist/clientEntryDev.mjs +7 -0
  15. package/dist/clientEntryDev.mjs.map +1 -0
  16. package/dist/dev.d.mts +3 -0
  17. package/dist/dev.mjs +3 -0
  18. package/dist/endpoint-EULRGCKA.mjs +108 -0
  19. package/dist/endpoint-EULRGCKA.mjs.map +1 -0
  20. package/dist/flattenHeaders-DM5qsSqO.mjs +13 -0
  21. package/dist/flattenHeaders-DM5qsSqO.mjs.map +1 -0
  22. package/dist/index.d.mts +1 -1
  23. package/dist/load-BntzvmwL.mjs +228 -0
  24. package/dist/load-BntzvmwL.mjs.map +1 -0
  25. package/dist/nav.mjs +1 -225
  26. package/dist/nav.mjs.map +1 -1
  27. package/dist/openapi.d.mts +1 -1
  28. package/dist/run.d.mts +1 -1
  29. package/dist/run.d.mts.map +1 -1
  30. package/dist/run.mjs +1 -1
  31. package/dist/{runOpenApi-BUi1RH1l.mjs → runOpenApi-DlOgF2Il.mjs} +54 -16
  32. package/dist/runOpenApi-DlOgF2Il.mjs.map +1 -0
  33. package/dist/server/Server.d.mts +2 -0
  34. package/dist/server/Server.mjs +2 -0
  35. package/dist/server.d.mts +2 -1
  36. package/dist/server.d.mts.map +1 -1
  37. package/dist/server.mjs +2 -1
  38. package/dist/server.mjs.map +1 -1
  39. package/dist/serverEntry-1qY3wZig.mjs +351 -0
  40. package/dist/serverEntry-1qY3wZig.mjs.map +1 -0
  41. package/dist/serverEntry-BAO8m03V.d.mts +6 -0
  42. package/dist/serverEntry-BAO8m03V.d.mts.map +1 -0
  43. package/dist/serverEntry.d.mts +2 -0
  44. package/dist/serverEntry.mjs +2 -0
  45. package/dist/test.d.mts +2 -2
  46. package/dist/test.mjs +2 -104
  47. package/dist/test.mjs.map +1 -1
  48. package/package.json +9 -8
  49. package/dist/Server-C_IKav_e.d.mts.map +0 -1
  50. package/dist/Site-CMa153FA.d.mts.map +0 -1
  51. package/dist/flattenHeaders-C_YYLdOq.mjs.map +0 -1
  52. package/dist/runOpenApi-BUi1RH1l.mjs.map +0 -1
  53. package/src/bin/index.ts +0 -22
  54. package/src/dev.ts +0 -10
  55. package/src/form/formDataToRecord.ts +0 -23
  56. package/src/form/readForm.ts +0 -71
  57. package/src/form.ts +0 -4
  58. package/src/index.ts +0 -82
  59. package/src/nav/api.test-d.ts +0 -79
  60. package/src/nav/api.ts +0 -104
  61. package/src/nav/formSubmit.ts +0 -70
  62. package/src/nav/load.ts +0 -9
  63. package/src/nav/loadData.ts +0 -134
  64. package/src/nav/navigate.ts +0 -189
  65. package/src/nav/reload.ts +0 -9
  66. package/src/nav/route.ts +0 -38
  67. package/src/nav.ts +0 -6
  68. package/src/openapi/docsHtml.ts +0 -25
  69. package/src/openapi/document.ts +0 -154
  70. package/src/openapi/plugin.ts +0 -78
  71. package/src/openapi/types.ts +0 -82
  72. package/src/openapi.ts +0 -14
  73. package/src/response/TypedResponse.ts +0 -17
  74. package/src/response/badRequest.ts +0 -31
  75. package/src/response/created.ts +0 -31
  76. package/src/response/forbidden.ts +0 -21
  77. package/src/response/found.ts +0 -30
  78. package/src/response/methodNotAllowed.ts +0 -18
  79. package/src/response/movedPermanently.ts +0 -25
  80. package/src/response/notFound.ts +0 -24
  81. package/src/response/notModified.ts +0 -34
  82. package/src/response/ok.ts +0 -38
  83. package/src/response/permanentRedirect.ts +0 -30
  84. package/src/response/response.ts +0 -22
  85. package/src/response/seeOther.ts +0 -19
  86. package/src/response/serverError.ts +0 -24
  87. package/src/response/temporaryRedirect.ts +0 -31
  88. package/src/response/transfer.ts +0 -14
  89. package/src/response/unauthorized.ts +0 -21
  90. package/src/response/unprocessable.ts +0 -30
  91. package/src/response.ts +0 -37
  92. package/src/run/depCache.ts +0 -175
  93. package/src/run/devPlugin.ts +0 -128
  94. package/src/run/prepareTemplate.ts +0 -42
  95. package/src/run/run.ts +0 -86
  96. package/src/run/runBuild.ts +0 -128
  97. package/src/run/runDev.ts +0 -177
  98. package/src/run/runOpenApi.ts +0 -52
  99. package/src/run/runPreview.ts +0 -84
  100. package/src/run/staleTorpCopies.ts +0 -100
  101. package/src/run.ts +0 -7
  102. package/src/schema.ts +0 -7
  103. package/src/server/CookieHelper.ts +0 -46
  104. package/src/server/HeaderHelper.ts +0 -25
  105. package/src/server/Server.ts +0 -168
  106. package/src/server/ServerEvent.ts +0 -62
  107. package/src/server/connect/connectMiddleware.ts +0 -85
  108. package/src/server/connect/flattenHeaders.ts +0 -22
  109. package/src/server/connect/nodeMessageToNodeResponse.ts +0 -89
  110. package/src/server/connect/requestToNodeMessage.ts +0 -29
  111. package/src/server/contentType.ts +0 -85
  112. package/src/server/types/HttpMethod.ts +0 -12
  113. package/src/server/types/MiddlewareFunction.ts +0 -22
  114. package/src/server/types/ServerFunction.ts +0 -7
  115. package/src/server.ts +0 -7
  116. package/src/site/Router.ts +0 -175
  117. package/src/site/Site.ts +0 -388
  118. package/src/site/checkLayoutSlots.ts +0 -114
  119. package/src/site/checkRoutes.ts +0 -435
  120. package/src/site/clientEntry.ts +0 -121
  121. package/src/site/clientEntryDev.ts +0 -3
  122. package/src/site/defaultAdapter.ts +0 -11
  123. package/src/site/layoutSlots.ts +0 -53
  124. package/src/site/manifest.ts +0 -173
  125. package/src/site/serverEntry.ts +0 -648
  126. package/src/state/$page.ts +0 -25
  127. package/src/state/$serverPage.ts +0 -22
  128. package/src/state/client.ts +0 -15
  129. package/src/state.ts +0 -3
  130. package/src/test/runTest.ts +0 -538
  131. package/src/test.ts +0 -3
  132. package/src/types/Adapter.ts +0 -20
  133. package/src/types/ClientState.ts +0 -8
  134. package/src/types/InternalState.ts +0 -7
  135. package/src/types/Jsonify.ts +0 -20
  136. package/src/types/LayoutHandler.ts +0 -5
  137. package/src/types/LayoutPath.ts +0 -10
  138. package/src/types/ManifestRoute.ts +0 -6
  139. package/src/types/PageData.test-d.ts +0 -136
  140. package/src/types/PageData.ts +0 -35
  141. package/src/types/PageEndPoint.ts +0 -55
  142. package/src/types/PageForm.test-d.ts +0 -78
  143. package/src/types/PageForm.ts +0 -29
  144. package/src/types/PageLoadEvent.ts +0 -26
  145. package/src/types/PageLoadReturn.ts +0 -13
  146. package/src/types/PageProps.ts +0 -14
  147. package/src/types/PageServerAction.ts +0 -13
  148. package/src/types/PageServerEndPoint.test-d.ts +0 -105
  149. package/src/types/PageServerEndPoint.ts +0 -110
  150. package/src/types/PageServerLoad.ts +0 -15
  151. package/src/types/PageState.ts +0 -8
  152. package/src/types/ParseRouteParams.test-d.ts +0 -84
  153. package/src/types/ParseRouteParams.ts +0 -64
  154. package/src/types/Route.ts +0 -30
  155. package/src/types/RouteHandler.ts +0 -23
  156. package/src/types/RouteLayoutHandler.ts +0 -5
  157. package/src/types/RouteMatchResult.ts +0 -7
  158. package/src/types/RouteType.ts +0 -19
  159. package/src/types/ServerEndPoint.test-d.ts +0 -77
  160. package/src/types/ServerEndPoint.ts +0 -169
  161. package/src/types/ServerHook.ts +0 -18
  162. package/src/types/ServerLoadEvent.ts +0 -82
  163. package/src/types/ServerRequest.ts +0 -15
  164. package/src/types/SitePlugin.ts +0 -24
  165. package/src/types/StandardSchema.ts +0 -109
  166. package/src/utils/pathToRegex.ts +0 -17
  167. package/src/utils/pathTrie.ts +0 -182
  168. package/src/utils/searchParamsToRecord.ts +0 -18
  169. package/src/utils/torporPackages.ts +0 -150
  170. package/src/utils/tsconfigAliases.ts +0 -90
  171. package/src/validation/ValidationError.ts +0 -18
  172. package/src/validation/endpoint.ts +0 -83
  173. package/src/validation/validate.ts +0 -26
  174. package/src/vite-env.d.ts +0 -2
@@ -1,435 +0,0 @@
1
- import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
2
- import path from "node:path";
3
- import type Route from "../types/Route";
4
- import {
5
- ERROR_ROUTE,
6
- HOOK_ROUTE,
7
- HOOK_SERVER_ROUTE,
8
- LAYOUT_ROUTE,
9
- LAYOUT_SERVER_ROUTE,
10
- PAGE_SERVER_ROUTE,
11
- SERVER_ROUTE,
12
- } from "../types/RouteType";
13
- import tsconfigAliases, { type AliasEntry } from "../utils/tsconfigAliases";
14
- import type Site from "./Site";
15
-
16
- /**
17
- * A problem found in a route file's type annotations by `checkRoutes`.
18
- */
19
- export interface RouteCheckIssue {
20
- /** The route file, relative to the site root */
21
- file: string;
22
- /** 1-based line number of the annotation */
23
- line: number;
24
- severity: "error" | "warning";
25
- message: string;
26
- }
27
-
28
- const ENDPOINT_TYPES = ["PageServerEndPoint", "PageEndPoint", "ServerEndPoint", "ServerHook"];
29
-
30
- const USAGE_RE = new RegExp(`\\b(satisfies|as|:)\\s*\\b(${ENDPOINT_TYPES.join("|")})\\b`, "g");
31
-
32
- const TYPE_NAME_RE = new RegExp(`^(?:${ENDPOINT_TYPES.join("|")})\\b`);
33
-
34
- /**
35
- * Checks the route files of a site against their type annotations:
36
- *
37
- * - Route path annotations (e.g. `PageServerEndPoint<"/posts/[id]">`) must
38
- * match the route derived from the file's location, so types can't silently
39
- * lie when files move
40
- * - Exports that widen the endpoint type (`satisfies T as T`, `as T`, or a
41
- * variable annotation) are flagged, because they destroy `PageData` /
42
- * `PageForm` / param inference
43
- * - Routes with dynamic params but no route annotation are flagged, because
44
- * their params are loosely typed
45
- *
46
- * Only `.ts` route files are checked; annotations are matched by the standard
47
- * type names (renamed imports are not detected). Best-effort, source-level —
48
- * comments mentioning these patterns may produce false positives.
49
- */
50
- export function checkRoutes(site: Site): RouteCheckIssue[] {
51
- const issues: RouteCheckIssue[] = [];
52
- for (const route of site.routes) {
53
- issues.push(...checkRoute(site, route));
54
- }
55
- return issues;
56
- }
57
-
58
- /**
59
- * Checks a single route's file. Returns no issues for routes without a `.ts`
60
- * file (`.torp` components, inline endpoints, `.js` files).
61
- */
62
- export function checkRoute(site: Site, route: Route): RouteCheckIssue[] {
63
- if (!route.file || !route.file.endsWith(".ts")) return [];
64
- let source: string;
65
- try {
66
- source = readFileSync(path.join(site.root, route.file), "utf8");
67
- } catch {
68
- return [];
69
- }
70
- return checkRouteSource(source, route.file, expectedAnnotationPath(route), typeNameFor(route));
71
- }
72
-
73
- /**
74
- * Checks a single route file's source. Exported for reuse (e.g. re-checking a
75
- * changed file during dev).
76
- * @param source The route file's source code
77
- * @param file The file path, relative to the site root (used in messages)
78
- * @param expected The route path derived from the file's location
79
- * @param typeName The endpoint type name conventionally used by this route
80
- * type (used in suggestions)
81
- */
82
- export function checkRouteSource(
83
- source: string,
84
- file: string,
85
- expected: string,
86
- typeName: string,
87
- ): RouteCheckIssue[] {
88
- const issues: RouteCheckIssue[] = [];
89
- let sawAnnotation = false;
90
- USAGE_RE.lastIndex = 0;
91
- for (let m = USAGE_RE.exec(source); m; m = USAGE_RE.exec(source)) {
92
- sawAnnotation = true;
93
- const kind = m[1];
94
- const usedTypeName = m[2];
95
- const { routePath, end } = parseTypeArgs(source, m.index + m[0].length);
96
- const line = lineOf(source, m.index);
97
- if (routePath !== undefined && routePath !== expected) {
98
- issues.push({
99
- file,
100
- line,
101
- severity: "error",
102
- message: `Route annotation "${routePath}" doesn't match this file's route "${expected}" (derived from its location). Did the file move?`,
103
- });
104
- }
105
- if (kind === "as" || kind === ":") {
106
- issues.push({
107
- file,
108
- line,
109
- severity: "warning",
110
- message:
111
- kind === "as"
112
- ? `The \`as ${usedTypeName}\` cast widens the exported type, so PageData/PageForm/param inference falls back to loose types. Use \`satisfies ${usedTypeName}${routePathArg(expected, routePath)}\` without a cast instead.`
113
- : `The variable annotation widens the exported type, so PageData/PageForm/param inference falls back to loose types. Use \`satisfies ${usedTypeName}${routePathArg(expected, routePath)}\` on the default export instead.`,
114
- });
115
- } else {
116
- const cast = parseAsCast(source, end);
117
- if (cast) {
118
- issues.push({
119
- file,
120
- line,
121
- severity: "warning",
122
- message: `The \`as\` cast after \`satisfies\` widens the exported type, so PageData/PageForm/param inference falls back to loose types. Use \`satisfies ${usedTypeName}${routePathArg(expected, routePath ?? cast.routePath)}\` on its own.`,
123
- });
124
- if (cast.routePath !== undefined && cast.routePath !== expected) {
125
- issues.push({
126
- file,
127
- line,
128
- severity: "error",
129
- message: `Route annotation "${cast.routePath}" doesn't match this file's route "${expected}" (derived from its location). Did the file move?`,
130
- });
131
- }
132
- // Skip the `as T` so it isn't reported again as a separate cast
133
- USAGE_RE.lastIndex = cast.end;
134
- } else if (routePath === undefined && expected.includes("[")) {
135
- issues.push({
136
- file,
137
- line,
138
- severity: "warning",
139
- message: `No route path on the annotation, so params are loosely typed. Use \`satisfies ${usedTypeName}<"${expected}">\`.`,
140
- });
141
- }
142
- }
143
- }
144
- if (!sawAnnotation && file.endsWith(".ts") && expected.includes("[")) {
145
- issues.push({
146
- file,
147
- line: 1,
148
- severity: "warning",
149
- message: `No route annotation, so params are loosely typed. Add \`satisfies ${typeName}<"${expected}">\` to the default export.`,
150
- });
151
- }
152
- return issues;
153
- }
154
-
155
- /**
156
- * Prints issues to the console and returns the number of errors.
157
- */
158
- export function reportRouteIssues(issues: RouteCheckIssue[]): number {
159
- for (const issue of issues) {
160
- console.error(`[torpor] ${issue.severity}: ${issue.file}:${issue.line} ${issue.message}`);
161
- }
162
- return issues.filter((i) => i.severity === "error").length;
163
- }
164
-
165
- const MAKE_API_RE = /\bmakeApi\s*<\s*(["'])([^"']+)\1\s*,\s*typeof\s+([A-Za-z_$][\w$]*)/g;
166
-
167
- const DEFAULT_IMPORT_RE = /\bimport\s+(?:type\s+)?([A-Za-z_$][\w$]*)\s+from\s*(["'])([^"']+)\2/g;
168
-
169
- const SKIP_DIRS = new Set(["node_modules", "dist", ".git", ".torpor", ".vite", "coverage"]);
170
-
171
- const SOURCE_FILE_RE = /\.(ts|js|torp)$/;
172
-
173
- /**
174
- * Precomputed state for checking `makeApi` calls: the site root, its route
175
- * files keyed by path, and tsconfig path aliases for resolving imports.
176
- */
177
- export interface ApiCallCheck {
178
- root: string;
179
- routesByFile: Map<string, Route>;
180
- aliases: AliasEntry[];
181
- }
182
-
183
- /**
184
- * Creates the shared state used by `checkApiCallSource`, so per-file checks
185
- * (e.g. re-checking a changed file during dev) don't rebuild it.
186
- */
187
- export function createApiCallCheck(site: Site): ApiCallCheck {
188
- const routesByFile = new Map<string, Route>();
189
- for (const route of site.routes) {
190
- if (route.file) routesByFile.set(route.file.replaceAll("\\", "/"), route);
191
- }
192
- return { root: site.root, routesByFile, aliases: tsconfigAliases(site.root) };
193
- }
194
-
195
- /**
196
- * Checks `makeApi<Route, typeof endpoint>` calls across the site's source
197
- * files: the `Route` argument must match the route derived from the imported
198
- * endpoint file's location. Calls whose endpoint isn't a type-only import of
199
- * a known route file are skipped.
200
- */
201
- export function checkApiCalls(site: Site): RouteCheckIssue[] {
202
- const check = createApiCallCheck(site);
203
- const issues: RouteCheckIssue[] = [];
204
- for (const file of walkSourceFiles(site.root)) {
205
- let source: string;
206
- try {
207
- source = readFileSync(file, "utf8");
208
- } catch {
209
- continue;
210
- }
211
- issues.push(...checkApiCallSource(source, file, check));
212
- }
213
- return issues;
214
- }
215
-
216
- /**
217
- * Checks the `makeApi` calls in a single file's source.
218
- * @param source The file's source code
219
- * @param file The file's absolute path (used to resolve relative imports and
220
- * to derive the path shown in messages)
221
- * @param check Shared state from `createApiCallCheck`
222
- */
223
- export function checkApiCallSource(
224
- source: string,
225
- file: string,
226
- check: ApiCallCheck,
227
- ): RouteCheckIssue[] {
228
- const imports = new Map<string, string>();
229
- for (let m = DEFAULT_IMPORT_RE.exec(source); m; m = DEFAULT_IMPORT_RE.exec(source)) {
230
- imports.set(m[1], m[3]);
231
- }
232
- const issues: RouteCheckIssue[] = [];
233
- for (let m = MAKE_API_RE.exec(source); m; m = MAKE_API_RE.exec(source)) {
234
- const specifier = imports.get(m[3]);
235
- if (!specifier) continue;
236
- const resolved = resolveSpecifier(specifier, file, check);
237
- if (!resolved) continue;
238
- const relFile = path.relative(check.root, resolved).replaceAll("\\", "/");
239
- const route = check.routesByFile.get(relFile);
240
- if (!route) continue;
241
- const expected = expectedAnnotationPath(route);
242
- if (m[2] !== expected) {
243
- issues.push({
244
- file: path.relative(check.root, file).replaceAll("\\", "/"),
245
- line: lineOf(source, m.index),
246
- severity: "error",
247
- message: `makeApi route "${m[2]}" doesn't match the endpoint's route "${expected}" (derived from ${relFile}). Did the endpoint move?`,
248
- });
249
- }
250
- }
251
- return issues;
252
- }
253
-
254
- /**
255
- * Resolves an import specifier to an existing file: relative specifiers are
256
- * resolved against the importing file, others against tsconfig path aliases.
257
- * Bare module specifiers that match no alias return undefined.
258
- */
259
- export function resolveSpecifier(
260
- specifier: string,
261
- fromFile: string,
262
- check: ApiCallCheck,
263
- ): string | undefined {
264
- let candidate: string | undefined;
265
- if (specifier.startsWith("./") || specifier.startsWith("../")) {
266
- candidate = path.resolve(path.dirname(fromFile), specifier);
267
- } else {
268
- for (const alias of check.aliases) {
269
- if (typeof alias.find === "string") {
270
- if (specifier.startsWith(alias.find)) {
271
- candidate = path.resolve(alias.replacement, specifier.slice(alias.find.length));
272
- break;
273
- }
274
- } else if (alias.find.test(specifier)) {
275
- candidate = specifier.replace(alias.find, alias.replacement);
276
- break;
277
- }
278
- }
279
- }
280
- if (!candidate) return undefined;
281
- const probes = [
282
- candidate,
283
- `${candidate}.ts`,
284
- `${candidate}.js`,
285
- path.join(candidate, "index.ts"),
286
- path.join(candidate, "index.js"),
287
- ];
288
- for (const probe of probes) {
289
- if (existsSync(probe) && statSync(probe).isFile()) return probe;
290
- }
291
- return undefined;
292
- }
293
-
294
- function walkSourceFiles(root: string): string[] {
295
- const files: string[] = [];
296
- const visit = (dir: string): void => {
297
- let entries;
298
- try {
299
- entries = readdirSync(dir, { withFileTypes: true });
300
- } catch {
301
- return;
302
- }
303
- for (const entry of entries) {
304
- if (entry.isDirectory()) {
305
- if (!SKIP_DIRS.has(entry.name)) visit(path.join(dir, entry.name));
306
- } else if (
307
- entry.isFile() &&
308
- SOURCE_FILE_RE.test(entry.name) &&
309
- !entry.name.endsWith(".d.ts")
310
- ) {
311
- files.push(path.join(dir, entry.name));
312
- }
313
- }
314
- };
315
- visit(root);
316
- return files;
317
- }
318
-
319
- /**
320
- * The route path a file's annotation should use, derived from its manifest
321
- * entry: internal suffixes (`/~server`, `/_layout` etc) are stripped so e.g.
322
- * `posts/[id]/+page.server.ts` expects `"/posts/[id]"`.
323
- */
324
- export function expectedAnnotationPath(route: Route): string {
325
- let p = route.path;
326
- switch (route.type) {
327
- case PAGE_SERVER_ROUTE:
328
- p = p.replace(/\/~server$/, "");
329
- break;
330
- case LAYOUT_ROUTE:
331
- p = p.replace(/\/_layout$/, "");
332
- break;
333
- case LAYOUT_SERVER_ROUTE:
334
- p = p.replace(/\/_layout\/~server$/, "");
335
- break;
336
- case HOOK_ROUTE:
337
- p = p.replace(/\/_hook$/, "");
338
- break;
339
- case HOOK_SERVER_ROUTE:
340
- p = p.replace(/\/_hook\/~server$/, "");
341
- break;
342
- case ERROR_ROUTE:
343
- p = p.replace(/\/_error$/, "");
344
- break;
345
- }
346
- return p === "" ? "/" : p;
347
- }
348
-
349
- function typeNameFor(route: Route): string {
350
- switch (route.type) {
351
- case PAGE_SERVER_ROUTE:
352
- case LAYOUT_SERVER_ROUTE:
353
- return "PageServerEndPoint";
354
- case SERVER_ROUTE:
355
- return "ServerEndPoint";
356
- case HOOK_ROUTE:
357
- case HOOK_SERVER_ROUTE:
358
- return "ServerHook";
359
- default:
360
- return "PageEndPoint";
361
- }
362
- }
363
-
364
- /**
365
- * Parses the type arguments that follow an endpoint type name, returning the
366
- * first argument when it is a string literal (the route path), plus the index
367
- * after the closing `>` (or after the type name when there are no arguments).
368
- */
369
- function parseTypeArgs(
370
- source: string,
371
- start: number,
372
- ): { routePath: string | undefined; end: number } {
373
- let i = start;
374
- while (i < source.length && /\s/.test(source[i])) i++;
375
- if (source[i] !== "<") return { routePath: undefined, end: i };
376
- i++;
377
- let depth = 1;
378
- let routePath: string | undefined;
379
- let seenComma = false;
380
- while (i < source.length) {
381
- const c = source[i];
382
- if (c === '"' || c === "'") {
383
- const quote = c;
384
- let j = i + 1;
385
- while (j < source.length && source[j] !== quote) {
386
- if (source[j] === "\\") j++;
387
- j++;
388
- }
389
- if (routePath === undefined && !seenComma) routePath = source.slice(i + 1, j);
390
- i = j + 1;
391
- continue;
392
- }
393
- if (c === "<") depth++;
394
- else if (c === ">") {
395
- depth--;
396
- if (depth === 0) return { routePath, end: i + 1 };
397
- } else if (c === "," && routePath === undefined) {
398
- seenComma = true;
399
- }
400
- i++;
401
- }
402
- return { routePath, end: i };
403
- }
404
-
405
- /**
406
- * Parses an `as T`-style cast that follows a `satisfies T` usage, returning
407
- * its optional route path argument and the index after the cast.
408
- */
409
- function parseAsCast(
410
- source: string,
411
- start: number,
412
- ): { routePath: string | undefined; end: number } | undefined {
413
- let i = start;
414
- while (i < source.length && /\s/.test(source[i])) i++;
415
- if (!/^as\b/.test(source.slice(i, i + 3))) return undefined;
416
- i += 2;
417
- while (i < source.length && /\s/.test(source[i])) i++;
418
- if (!TYPE_NAME_RE.test(source.slice(i))) return undefined;
419
- i = i + source.slice(i).match(TYPE_NAME_RE)![0].length;
420
- return parseTypeArgs(source, i);
421
- }
422
-
423
- function routePathArg(expected: string, actual: string | undefined): string {
424
- // Keep the user's own (correct) path when widening a properly-annotated
425
- // export; otherwise suggest the path derived from the file's location
426
- return `<"${actual ?? expected}">`;
427
- }
428
-
429
- function lineOf(source: string, index: number): number {
430
- let line = 1;
431
- for (let i = 0; i < index; i++) {
432
- if (source[i] === "\n") line++;
433
- }
434
- return line;
435
- }
@@ -1,121 +0,0 @@
1
- import manifest from "@torpor/build/manifest";
2
- import loadData from "../nav/loadData.ts";
3
- import navigate from "../nav/navigate.ts";
4
- import $page from "../state/$page.ts";
5
- import client from "../state/client";
6
- import type LayoutPath from "../types/LayoutPath";
7
- import type PageEndPoint from "../types/PageEndPoint";
8
- import type PageServerEndPoint from "../types/PageServerEndPoint";
9
- import Router from "./Router.ts";
10
-
11
- // Build the router from the Site object created by the user
12
- const router = new Router();
13
- router.addPages(manifest.routes);
14
-
15
- // Put it in client state so that it can be accessed from navigate. We can't
16
- // just import it to navigate because that gets built by tsdown, without Vite
17
- // and the manifest
18
- client.router = router;
19
-
20
- // Intercept clicks on links
21
- window.addEventListener("click", async (e) => {
22
- const link = getLink(e.target);
23
- if (link) {
24
- if (ignoreAnchorMouseEvent(link, e)) {
25
- return;
26
- }
27
-
28
- e.preventDefault();
29
-
30
- const href = link.href;
31
- const url = new URL(href);
32
-
33
- if (await navigate(url)) {
34
- window.history.pushState(null, "", url);
35
- } else {
36
- window.location.href = href;
37
- }
38
- }
39
- });
40
-
41
- // Intercept mouseover/focus/touchstart on links for prefetching
42
- // TODO: settings e.g. <a href="" data-tb-prefetch="eager"/"visible"/"hover"/"touch">
43
- window.addEventListener("mouseover", maybePrefetch);
44
- window.addEventListener("touchstart", maybePrefetch);
45
-
46
- async function maybePrefetch(e: MouseEvent | TouchEvent) {
47
- const link = getLink(e.target);
48
- if (link) {
49
- if (ignoreAnchorMouseEvent(link, e)) {
50
- return;
51
- }
52
-
53
- const href = link.href;
54
- const url = new URL(href);
55
-
56
- const path = url.pathname;
57
- const query = url.searchParams;
58
- const route = router.match(path, query);
59
- if (route) {
60
- const handler = route.handler;
61
- const params = route.params || {};
62
-
63
- // There should be a client endpoint
64
- const clientEndPoint: PageEndPoint | undefined = (await handler.endPoint()).default;
65
-
66
- // There may be a server endpoint
67
- const serverEndPoint: PageServerEndPoint | undefined =
68
- handler.serverEndPoint && (await handler.serverEndPoint())?.default;
69
-
70
- // Just dummy this up
71
- let newStack: LayoutPath[] = [];
72
-
73
- await loadData(handler, params, path, query, newStack, clientEndPoint, serverEndPoint, true);
74
- }
75
- }
76
- }
77
-
78
- function getLink(target: EventTarget | null): HTMLAnchorElement | undefined {
79
- // The target may be a child element of the link (e.g. an icon inside it),
80
- // so walk up to the nearest actual link. Fragment-only links (href="#...")
81
- // are left to the browser's native jump-to-anchor behavior
82
- if (!(target instanceof Element)) return;
83
- const link = target.closest("a[href]");
84
- if (link?.getAttribute("href")?.startsWith("#")) return;
85
- return (link as HTMLAnchorElement) ?? undefined;
86
- }
87
-
88
- function ignoreAnchorMouseEvent(link: HTMLAnchorElement, e: MouseEvent | TouchEvent) {
89
- // Ignore the event if it was already handled (e.g. a user handler calling
90
- // preventDefault), a modifier key is pressed, the target is not "_self"
91
- // (e.g. "_blank"), rel="external", it's a download link, or it's in a
92
- // contenteditable region
93
- return (
94
- e.defaultPrevented ||
95
- e.altKey ||
96
- e.ctrlKey ||
97
- e.metaKey ||
98
- e.shiftKey ||
99
- (link.target && link.target !== "_self") ||
100
- link.rel === "external" ||
101
- link.hasAttribute("download") ||
102
- link.isContentEditable
103
- );
104
- }
105
-
106
- // Listen for changes to the URL that occur when the user navigates using the
107
- // back or forward buttons
108
- window.addEventListener("popstate", async () => {
109
- await navigateToLocation(document.location);
110
- });
111
-
112
- // Do the initial navigation and hydration
113
- await navigateToLocation(document.location, true);
114
-
115
- async function navigateToLocation(location: Location, firstTime = false) {
116
- // Reset $page on submit and link navigation
117
- // TODO: Should we put data in $page.data??
118
- $page.form = undefined;
119
-
120
- return await navigate(new URL(location.toString()), firstTime);
121
- }
@@ -1,3 +0,0 @@
1
- import { setDevMode } from "@torpor/view/dev";
2
-
3
- setDevMode();
@@ -1,11 +0,0 @@
1
- import type Adapter from "../types/Adapter";
2
-
3
- // TODO: This should be an adapter-auto package that conditionally loads other packages...
4
-
5
- const defaultAdapter: Adapter = {
6
- serve: () => {
7
- console.log("An adapter needs to be set in site.config");
8
- },
9
- };
10
-
11
- export default defaultAdapter;
@@ -1,53 +0,0 @@
1
- /**
2
- * Layouts are composed by the layout engine passing each layout (and finally
3
- * the page) as the default slot of its parent — so every `_layout` component
4
- * must render `<slot />`, or pass the default slot down to a child component
5
- * it renders. A layout that doesn't drops its entire content: the server
6
- * renders the layout's own markup with an empty content area (no error), and
7
- * the first client-side navigation that reuses the layout crashes on a null
8
- * slot region.
9
- *
10
- * The static `checkLayoutSlots` check catches this at build/dev time; these
11
- * helpers report it at request time for the cases static analysis can't see
12
- * (e.g. a `<slot />` rendered conditionally inside `@if`). They are
13
- * server-side only — nothing here is imported into the client bundle.
14
- */
15
-
16
- /**
17
- * Returns the path of the outermost layout that never rendered its
18
- * `<slot />`, or undefined when every level rendered.
19
- *
20
- * Slot level j (1-based) is the slot render that layout j - 1 receives: the
21
- * root layout (layouts[0]) renders level 1, layout i renders level i + 1,
22
- * and level layouts.length renders the page. A broken level makes every
23
- * deeper level unreachable, so the first level that never ran identifies
24
- * the offending layout.
25
- */
26
- export function findMissingSlotLayout(
27
- layouts: readonly { path: string }[],
28
- invokedLevels: ReadonlySet<number>,
29
- ): string | undefined {
30
- for (let level = 1; level <= layouts.length; level++) {
31
- if (!invokedLevels.has(level)) {
32
- return layouts[level - 1].path;
33
- }
34
- }
35
- return undefined;
36
- }
37
-
38
- const warnedLayouts = new Set<string>();
39
-
40
- /**
41
- * Reports a layout that dropped its slot content. The condition is a genuine
42
- * rendering bug (the page content is missing from the output), so it is not
43
- * dev-gated — but it warns only once per layout path to avoid per-request
44
- * noise.
45
- */ export function warnMissingSlotContent(layoutPath: string): void {
46
- if (warnedLayouts.has(layoutPath)) return;
47
- warnedLayouts.add(layoutPath);
48
- console.warn(
49
- `[torpor] The layout at "${layoutPath}" never rendered <slot />, so the page content ` +
50
- `was not included in the render. Every _layout component must render <slot /> (or ` +
51
- `pass the default slot down to a child component it renders).`,
52
- );
53
- }