@kasperrt/smolter 0.2.0 → 0.3.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.
Files changed (264) hide show
  1. package/{src/internal → .generated}/client/index.ts +1 -0
  2. package/.generated/core/environment.ts +43 -0
  3. package/{src/internal → .generated}/core/errors.ts +1 -1
  4. package/{src/internal → .generated}/core/index.ts +1 -0
  5. package/.generated/core/navigation-router.ts +107 -0
  6. package/.generated/core/navigation.ts +147 -0
  7. package/.generated/core/presentation.ts +108 -0
  8. package/.generated/core/resource-types.ts +44 -0
  9. package/.generated/core/resources.ts +444 -0
  10. package/.generated/core/responses.ts +86 -0
  11. package/{src/internal → .generated}/core/router.ts +136 -63
  12. package/{src/internal → .generated}/core/serialize.ts +7 -2
  13. package/.generated/core/store.ts +25 -0
  14. package/.generated/core/submission.ts +116 -0
  15. package/{src/internal → .generated}/core/types.ts +18 -1
  16. package/.generated/core/url.ts +29 -0
  17. package/.generated/core/utils.ts +5 -0
  18. package/.generated/preact/await.tsx +41 -0
  19. package/.generated/preact/client.tsx +93 -0
  20. package/.generated/preact/context.ts +59 -0
  21. package/.generated/preact/document.tsx +198 -0
  22. package/{src/internal → .generated}/preact/fetcher.ts +43 -23
  23. package/.generated/preact/form.ts +58 -0
  24. package/{src/internal → .generated}/preact/hooks.ts +25 -28
  25. package/.generated/preact/index.ts +34 -0
  26. package/.generated/preact/lazy-route.ts +100 -0
  27. package/.generated/preact/navigation-state.ts +9 -0
  28. package/.generated/preact/navigation.tsx +147 -0
  29. package/.generated/preact/root-context.ts +21 -0
  30. package/.generated/preact/root-data.ts +45 -0
  31. package/.generated/preact/route-hooks.tsx +170 -0
  32. package/.generated/preact/route-types.ts +76 -0
  33. package/.generated/preact/router.tsx +107 -0
  34. package/{src/internal → .generated}/preact/routes.ts +17 -9
  35. package/.generated/react/context.ts +59 -0
  36. package/.generated/react/data.ts +25 -0
  37. package/{src/internal → .generated}/react/fetcher.ts +13 -11
  38. package/.generated/react/form.ts +58 -0
  39. package/{src/internal → .generated}/react/hooks.ts +9 -14
  40. package/{src/internal/preact → .generated/react}/index.ts +5 -1
  41. package/{src/internal → .generated}/react/link.ts +13 -21
  42. package/.generated/react/resources/components.tsx +404 -0
  43. package/{src/internal → .generated}/react/routes.ts +17 -9
  44. package/.generated/server/application.tsx +185 -0
  45. package/.generated/server/assets.ts +19 -0
  46. package/.generated/server/handler.ts +69 -0
  47. package/.generated/server/http.ts +55 -0
  48. package/{src/internal → .generated}/server/index.ts +3 -3
  49. package/.generated/server/node.ts +4 -0
  50. package/.generated/server/preact.ts +7 -0
  51. package/.generated/vite/application/browser-boundary.ts +42 -0
  52. package/.generated/vite/application/lazy-files.ts +136 -0
  53. package/.generated/vite/application/vite.ts +144 -0
  54. package/{src/preact-vite.ts → .generated/vite/preact.ts} +9 -3
  55. package/{src/react-vite.ts → .generated/vite/react.ts} +3 -3
  56. package/CHANGELOG.md +54 -0
  57. package/README.md +227 -35
  58. package/dist/client/deserialize.js +10 -0
  59. package/dist/{internal/client → client}/index.d.ts +1 -0
  60. package/dist/client/index.js +46 -0
  61. package/dist/core/environment.d.ts +12 -0
  62. package/dist/core/environment.js +23 -0
  63. package/dist/core/errors.js +7 -0
  64. package/dist/{internal/core → core}/index.d.ts +1 -0
  65. package/dist/core/match.js +33 -0
  66. package/dist/core/navigation-router.d.ts +15 -0
  67. package/dist/core/navigation-router.js +64 -0
  68. package/dist/core/navigation.d.ts +15 -0
  69. package/dist/core/navigation.js +92 -0
  70. package/dist/core/presentation.d.ts +5 -0
  71. package/dist/core/presentation.js +69 -0
  72. package/dist/core/resource-types.d.ts +37 -0
  73. package/dist/core/resources.d.ts +19 -0
  74. package/dist/core/resources.js +345 -0
  75. package/dist/core/responses.d.ts +15 -0
  76. package/dist/core/responses.js +48 -0
  77. package/dist/{internal/core → core}/router.d.ts +1 -1
  78. package/dist/core/router.js +204 -0
  79. package/dist/{internal/core → core}/serialize.d.ts +2 -0
  80. package/dist/core/serialize.js +17 -0
  81. package/dist/core/store.d.ts +6 -0
  82. package/dist/core/store.js +21 -0
  83. package/dist/core/submission.d.ts +30 -0
  84. package/dist/core/submission.js +41 -0
  85. package/dist/{internal/core → core}/types.d.ts +16 -1
  86. package/dist/core/url.d.ts +7 -0
  87. package/dist/core/url.js +12 -0
  88. package/dist/core/utils.d.ts +5 -0
  89. package/dist/core/utils.js +5 -0
  90. package/dist/files/index.js +64 -0
  91. package/dist/preact/await.d.ts +7 -0
  92. package/dist/preact/await.js +19 -0
  93. package/dist/preact/client.d.ts +7 -0
  94. package/dist/preact/client.js +67 -0
  95. package/dist/preact/context.d.ts +18 -0
  96. package/dist/preact/context.js +23 -0
  97. package/dist/preact/document.d.ts +15 -0
  98. package/dist/preact/document.js +155 -0
  99. package/dist/{internal/preact → preact}/fetcher.d.ts +9 -5
  100. package/dist/preact/fetcher.js +45 -0
  101. package/dist/{internal/preact → preact}/form.d.ts +5 -6
  102. package/dist/preact/form.js +21 -0
  103. package/dist/{internal/preact → preact}/hooks.d.ts +7 -6
  104. package/dist/preact/hooks.js +25 -0
  105. package/dist/preact/index.d.ts +18 -0
  106. package/dist/preact/index.js +15 -0
  107. package/dist/preact/lazy-route.d.ts +19 -0
  108. package/dist/preact/lazy-route.js +51 -0
  109. package/dist/preact/navigation-state.d.ts +2 -0
  110. package/dist/preact/navigation-state.js +9 -0
  111. package/dist/preact/navigation.d.ts +28 -0
  112. package/dist/preact/navigation.js +60 -0
  113. package/dist/preact/root-context.d.ts +13 -0
  114. package/dist/preact/root-context.js +9 -0
  115. package/dist/preact/root-data.d.ts +8 -0
  116. package/dist/preact/root-data.js +28 -0
  117. package/dist/preact/route-hooks.d.ts +32 -0
  118. package/dist/preact/route-hooks.js +94 -0
  119. package/dist/preact/route-types.d.ts +54 -0
  120. package/dist/preact/router.d.ts +16 -0
  121. package/dist/preact/router.js +63 -0
  122. package/dist/{internal/preact → preact}/routes.d.ts +1 -1
  123. package/dist/preact/routes.js +12 -0
  124. package/dist/{internal/preact → preact}/runtime.js +1 -1
  125. package/dist/react/context.d.ts +18 -0
  126. package/dist/react/context.js +23 -0
  127. package/dist/react/data.d.ts +5 -0
  128. package/dist/react/data.js +3 -0
  129. package/dist/{internal/react → react}/fetcher.d.ts +2 -2
  130. package/dist/react/fetcher.js +34 -0
  131. package/dist/{internal/react → react}/form.d.ts +5 -6
  132. package/dist/react/form.js +21 -0
  133. package/dist/react/hooks.js +25 -0
  134. package/dist/{internal/react → react}/index.d.ts +1 -1
  135. package/dist/react/index.js +10 -0
  136. package/dist/{internal/react → react}/link.d.ts +2 -2
  137. package/dist/react/link.js +27 -0
  138. package/dist/react/resources/components.d.ts +64 -0
  139. package/dist/react/resources/components.js +239 -0
  140. package/dist/{internal/react → react}/routes.d.ts +1 -1
  141. package/dist/react/routes.js +12 -0
  142. package/dist/react/runtime.js +1 -0
  143. package/dist/server/application.d.ts +4 -0
  144. package/dist/server/application.js +119 -0
  145. package/dist/server/assets.d.ts +5 -0
  146. package/dist/server/assets.js +7 -0
  147. package/dist/server/handler.d.ts +19 -0
  148. package/dist/server/handler.js +22 -0
  149. package/dist/server/http.d.ts +4 -0
  150. package/dist/server/http.js +27 -0
  151. package/dist/server/index.js +76 -0
  152. package/dist/server/node.d.ts +3 -0
  153. package/dist/server/node.js +3 -0
  154. package/dist/server/preact.d.ts +3 -0
  155. package/dist/server/preact.js +4 -0
  156. package/dist/vite/application/browser-boundary.d.ts +3 -0
  157. package/dist/vite/application/browser-boundary.js +35 -0
  158. package/dist/vite/application/lazy-files.d.ts +2 -0
  159. package/dist/vite/application/lazy-files.js +87 -0
  160. package/dist/vite/application/vite.d.ts +9 -0
  161. package/dist/vite/application/vite.js +101 -0
  162. package/dist/vite/index.js +104 -0
  163. package/dist/{preact-vite.d.ts → vite/preact.d.ts} +5 -3
  164. package/dist/vite/preact.js +15 -0
  165. package/dist/{react-vite.d.ts → vite/react.d.ts} +3 -3
  166. package/dist/vite/react.js +14 -0
  167. package/dist/vite/transport.js +35 -0
  168. package/jsr.json +28 -16
  169. package/package.json +116 -36
  170. package/dist/client.d.ts +0 -2
  171. package/dist/client.js +0 -2
  172. package/dist/core.d.ts +0 -2
  173. package/dist/core.js +0 -2
  174. package/dist/files.d.ts +0 -2
  175. package/dist/files.js +0 -2
  176. package/dist/internal/client/deserialize.js +0 -11
  177. package/dist/internal/client/index.js +0 -44
  178. package/dist/internal/core/errors.js +0 -6
  179. package/dist/internal/core/index.js +0 -7
  180. package/dist/internal/core/match.js +0 -27
  181. package/dist/internal/core/router.js +0 -189
  182. package/dist/internal/core/serialize.js +0 -13
  183. package/dist/internal/core/types.js +0 -5
  184. package/dist/internal/core/url.d.ts +0 -2
  185. package/dist/internal/core/url.js +0 -6
  186. package/dist/internal/files/index.js +0 -77
  187. package/dist/internal/preact/context.d.ts +0 -18
  188. package/dist/internal/preact/context.js +0 -20
  189. package/dist/internal/preact/fetcher.js +0 -28
  190. package/dist/internal/preact/form.js +0 -41
  191. package/dist/internal/preact/hooks.js +0 -35
  192. package/dist/internal/preact/index.d.ts +0 -9
  193. package/dist/internal/preact/index.js +0 -7
  194. package/dist/internal/preact/link.d.ts +0 -12
  195. package/dist/internal/preact/link.js +0 -28
  196. package/dist/internal/preact/routes.js +0 -22
  197. package/dist/internal/react/context.d.ts +0 -18
  198. package/dist/internal/react/context.js +0 -20
  199. package/dist/internal/react/fetcher.js +0 -28
  200. package/dist/internal/react/form.js +0 -41
  201. package/dist/internal/react/hooks.js +0 -35
  202. package/dist/internal/react/index.js +0 -7
  203. package/dist/internal/react/link.js +0 -28
  204. package/dist/internal/react/routes.js +0 -22
  205. package/dist/internal/react/runtime.js +0 -2
  206. package/dist/internal/server/index.js +0 -69
  207. package/dist/internal/vite/index.js +0 -132
  208. package/dist/internal/vite/transport.js +0 -42
  209. package/dist/preact-client.d.ts +0 -2
  210. package/dist/preact-client.js +0 -2
  211. package/dist/preact-server.d.ts +0 -2
  212. package/dist/preact-server.js +0 -2
  213. package/dist/preact-vite.js +0 -9
  214. package/dist/preact.d.ts +0 -2
  215. package/dist/preact.js +0 -2
  216. package/dist/react-client.d.ts +0 -2
  217. package/dist/react-client.js +0 -2
  218. package/dist/react-server.d.ts +0 -2
  219. package/dist/react-server.js +0 -2
  220. package/dist/react-vite.js +0 -9
  221. package/dist/react.d.ts +0 -2
  222. package/dist/react.js +0 -2
  223. package/dist/server.d.ts +0 -2
  224. package/dist/server.js +0 -2
  225. package/dist/utils.d.ts +0 -5
  226. package/dist/utils.js +0 -5
  227. package/dist/vite.d.ts +0 -2
  228. package/dist/vite.js +0 -2
  229. package/src/client.ts +0 -2
  230. package/src/core.ts +0 -2
  231. package/src/files.ts +0 -2
  232. package/src/internal/core/url.ts +0 -6
  233. package/src/internal/preact/context.ts +0 -46
  234. package/src/internal/preact/form.ts +0 -79
  235. package/src/internal/preact/link.ts +0 -46
  236. package/src/internal/react/context.ts +0 -46
  237. package/src/internal/react/form.ts +0 -79
  238. package/src/internal/react/index.ts +0 -13
  239. package/src/preact-client.ts +0 -2
  240. package/src/preact-server.ts +0 -2
  241. package/src/preact.ts +0 -2
  242. package/src/react-client.ts +0 -2
  243. package/src/react-server.ts +0 -2
  244. package/src/react.ts +0 -2
  245. package/src/server.ts +0 -2
  246. package/src/utils.ts +0 -5
  247. package/src/vite.ts +0 -2
  248. /package/{src/internal → .generated}/client/deserialize.ts +0 -0
  249. /package/{src/internal → .generated}/core/match.ts +0 -0
  250. /package/{src/internal → .generated}/files/index.ts +0 -0
  251. /package/{src/internal → .generated}/preact/runtime.ts +0 -0
  252. /package/{src/internal → .generated}/react/runtime.ts +0 -0
  253. /package/{src/internal → .generated}/vite/index.ts +0 -0
  254. /package/{src/internal → .generated}/vite/transport.ts +0 -0
  255. /package/dist/{internal/client → client}/deserialize.d.ts +0 -0
  256. /package/dist/{internal/core → core}/errors.d.ts +0 -0
  257. /package/dist/{internal/core → core}/match.d.ts +0 -0
  258. /package/dist/{internal/files → files}/index.d.ts +0 -0
  259. /package/dist/{internal/preact → preact}/runtime.d.ts +0 -0
  260. /package/dist/{internal/react → react}/hooks.d.ts +0 -0
  261. /package/dist/{internal/react → react}/runtime.d.ts +0 -0
  262. /package/dist/{internal/server → server}/index.d.ts +0 -0
  263. /package/dist/{internal/vite → vite}/index.d.ts +0 -0
  264. /package/dist/{internal/vite → vite}/transport.d.ts +0 -0
package/dist/core.d.ts DELETED
@@ -1,2 +0,0 @@
1
- /** Core entry point. @module */
2
- export * from "./internal/core/index.ts";
package/dist/core.js DELETED
@@ -1,2 +0,0 @@
1
- /** Core entry point. @module */
2
- export * from "./internal/core/index.js";
package/dist/files.d.ts DELETED
@@ -1,2 +0,0 @@
1
- /** Files entry point. @module */
2
- export * from "./internal/files/index.ts";
package/dist/files.js DELETED
@@ -1,2 +0,0 @@
1
- /** Files entry point. @module */
2
- export * from "./internal/files/index.js";
@@ -1,11 +0,0 @@
1
- /** Restore a trusted snapshot produced by @kasperrt/smolter/server serialize, including its route error. */
2
- export function deserialize(json) {
3
- const snapshot = JSON.parse(json);
4
- const error = snapshot.error;
5
- if (error) {
6
- snapshot.error = Object.assign(new Error(error.message), {
7
- name: error.name,
8
- });
9
- }
10
- return snapshot;
11
- }
@@ -1,44 +0,0 @@
1
- /**
2
- * Optional browser transport for server loaders and actions.
3
- * @module
4
- */
5
- export { deserialize } from "./deserialize.js";
6
- /** Browser transport for server routes. File-router builds generate this call. */
7
- export function serverRoute(path, module, handlers = { loader: true, action: true }) {
8
- const remote = async ({ request, signal }) => {
9
- const url = new URL(request.url);
10
- const response = await fetch(`/_smolter?url=${encodeURIComponent(url.pathname + url.search)}`, {
11
- method: request.method,
12
- signal,
13
- ...(request.method !== "GET" && { body: await request.formData() }),
14
- });
15
- const location = response.headers.get("X-Smolter-Redirect");
16
- if (location)
17
- return new Response(null, {
18
- status: 303,
19
- headers: { Location: location },
20
- });
21
- const data = await response.json();
22
- if (!response.ok)
23
- return new Error(data.error ?? "Route request failed");
24
- return data;
25
- };
26
- return {
27
- ...module,
28
- path,
29
- ...(handlers.loader && { loader: remote }),
30
- ...(handlers.action && { action: remote }),
31
- ...(module.clientLoader && {
32
- clientLoader: (args) => module.clientLoader({ ...args, serverLoader: () => remote(args) }),
33
- }),
34
- ...(module.clientAction && {
35
- clientAction: (args) => {
36
- const request = args.request.clone();
37
- return module.clientAction({
38
- ...args,
39
- serverAction: () => remote({ ...args, request: request.clone() }),
40
- });
41
- },
42
- }),
43
- };
44
- }
@@ -1,6 +0,0 @@
1
- /** Resolve returned or rejected errors into Error values without losing their cause. */
2
- export function safeWrapAsync(work) {
3
- return Promise.resolve()
4
- .then(work)
5
- .then((data) => (data instanceof Error ? { error: data } : { data }), (cause) => ({ error: new Error(String(cause), { cause }) }));
6
- }
@@ -1,7 +0,0 @@
1
- /** Framework-independent data routing. @module */
2
- export * from "./errors.js";
3
- export * from "./match.js";
4
- export * from "./router.js";
5
- export * from "./serialize.js";
6
- export * from "./types.js";
7
- export * from "./url.js";
@@ -1,27 +0,0 @@
1
- /** Associate a URL pattern with a module while retaining its inferred handler types. */
2
- export function defineRoute(path, module) {
3
- return { ...module, path };
4
- }
5
- /** Static segments, :params and a terminal * wildcard; first match wins. */
6
- export function matchRoutes(routes, pathname) {
7
- const segments = pathname.replace(/\/$/, "").split("/");
8
- for (const route of routes) {
9
- const parts = route.path.replace(/\/$/, "").split("/");
10
- const params = {};
11
- const matched = (parts.length === segments.length || parts.at(-1) === "*") &&
12
- parts.every((part, index) => {
13
- const value = segments[index];
14
- if (part === "*") {
15
- params["*"] = segments.slice(index).join("/");
16
- return true;
17
- }
18
- if (part[0] === ":" && value) {
19
- params[part.slice(1)] = new URLSearchParams(`=${value.replace(/[+&]/g, encodeURIComponent)}`).get("");
20
- return true;
21
- }
22
- return part === value;
23
- });
24
- if (matched)
25
- return { route, params };
26
- }
27
- }
@@ -1,189 +0,0 @@
1
- import { safeWrapAsync } from "./errors.js";
2
- import { matchRoutes } from "./match.js";
3
- import { internalURL } from "./url.js";
4
- /** Create an isolated router. Use browser: true for browser history, or loadRouter for SSR. */
5
- export function createRouter(routes, options = {}) {
6
- const browser = options.browser && typeof window !== "undefined";
7
- let initialized = options.hydrationData?.state === "idle";
8
- const initial = {
9
- loaded: initialized,
10
- ...(options.hydrationData ?? {
11
- url: new URL(options.url ?? (browser ? location.href : "/"), "http://localhost").href,
12
- params: {},
13
- state: "idle",
14
- }),
15
- };
16
- const disposers = new Set();
17
- const channel = (initial, local = false) => {
18
- let snapshot = initial;
19
- let controller;
20
- const listeners = new Set();
21
- const dispose = () => {
22
- disposers.delete(dispose);
23
- controller?.abort();
24
- listeners.clear();
25
- };
26
- const emit = (next) => {
27
- snapshot = { ...snapshot, ...next };
28
- for (const listener of listeners)
29
- listener();
30
- };
31
- const run = async (href, body, operation = {},
32
- // true pushes history; false handles popstate; undefined is a data request.
33
- navigation) => {
34
- controller?.abort();
35
- controller = new AbortController();
36
- const { signal } = controller;
37
- disposers.add(dispose);
38
- const previous = snapshot;
39
- const url = internalURL(main.getSnapshot().url, href);
40
- const match = url && matchRoutes(routes, url.pathname);
41
- const commit = () => {
42
- if (local || !url)
43
- return;
44
- const changed = snapshot.url !== url.href;
45
- if (!changed && snapshot.path !== undefined)
46
- return;
47
- if (browser && (navigation || body) && changed)
48
- history[operation.replace ? "replaceState" : "pushState"](null, "", url);
49
- const sameRoute = snapshot.path === match?.route.path;
50
- snapshot = {
51
- ...snapshot,
52
- url: url.href,
53
- path: match?.route.path,
54
- params: match?.params ?? {},
55
- actionData: undefined,
56
- data: sameRoute ? snapshot.data : undefined,
57
- loaded: sameRoute && snapshot.loaded,
58
- };
59
- };
60
- if (!body)
61
- commit();
62
- emit({ state: body ? "submitting" : "loading", error: undefined });
63
- const result = await safeWrapAsync(() => {
64
- if (!match)
65
- return new Error(url ? "No route" : "External URL");
66
- if (navigation !== undefined &&
67
- !body &&
68
- previous.loaded &&
69
- !previous.error &&
70
- previous.path === match.route.path &&
71
- match.route.shouldRevalidate?.({
72
- url,
73
- currentUrl: new URL(previous.url),
74
- nextUrl: url,
75
- defaultShouldRevalidate: true,
76
- reason: "navigation",
77
- }) === false)
78
- return previous.data;
79
- const fn = match.route[body ? "clientAction" : "clientLoader"] ||
80
- match.route[body ? "action" : "loader"];
81
- if (!fn)
82
- return body && new Error("No action");
83
- return fn({
84
- request: new Request(url, {
85
- signal,
86
- method: body && (operation.method ?? "POST"),
87
- body,
88
- }),
89
- params: match.params,
90
- signal,
91
- });
92
- });
93
- if (signal.aborted)
94
- return result;
95
- const redirectTo = result.data instanceof Response && result.data.headers.get("Location");
96
- if (redirectTo && (operation.hops ?? 0) < 10) {
97
- await main.load(new URL(redirectTo, url).href, undefined, {
98
- replace: true,
99
- hops: (operation.hops ?? 0) + 1,
100
- }, true);
101
- if (!signal.aborted)
102
- emit({ state: "idle" });
103
- return result;
104
- }
105
- if (redirectTo)
106
- result.error = new Error("Redirect loop");
107
- if (body && !result.error)
108
- commit();
109
- emit({
110
- [body && !local ? "actionData" : "data"]: result.data,
111
- error: result.error,
112
- loaded: body ? snapshot.loaded : !result.error,
113
- state: body && !result.error ? "loading" : "idle",
114
- });
115
- if (body && !result.error) {
116
- await router.revalidate("action", {
117
- formMethod: operation.method ?? "POST",
118
- formAction: url.href,
119
- formData: body,
120
- actionResult: result.data,
121
- });
122
- if (!signal.aborted)
123
- emit({ state: "idle" });
124
- }
125
- return result;
126
- };
127
- return {
128
- getSnapshot: () => snapshot,
129
- subscribe(listener) {
130
- listeners.add(listener);
131
- return () => {
132
- listeners.delete(listener);
133
- };
134
- },
135
- load: run,
136
- submit: run,
137
- dispose,
138
- };
139
- };
140
- const main = channel(initial);
141
- let started = 0;
142
- const pop = () => {
143
- void main.load(location.href, undefined, undefined, false);
144
- };
145
- const router = {
146
- ...main,
147
- routes,
148
- start() {
149
- if (!started++ && browser)
150
- addEventListener("popstate", pop);
151
- if (!initialized) {
152
- initialized = true;
153
- void main.load(initial.url);
154
- }
155
- return () => {
156
- if (!--started && browser)
157
- removeEventListener("popstate", pop);
158
- };
159
- },
160
- async navigate(url, operation) {
161
- initialized = true;
162
- await main.load(url, undefined, operation, true);
163
- },
164
- async revalidate(reason = "manual", context = {}) {
165
- const url = new URL(main.getSnapshot().url);
166
- if (matchRoutes(routes, url.pathname)?.route.shouldRevalidate?.({
167
- ...context,
168
- url,
169
- currentUrl: url,
170
- nextUrl: url,
171
- defaultShouldRevalidate: true,
172
- reason,
173
- }) === false)
174
- return;
175
- await main.load(url.href);
176
- },
177
- createFetcher() {
178
- return channel({ state: "idle" }, true);
179
- },
180
- dispose() {
181
- main.dispose();
182
- for (const dispose of disposers)
183
- dispose();
184
- if (browser)
185
- removeEventListener("popstate", pop);
186
- },
187
- };
188
- return router;
189
- }
@@ -1,13 +0,0 @@
1
- /** Safe inside a script element. Data must be JSON serializable; error stacks and causes are omitted. */
2
- export function serialize(snapshot) {
3
- return JSON.stringify({
4
- ...snapshot,
5
- error: snapshot.error && {
6
- name: snapshot.error.name,
7
- message: snapshot.error.message,
8
- },
9
- })
10
- .replace(/</g, "\\u003c")
11
- .replace(/\u2028/g, "\\u2028")
12
- .replace(/\u2029/g, "\\u2029");
13
- }
@@ -1,5 +0,0 @@
1
- /**
2
- * Framework-independent route matching, data requests, and state.
3
- * @module
4
- */
5
- export {};
@@ -1,2 +0,0 @@
1
- /** Resolve an internal URL; external links remain the browser's responsibility. */
2
- export declare function internalURL(base: string, href?: string): URL | undefined;
@@ -1,6 +0,0 @@
1
- /** Resolve an internal URL; external links remain the browser's responsibility. */
2
- export function internalURL(base, href = base) {
3
- const url = new URL(href, base);
4
- if (url.origin === new URL(base).origin)
5
- return url;
6
- }
@@ -1,77 +0,0 @@
1
- /**
2
- * Build-time discovery of flat file routes.
3
- * @module
4
- */
5
- import { readdir } from "node:fs/promises";
6
- import { resolve } from "node:path";
7
- import { safeWrapAsync } from "../core/index.js";
8
- const routeFile = /^route\.[jt]sx?$/;
9
- const flatFile = /\.[jt]sx?$/;
10
- /** Read only flat files and immediate route folders. Colocated helpers are ignored. */
11
- export async function flatRoutes(directory) {
12
- const result = await safeWrapAsync(async () => {
13
- const entries = await readdir(directory, { withFileTypes: true });
14
- const routes = [];
15
- const patterns = new Set();
16
- for (const entry of entries) {
17
- let name = entry.name;
18
- let file = resolve(directory, name);
19
- if (entry.isDirectory()) {
20
- const candidates = (await readdir(file)).filter((name) => routeFile.test(name));
21
- if (candidates.length > 1)
22
- return new Error(`Multiple route files in ${file}`);
23
- if (!candidates.length)
24
- continue;
25
- file = resolve(file, candidates[0]);
26
- }
27
- else {
28
- if (!entry.isFile() || !flatFile.test(name) || name.endsWith(".d.ts"))
29
- continue;
30
- name = name.replace(flatFile, "");
31
- }
32
- if (/[()[\]:*?]/.test(name))
33
- return new Error(`Unsupported flat route name: ${name}`);
34
- const segments = name.split(".");
35
- const parts = [];
36
- for (const [index, segment] of segments.entries()) {
37
- if (!segment)
38
- return new Error(`Empty route segment: ${name}`);
39
- if (segment === "_index" && index !== segments.length - 1)
40
- return new Error(`_index must be last: ${name}`);
41
- if (segment === "$" && index !== segments.length - 1)
42
- return new Error(`Splat must be last: ${name}`);
43
- if (segment.startsWith("_"))
44
- continue;
45
- const part = segment.replace(/_$/, "");
46
- parts.push(part === "$" ? "*" : part.replace(/^\$/, ":"));
47
- }
48
- // Pathless grouping modules are not nested layouts in a flat router.
49
- if (segments.at(-1)?.startsWith("_") && segments.at(-1) !== "_index")
50
- continue;
51
- const path = `/${parts.join("/")}`;
52
- const pattern = path.replace(/:[^/]+/g, ":");
53
- if (patterns.has(pattern))
54
- return new Error(`Conflicting flat route: ${path}`);
55
- patterns.add(pattern);
56
- routes.push({ path, file });
57
- }
58
- return routes.sort((a, b) => {
59
- const left = a.path.split("/");
60
- const right = b.path.split("/");
61
- const rank = (part) => part === "*"
62
- ? 0
63
- : part?.startsWith(":")
64
- ? 1
65
- : part === undefined
66
- ? 2
67
- : 3;
68
- for (let index = 0; index < Math.max(left.length, right.length); index++) {
69
- const difference = rank(right[index]) - rank(left[index]);
70
- if (difference)
71
- return difference;
72
- }
73
- return a.path.localeCompare(b.path);
74
- });
75
- });
76
- return result.error ?? result.data;
77
- }
@@ -1,18 +0,0 @@
1
- /**
2
- * Preact components and typed data hooks. Import from @kasperrt/smolter/preact.
3
- * @module
4
- */
5
- import type { Router, Snapshot } from "../core/index.ts";
6
- import type { ReactNode } from "./runtime.ts";
7
- /** Router instance and children rendered within its context. */
8
- export interface RouterProviderProps {
9
- router: Router;
10
- children?: ReactNode;
11
- }
12
- /** Provide a router to descendant hooks and connect its browser lifecycle. */
13
- export declare function RouterProvider({ router, children, }: RouterProviderProps): ReactNode;
14
- /** Hooks require a RouterProvider. */
15
- export declare function useRouter(): Router;
16
- type SnapshotSelector<T> = (snapshot: Snapshot) => T;
17
- export declare function useSnapshot<T>(select: SnapshotSelector<T>): T;
18
- export {};
@@ -1,20 +0,0 @@
1
- /**
2
- * Preact components and typed data hooks. Import from @kasperrt/smolter/preact.
3
- * @module
4
- */
5
- import { createContext, createElement, useContext, useEffect, useSyncExternalStore, } from "./runtime.js";
6
- const RouterContext = createContext(undefined);
7
- /** Provide a router to descendant hooks and connect its browser lifecycle. */
8
- export function RouterProvider({ router, children, }) {
9
- useEffect(router.start, [router]);
10
- return createElement(RouterContext.Provider, { value: router }, children);
11
- }
12
- /** Hooks require a RouterProvider. */
13
- export function useRouter() {
14
- return useContext(RouterContext);
15
- }
16
- export function useSnapshot(select) {
17
- const router = useRouter();
18
- const getSnapshot = () => select(router.getSnapshot());
19
- return useSyncExternalStore(router.subscribe, getSnapshot, getSnapshot);
20
- }
@@ -1,28 +0,0 @@
1
- /**
2
- * Preact components and typed data hooks. Import from @kasperrt/smolter/preact.
3
- * @module
4
- */
5
- import { useRouter } from "./context.js";
6
- import { Form } from "./form.js";
7
- import { createElement, useEffect, useMemo, useSyncExternalStore, } from "./runtime.js";
8
- /** Create a fetcher for independent route reads and mutations; its data stays local. */
9
- export function useFetcher(route) {
10
- const router = useRouter();
11
- const path = route?.path;
12
- const fetcher = useMemo(() => {
13
- const store = router.createFetcher();
14
- const target = () => {
15
- const snapshot = router.getSnapshot();
16
- return path === undefined || path === snapshot.path ? snapshot.url : path;
17
- };
18
- return {
19
- ...store,
20
- Form: (props) => createElement(Form, { action: target(), ...props, fetcher: store }),
21
- load: (url) => store.load(url ?? target()),
22
- submit: (body, options) => store.submit(options?.action ?? target(), body, options),
23
- };
24
- }, [router, path]);
25
- useEffect(() => fetcher.dispose, [fetcher]);
26
- const state = useSyncExternalStore(fetcher.subscribe, fetcher.getSnapshot, fetcher.getSnapshot);
27
- return { ...fetcher, ...state };
28
- }
@@ -1,41 +0,0 @@
1
- /**
2
- * Preact components and typed data hooks. Import from @kasperrt/smolter/preact.
3
- * @module
4
- */
5
- import { internalURL } from "../core/index.js";
6
- import { useRouter } from "./context.js";
7
- import { createElement } from "./runtime.js";
8
- /** Submit form data to a registered route without a document reload. */
9
- export function Form({ action, method = "post", fetcher, onSubmit, ...props }) {
10
- const router = useRouter();
11
- const submit = (event) => {
12
- onSubmit?.(event);
13
- if (event.defaultPrevented)
14
- return;
15
- const submitter = event.submitter;
16
- const target = internalURL(router.getSnapshot().url, submitter?.getAttribute("formaction") ?? action);
17
- if (!target)
18
- return;
19
- event.preventDefault();
20
- const data = new FormData(event.currentTarget, submitter);
21
- const verb = (submitter?.getAttribute("formmethod") ?? method).toUpperCase();
22
- if (verb === "GET") {
23
- const search = new URLSearchParams();
24
- for (const [key, value] of data)
25
- search.append(key, typeof value === "string" ? value : value.name);
26
- target.search = String(search);
27
- void (fetcher ? fetcher.load : router.navigate)(target.href);
28
- return;
29
- }
30
- const options = {
31
- method: verb,
32
- };
33
- void (fetcher ?? router).submit(target.href, data, options);
34
- };
35
- return createElement("form", {
36
- ...props,
37
- action,
38
- method: method === "get" ? "get" : "post",
39
- onSubmit: submit,
40
- });
41
- }
@@ -1,35 +0,0 @@
1
- /**
2
- * Preact components and typed data hooks. Import from @kasperrt/smolter/preact.
3
- * @module
4
- */
5
- import { useRouter, useSnapshot } from "./context.js";
6
- function useData(key, route) {
7
- const path = route?.path;
8
- return useSnapshot((snapshot) => {
9
- if (path !== undefined && path !== snapshot.path)
10
- return undefined;
11
- return snapshot[key];
12
- });
13
- }
14
- /** Pass a route for inference, or use useLoaderData<typeof loader>() in its module. */
15
- export function useLoaderData(route) {
16
- return useData("data", route);
17
- }
18
- /** Read the last page action result, optionally inferring its type from a route. */
19
- export function useActionData(route) {
20
- return useData("actionData", route);
21
- }
22
- /** Read path parameters for the active route. */
23
- export function useParams() {
24
- return useSnapshot((snapshot) => snapshot.params);
25
- }
26
- /** Read the current path, search, and hash together with the navigate function. */
27
- export function useLocation() {
28
- const router = useRouter();
29
- const url = useSnapshot((snapshot) => snapshot.url);
30
- return [url.replace(/^\w+:\/\/[^/]+/, ""), router.navigate];
31
- }
32
- /** Get the function that revalidates the current loader subject to shouldRevalidate. */
33
- export function useRevalidator() {
34
- return useRouter().revalidate;
35
- }
@@ -1,9 +0,0 @@
1
- /** Framework components and typed data hooks. @module */
2
- export type * from "../core/index.ts";
3
- export { createRouter, defineRoute } from "../core/index.ts";
4
- export { RouterProvider, type RouterProviderProps, useRouter, } from "./context.ts";
5
- export * from "./fetcher.ts";
6
- export * from "./form.ts";
7
- export * from "./hooks.ts";
8
- export * from "./link.ts";
9
- export * from "./routes.ts";
@@ -1,7 +0,0 @@
1
- export { createRouter, defineRoute } from "../core/index.js";
2
- export { RouterProvider, useRouter, } from "./context.js";
3
- export * from "./fetcher.js";
4
- export * from "./form.js";
5
- export * from "./hooks.js";
6
- export * from "./link.js";
7
- export * from "./routes.js";
@@ -1,12 +0,0 @@
1
- /**
2
- * Preact components and typed data hooks. Import from @kasperrt/smolter/preact.
3
- * @module
4
- */
5
- import type { AnchorHTMLAttributes, ReactNode } from "./runtime.ts";
6
- /** Anchor attributes with internal navigation and optional history replacement. */
7
- export interface LinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
8
- href: string;
9
- replace?: boolean;
10
- }
11
- /** Render an anchor that handles internal navigation and preserves native modified clicks. */
12
- export declare function Link({ href, replace, onClick, ...props }: LinkProps): ReactNode;
@@ -1,28 +0,0 @@
1
- /**
2
- * Preact components and typed data hooks. Import from @kasperrt/smolter/preact.
3
- * @module
4
- */
5
- import { internalURL } from "../core/index.js";
6
- import { useRouter } from "./context.js";
7
- import { createElement } from "./runtime.js";
8
- /** Render an anchor that handles internal navigation and preserves native modified clicks. */
9
- export function Link({ href, replace, onClick, ...props }) {
10
- const router = useRouter();
11
- const click = (event) => {
12
- onClick?.(event);
13
- if (event.defaultPrevented ||
14
- event.button ||
15
- event.metaKey ||
16
- event.ctrlKey ||
17
- event.altKey ||
18
- event.shiftKey ||
19
- props.download !== undefined ||
20
- (props.target && props.target !== "_self"))
21
- return;
22
- if (!internalURL(router.getSnapshot().url, href))
23
- return;
24
- event.preventDefault();
25
- void router.navigate(href, { replace });
26
- };
27
- return createElement("a", { ...props, href, onClick: click });
28
- }
@@ -1,22 +0,0 @@
1
- /**
2
- * Preact components and typed data hooks. Import from @kasperrt/smolter/preact.
3
- * @module
4
- */
5
- import { useRouter, useSnapshot } from "./context.js";
6
- import { createElement, useMemo } from "./runtime.js";
7
- /** Renders the first matching registered module. */
8
- export function Routes({ fallback = null }) {
9
- const router = useRouter();
10
- const state = useSnapshot((snapshot) => snapshot);
11
- const module = router.routes.find((route) => route.path === state.path);
12
- const component = state.error
13
- ? module?.ErrorBoundary
14
- : state.state === "loading" && !state.loaded
15
- ? module?.HydrateFallback
16
- : module?.default;
17
- const element = useMemo(() => component &&
18
- createElement(component, {
19
- error: state.error,
20
- }), [component, state.error]);
21
- return element ?? state.error?.message ?? fallback;
22
- }