@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
@@ -0,0 +1,204 @@
1
+ import { safeWrapAsync } from "./errors.js";
2
+ import { matchRoutes } from "./match.js";
3
+ import { internalURL } from "./url.js";
4
+ //#region .generated/core/router.ts
5
+ /** Create an isolated router. Use browser: true for browser history, or loadRouter for SSR. */
6
+ function createDataRouter(routes, options = {}) {
7
+ const browser = options.browser && typeof window !== "undefined";
8
+ const hydrated = options.hydrationData?.state === "idle";
9
+ const initial = {
10
+ loaded: hydrated,
11
+ ...options.hydrationData ?? {
12
+ url: new URL(options.url ?? (browser ? location.href : "/"), "http://localhost").href,
13
+ params: {},
14
+ state: "idle"
15
+ }
16
+ };
17
+ const revalidators = /* @__PURE__ */ new Set();
18
+ const channel = (initial, local) => {
19
+ let snapshot = initial;
20
+ let loadedURL;
21
+ let submission;
22
+ let controller;
23
+ const listeners = /* @__PURE__ */ new Set();
24
+ const emit = (state, next) => {
25
+ snapshot = {
26
+ ...snapshot,
27
+ ...next,
28
+ state
29
+ };
30
+ for (const listener of listeners) listener();
31
+ };
32
+ const run = async (href, body, operation = {}, navigation, redirects = 10) => {
33
+ controller?.abort();
34
+ controller = new AbortController();
35
+ const { signal } = controller;
36
+ const { method = "POST" } = operation;
37
+ revalidators.add(task);
38
+ submission = body;
39
+ const previous = snapshot;
40
+ const url = internalURL(main.getSnapshot().url, href);
41
+ const match = url && matchRoutes(routes, url.pathname);
42
+ const commit = () => {
43
+ if (local || !url) return;
44
+ const changed = snapshot.url !== url.href;
45
+ if (!changed && snapshot.path !== void 0) return;
46
+ if (browser && (navigation || body) && changed) history[operation.replace ? "replaceState" : "pushState"](null, "", url);
47
+ const sameRoute = snapshot.path === match?.route.path;
48
+ snapshot = {
49
+ ...snapshot,
50
+ url: url.href,
51
+ path: match?.route.path,
52
+ params: match?.params ?? {},
53
+ actionData: void 0,
54
+ ...!sameRoute && {
55
+ data: void 0,
56
+ loaded: false
57
+ }
58
+ };
59
+ };
60
+ if (!body) commit();
61
+ emit(body ? "submitting" : "loading", { error: void 0 });
62
+ const result = await safeWrapAsync(() => {
63
+ if (!match) return Error(url ? "No route" : "External URL");
64
+ if (navigation !== void 0 && previous.loaded && !previous.error && previous.path === match.route.path && match.route.shouldRevalidate?.({
65
+ url,
66
+ currentUrl: new URL(previous.url),
67
+ nextUrl: url,
68
+ defaultShouldRevalidate: true,
69
+ reason: "navigation"
70
+ }) === false) return previous.data;
71
+ const fn = match.route[body ? "clientAction" : "clientLoader"] || match.route[body ? "action" : "loader"];
72
+ if (!fn) return body && Error("No action");
73
+ return fn({
74
+ request: new Request(url, {
75
+ signal,
76
+ method: body && method,
77
+ body
78
+ }),
79
+ params: match.params,
80
+ signal
81
+ });
82
+ });
83
+ if (signal.aborted) return result;
84
+ const redirectTo = result.data instanceof Response && result.data.headers.get("Location");
85
+ if (redirectTo && redirects) {
86
+ await main.load(new URL(redirectTo, url).href, void 0, { replace: true }, true, redirects - 1);
87
+ if (!signal.aborted) {
88
+ submission = void 0;
89
+ emit("idle");
90
+ }
91
+ return result;
92
+ }
93
+ if (redirectTo) result.error = Error("Redirect loop");
94
+ if (!body && !result.error) loadedURL = url.href;
95
+ if (body && !result.error) commit();
96
+ emit(body && !result.error ? "loading" : "idle", {
97
+ [body && !local ? "actionData" : "data"]: result.data,
98
+ error: result.error,
99
+ loaded: body ? snapshot.loaded : !result.error
100
+ });
101
+ if (body && !result.error) {
102
+ await router.revalidate("action", {
103
+ formMethod: method,
104
+ formAction: url.href,
105
+ formData: body,
106
+ actionResult: result.data
107
+ }).finally(() => {
108
+ if (!signal.aborted) {
109
+ submission = void 0;
110
+ emit("idle");
111
+ }
112
+ });
113
+ return result;
114
+ }
115
+ if (!signal.aborted) submission = void 0;
116
+ return result;
117
+ };
118
+ const task = {
119
+ revalidate: async (args, targets) => {
120
+ if (local && (!loadedURL || submission)) return;
121
+ const url = new URL(local ? loadedURL : main.getSnapshot().url);
122
+ const route = matchRoutes(routes, url.pathname)?.route;
123
+ if (!route || targets && !targets.includes(route.id) && !targets.includes(route.path)) return;
124
+ if (route.shouldRevalidate?.({
125
+ ...args,
126
+ url,
127
+ currentUrl: url,
128
+ nextUrl: url
129
+ }) === false) return;
130
+ return run(url.href);
131
+ },
132
+ getSnapshot: () => snapshot,
133
+ subscribe(listener) {
134
+ listeners.add(listener);
135
+ return () => {
136
+ listeners.delete(listener);
137
+ };
138
+ },
139
+ load: run,
140
+ submit: run,
141
+ dispose() {
142
+ controller?.abort();
143
+ listeners.clear();
144
+ revalidators.delete(task);
145
+ }
146
+ };
147
+ revalidators.add(task);
148
+ return task;
149
+ };
150
+ const main = channel(initial);
151
+ let started = 0;
152
+ const pop = () => main.load(location.href, void 0, void 0, false);
153
+ const router = {
154
+ ...main,
155
+ routes,
156
+ start() {
157
+ if (!started++ && browser) addEventListener("popstate", pop);
158
+ if (!hydrated && main.getSnapshot() === initial) main.load(initial.url);
159
+ return () => {
160
+ if (!--started && browser) removeEventListener("popstate", pop);
161
+ };
162
+ },
163
+ async navigate(url, operation) {
164
+ await main.load(url, void 0, operation, true);
165
+ },
166
+ async revalidate(options = {}, context) {
167
+ const { route, reason = "manual", ...details } = typeof options === "string" ? {
168
+ reason: options,
169
+ ...context
170
+ } : options;
171
+ const url = new URL(main.getSnapshot().url);
172
+ const args = {
173
+ ...details,
174
+ url,
175
+ currentUrl: url,
176
+ nextUrl: url,
177
+ defaultShouldRevalidate: true,
178
+ reason
179
+ };
180
+ const targets = typeof route === "string" ? [route] : route;
181
+ await Promise.all([...revalidators].map((refresh) => refresh.revalidate(args, targets)));
182
+ },
183
+ registerRevalidator(id, refresh) {
184
+ const task = { revalidate: async (args, targets) => {
185
+ if (!targets || targets.includes(id)) return refresh(args);
186
+ } };
187
+ revalidators.add(task);
188
+ return () => {
189
+ revalidators.delete(task);
190
+ };
191
+ },
192
+ createFetcher() {
193
+ return channel({ state: "idle" }, true);
194
+ },
195
+ dispose() {
196
+ for (const refresh of revalidators) refresh.dispose?.();
197
+ revalidators.clear();
198
+ if (browser) removeEventListener("popstate", pop);
199
+ }
200
+ };
201
+ return router;
202
+ }
203
+ //#endregion
204
+ export { createDataRouter };
@@ -1,3 +1,5 @@
1
1
  import type { Snapshot } from "./types.ts";
2
2
  /** Safe inside a script element. Data must be JSON serializable; error stacks and causes are omitted. */
3
3
  export declare function serialize(snapshot: Snapshot): string;
4
+ /** Encode JSON for embedding inside an HTML script element. */
5
+ export declare function serializeData(value: unknown): string;
@@ -0,0 +1,17 @@
1
+ //#region .generated/core/serialize.ts
2
+ /** Safe inside a script element. Data must be JSON serializable; error stacks and causes are omitted. */
3
+ function serialize(snapshot) {
4
+ return serializeData({
5
+ ...snapshot,
6
+ error: snapshot.error && {
7
+ name: snapshot.error.name,
8
+ message: snapshot.error.message
9
+ }
10
+ });
11
+ }
12
+ /** Encode JSON for embedding inside an HTML script element. */
13
+ function serializeData(value) {
14
+ return JSON.stringify(value).replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
15
+ }
16
+ //#endregion
17
+ export { serialize, serializeData };
@@ -0,0 +1,6 @@
1
+ /** Small observable value shared by layout data and navigation state. */
2
+ export declare function createStore<T>(initial: T): {
3
+ getSnapshot: () => T;
4
+ subscribe(listener: () => void): () => void;
5
+ update(next: T): void;
6
+ };
@@ -0,0 +1,21 @@
1
+ //#region .generated/core/store.ts
2
+ /** Small observable value shared by layout data and navigation state. */
3
+ function createStore(initial) {
4
+ let value = initial;
5
+ const listeners = /* @__PURE__ */ new Set();
6
+ return {
7
+ getSnapshot: () => value,
8
+ subscribe(listener) {
9
+ listeners.add(listener);
10
+ return () => {
11
+ listeners.delete(listener);
12
+ };
13
+ },
14
+ update(next) {
15
+ value = next;
16
+ for (const listener of listeners) listener();
17
+ }
18
+ };
19
+ }
20
+ //#endregion
21
+ export { createStore };
@@ -0,0 +1,30 @@
1
+ import type { Fetcher, NavigateOptions, Router } from "./types.ts";
2
+ /** Values accepted by programmatic route submissions. */
3
+ export type Submission = FormData | HTMLFormElement | Record<string, unknown>;
4
+ /** Destination and encoding for a programmatic route submission. */
5
+ export interface SubmissionOptions {
6
+ action?: string;
7
+ encType?: string;
8
+ method?: string;
9
+ replace?: boolean;
10
+ preventScrollReset?: boolean;
11
+ }
12
+ export declare function formData(value: Submission): FormData;
13
+ export declare function submissionBody(value: Submission, encoding?: string): FormData;
14
+ /** Encode successful form controls for a GET route request. */
15
+ export declare function submissionSearch(body: FormData): string;
16
+ /** Read the JSON payload of a programmatic submission. */
17
+ export declare function readSubmission(request: Request): Promise<unknown>;
18
+ /** Browser form behavior shared by the React and Preact adapters. */
19
+ export interface RouteFormOptions extends NavigateOptions {
20
+ action?: string;
21
+ method?: "get" | "post" | "put" | "patch" | "delete";
22
+ reloadDocument?: boolean;
23
+ }
24
+ export interface FormSubmissionEvent {
25
+ currentTarget: HTMLFormElement;
26
+ defaultPrevented: boolean;
27
+ preventDefault: () => void;
28
+ }
29
+ /** Submit an internal form while leaving external actions to the browser. */
30
+ export declare function submitForm(router: Router, event: FormSubmissionEvent, submitter: HTMLElement | null, options: RouteFormOptions, fetcher?: Fetcher): void;
@@ -0,0 +1,41 @@
1
+ import { internalURL } from "./url.js";
2
+ //#region .generated/core/submission.ts
3
+ function formData(value) {
4
+ if (value instanceof FormData) return value;
5
+ if (typeof HTMLFormElement !== "undefined" && value instanceof HTMLFormElement) return new FormData(value);
6
+ const body = new FormData();
7
+ for (const [key, field] of Object.entries(value)) body.append(key, String(field));
8
+ return body;
9
+ }
10
+ function submissionBody(value, encoding) {
11
+ return formData(encoding === "application/json" ? { _payload: JSON.stringify(value) } : value);
12
+ }
13
+ /** Encode successful form controls for a GET route request. */
14
+ function submissionSearch(body) {
15
+ const search = new URLSearchParams();
16
+ for (const [key, value] of body) search.append(key, typeof value === "string" ? value : value.name);
17
+ return String(search);
18
+ }
19
+ /** Read the JSON payload of a programmatic submission. */
20
+ async function readSubmission(request) {
21
+ if (request.headers.get("Content-Type")?.includes("application/json")) return request.json();
22
+ const body = await request.formData();
23
+ return JSON.parse(String(body.get("_payload") ?? "{}"));
24
+ }
25
+ /** Submit an internal form while leaving external actions to the browser. */
26
+ function submitForm(router, event, submitter, options, fetcher) {
27
+ if (event.defaultPrevented || options.reloadDocument) return;
28
+ const target = internalURL(router.getSnapshot().url, submitter?.getAttribute("formaction") ?? options.action);
29
+ if (!target) return;
30
+ const data = new FormData(event.currentTarget, submitter);
31
+ const method = (submitter?.getAttribute("formmethod") ?? options.method)?.toUpperCase();
32
+ event.preventDefault();
33
+ if (method === "GET") {
34
+ target.search = submissionSearch(data);
35
+ fetcher ? fetcher.load(target.href) : router.navigate(target.href, options);
36
+ return;
37
+ }
38
+ (fetcher ?? router).submit(target.href, data, { method });
39
+ }
40
+ //#endregion
41
+ export { formData, readSubmission, submissionBody, submissionSearch, submitForm };
@@ -41,6 +41,8 @@ export interface RevalidationArgs extends RevalidationContext {
41
41
  }
42
42
  /** A route component and its optional server and client data handlers. */
43
43
  export interface RouteModule {
44
+ /** Stable identifier for targeted revalidation; defaults to the path. */
45
+ id?: string;
44
46
  default?: unknown;
45
47
  HydrateFallback?: unknown;
46
48
  ErrorBoundary?: unknown;
@@ -80,7 +82,11 @@ export interface RouterOptions {
80
82
  }
81
83
  /** Options for replacing rather than appending a browser history entry. */
82
84
  export interface NavigateOptions {
85
+ state?: unknown;
86
+ viewTransition?: boolean;
83
87
  replace?: boolean;
88
+ /** Preserve scroll in integrations that manage scroll restoration. */
89
+ preventScrollReset?: boolean;
84
90
  }
85
91
  /** HTTP method for a route action submission; defaults to POST. */
86
92
  export interface SubmitOptions {
@@ -104,6 +110,13 @@ export interface Fetcher {
104
110
  /** Abort work and release subscriptions when this imperative fetcher is no longer used. */
105
111
  dispose: () => void;
106
112
  }
113
+ /** Refresh active route data without navigation, optionally selecting route IDs or paths. */
114
+ export interface RevalidateOptions extends RevalidationContext {
115
+ route?: string | readonly string[];
116
+ reason?: RevalidationArgs["reason"];
117
+ }
118
+ /** A registered layout loader's revalidation callback. */
119
+ export type Revalidator = (args: RevalidationArgs) => void | Promise<void>;
107
120
  /** Route navigation, subscriptions, fetchers, and loader revalidation. */
108
121
  export interface Router {
109
122
  routes: readonly Route[];
@@ -111,7 +124,9 @@ export interface Router {
111
124
  subscribe: (listener: () => void) => () => void;
112
125
  start: () => () => void;
113
126
  navigate: (url: string, options?: NavigateOptions) => Promise<void>;
114
- revalidate: (reason?: RevalidationArgs["reason"], context?: RevalidationContext) => Promise<void>;
127
+ revalidate: (options?: RevalidateOptions | RevalidationArgs["reason"], context?: RevalidationContext) => Promise<void>;
128
+ /** Register layout data under a route ID. The returned function unregisters it. */
129
+ registerRevalidator: (id: string, revalidate: Revalidator) => () => void;
115
130
  submit: (url: string, body: FormData, options?: SubmitOptions) => Promise<Result>;
116
131
  /** Create an independent fetcher. Framework useFetcher hooks manage disposal automatically. */
117
132
  createFetcher: () => Fetcher;
@@ -0,0 +1,7 @@
1
+ /** Resolve an internal URL; external links remain the browser's responsibility. */
2
+ export declare function internalURL(base: string, href?: string): URL | undefined;
3
+ /** Browser and framework event fields used by navigation interception. */
4
+ type NavigationClickEvent = Pick<MouseEvent, "defaultPrevented" | "button" | "metaKey" | "ctrlKey" | "altKey" | "shiftKey">;
5
+ /** Whether an anchor click should use router navigation. */
6
+ export declare function navigationClick(event: NavigationClickEvent, anchor: HTMLAnchorElement): boolean;
7
+ export {};
@@ -0,0 +1,12 @@
1
+ //#region .generated/core/url.ts
2
+ /** Resolve an internal URL; external links remain the browser's responsibility. */
3
+ function internalURL(base, href = base) {
4
+ const url = new URL(href, base);
5
+ if (url.origin === new URL(base).origin) return url;
6
+ }
7
+ /** Whether an anchor click should use router navigation. */
8
+ function navigationClick(event, anchor) {
9
+ return !(event.defaultPrevented || event.button || event.metaKey || event.ctrlKey || event.altKey || event.shiftKey || anchor.hasAttribute("download") || anchor.target && anchor.target !== "_self");
10
+ }
11
+ //#endregion
12
+ export { internalURL, navigationClick };
@@ -0,0 +1,5 @@
1
+ /** Framework-independent, browser-safe utilities. @module */
2
+ export * from "./errors.ts";
3
+ export * from "./match.ts";
4
+ export * from "./serialize.ts";
5
+ export * from "./url.ts";
@@ -0,0 +1,5 @@
1
+ import { safeWrapAsync } from "./errors.js";
2
+ import { defineRoute, matchRoutes } from "./match.js";
3
+ import { internalURL, navigationClick } from "./url.js";
4
+ import { serialize, serializeData } from "./serialize.js";
5
+ export { defineRoute, internalURL, matchRoutes, navigationClick, safeWrapAsync, serialize, serializeData };
@@ -0,0 +1,64 @@
1
+ import { safeWrapAsync } from "../core/errors.js";
2
+ import { readdir } from "node:fs/promises";
3
+ import { resolve } from "node:path";
4
+ //#region .generated/files/index.ts
5
+ /**
6
+ * Build-time discovery of flat file routes.
7
+ * @module
8
+ */
9
+ var routeFile = /^route\.[jt]sx?$/;
10
+ var flatFile = /\.[jt]sx?$/;
11
+ /** Read only flat files and immediate route folders. Colocated helpers are ignored. */
12
+ async function flatRoutes(directory) {
13
+ const result = await safeWrapAsync(async () => {
14
+ const entries = await readdir(directory, { withFileTypes: true });
15
+ const routes = [];
16
+ const patterns = /* @__PURE__ */ new Set();
17
+ for (const entry of entries) {
18
+ let name = entry.name;
19
+ let file = resolve(directory, name);
20
+ if (entry.isDirectory()) {
21
+ const candidates = (await readdir(file)).filter((name) => routeFile.test(name));
22
+ if (candidates.length > 1) return /* @__PURE__ */ new Error(`Multiple route files in ${file}`);
23
+ if (!candidates.length) continue;
24
+ file = resolve(file, candidates[0]);
25
+ } else {
26
+ if (!entry.isFile() || !flatFile.test(name) || name.endsWith(".d.ts")) continue;
27
+ name = name.replace(flatFile, "");
28
+ }
29
+ if (/[()[\]:*?]/.test(name)) return /* @__PURE__ */ new Error(`Unsupported flat route name: ${name}`);
30
+ const segments = name.split(".");
31
+ const parts = [];
32
+ for (const [index, segment] of segments.entries()) {
33
+ if (!segment) return /* @__PURE__ */ new Error(`Empty route segment: ${name}`);
34
+ if (segment === "_index" && index !== segments.length - 1) return /* @__PURE__ */ new Error(`_index must be last: ${name}`);
35
+ if (segment === "$" && index !== segments.length - 1) return /* @__PURE__ */ new Error(`Splat must be last: ${name}`);
36
+ if (segment.startsWith("_")) continue;
37
+ const part = segment.replace(/_$/, "");
38
+ parts.push(part === "$" ? "*" : part.replace(/^\$/, ":"));
39
+ }
40
+ if (segments.at(-1)?.startsWith("_") && segments.at(-1) !== "_index") continue;
41
+ const path = `/${parts.join("/")}`;
42
+ const pattern = path.replace(/:[^/]+/g, ":");
43
+ if (patterns.has(pattern)) return /* @__PURE__ */ new Error(`Conflicting flat route: ${path}`);
44
+ patterns.add(pattern);
45
+ routes.push({
46
+ path,
47
+ file
48
+ });
49
+ }
50
+ return routes.sort((a, b) => {
51
+ const left = a.path.split("/");
52
+ const right = b.path.split("/");
53
+ const rank = (part) => part === "*" ? 0 : part?.startsWith(":") ? 1 : part === void 0 ? 2 : 3;
54
+ for (let index = 0; index < Math.max(left.length, right.length); index++) {
55
+ const difference = rank(right[index]) - rank(left[index]);
56
+ if (difference) return difference;
57
+ }
58
+ return a.path.localeCompare(b.path);
59
+ });
60
+ });
61
+ return result.error ?? result.data;
62
+ }
63
+ //#endregion
64
+ export { flatRoutes };
@@ -0,0 +1,7 @@
1
+ import type { ComponentChildren } from "preact";
2
+ export interface AwaitProps<T> {
3
+ resolve: T | Promise<T>;
4
+ children: (value: Awaited<T>) => ComponentChildren;
5
+ errorElement?: ComponentChildren;
6
+ }
7
+ export declare function Await<T>({ resolve, children, errorElement, }: AwaitProps<T>): ComponentChildren;
@@ -0,0 +1,19 @@
1
+ import { use } from "preact/compat";
2
+ //#region .generated/preact/await.tsx
3
+ var resolutions = /* @__PURE__ */ new WeakMap();
4
+ function Await({ resolve, children, errorElement }) {
5
+ if (!(resolve instanceof Promise)) return children(resolve);
6
+ let pending = resolutions.get(resolve);
7
+ if (!pending) {
8
+ pending = resolve.then((value) => ({ value }), (error) => ({ error }));
9
+ resolutions.set(resolve, pending);
10
+ }
11
+ const result = use(pending);
12
+ if ("error" in result) {
13
+ if (errorElement !== void 0) return errorElement;
14
+ throw result.error;
15
+ }
16
+ return children(result.value);
17
+ }
18
+ //#endregion
19
+ export { Await };
@@ -0,0 +1,7 @@
1
+ /** Browser mounting and hydration for Preact applications. @module */
2
+ /** @jsxImportSource preact */
3
+ import { type Router } from "../core/index.ts";
4
+ import type { AppRoute, RootModule } from "./route-types.ts";
5
+ export * from "../client/index.ts";
6
+ export { configureApplication } from "../core/environment.ts";
7
+ export declare function mountApplication(routes: AppRoute[], rootModule?: RootModule, hash?: boolean): Promise<Router>;
@@ -0,0 +1,67 @@
1
+ import { matchRoutes } from "../core/match.js";
2
+ import { createDataRouter } from "../core/router.js";
3
+ import { configureApplication } from "../core/environment.js";
4
+ import { retainRoute } from "../core/presentation.js";
5
+ import { connectNavigation } from "../core/navigation.js";
6
+ import { attachRootData } from "./root-data.js";
7
+ import { RouterApplication, prepareRoutes } from "./router.js";
8
+ import { deserialize } from "../client/deserialize.js";
9
+ import { serverRoute } from "../client/index.js";
10
+ import { flushSync } from "preact/compat";
11
+ import { jsx } from "preact/jsx-runtime";
12
+ import { hydrate, render } from "preact";
13
+ //#region .generated/preact/client.tsx
14
+ /** Browser mounting and hydration for Preact applications. @module */
15
+ /** @jsxImportSource preact */
16
+ async function mountApplication(routes, rootModule, hash = false) {
17
+ const initialURL = new URL(hash ? location.hash.slice(1) || "/" : location.href, location.origin);
18
+ await (matchRoutes(routes, initialURL.pathname)?.route)?.preload?.();
19
+ routes = prepareRoutes(routes);
20
+ const element = document.getElementById("smolter-data");
21
+ const hydration = element?.textContent ? JSON.parse(element.textContent) : void 0;
22
+ if (hydration?.snapshot.error) hydration.snapshot.error = Object.assign(new Error(hydration.snapshot.error.message), hydration.snapshot.error);
23
+ const router = createDataRouter(routes, {
24
+ browser: !hash,
25
+ url: hash ? new URL(location.hash.slice(1) || "/", location.origin).href : location.href,
26
+ ...hydration && { hydrationData: hydration.snapshot }
27
+ });
28
+ retainRoute(router, flushSync);
29
+ if (hash) connectHashHistory(router);
30
+ connectNavigation(router);
31
+ const rootStore = attachRootData(router, hydration?.root, rootModule);
32
+ const props = {
33
+ routes,
34
+ router,
35
+ root: hydration?.root,
36
+ rootModule,
37
+ rootStore
38
+ };
39
+ if (hydration) {
40
+ hydrate(/* @__PURE__ */ jsx(RouterApplication, {
41
+ ...props,
42
+ document: true
43
+ }), document);
44
+ return router;
45
+ }
46
+ const target = document.getElementById("root");
47
+ if (target) render(/* @__PURE__ */ jsx(RouterApplication, { ...props }), target);
48
+ return router;
49
+ }
50
+ function connectHashHistory(router) {
51
+ const navigate = router.navigate;
52
+ router.navigate = async (url, options) => {
53
+ const target = new URL(url, router.getSnapshot().url);
54
+ const hash = `#${target.pathname}${target.search}`;
55
+ history[options?.replace ? "replaceState" : "pushState"](null, "", hash);
56
+ await navigate(url, options);
57
+ };
58
+ const onHashChange = () => void navigate(location.hash.slice(1) || "/");
59
+ window.addEventListener("hashchange", onHashChange);
60
+ const dispose = router.dispose;
61
+ router.dispose = () => {
62
+ window.removeEventListener("hashchange", onHashChange);
63
+ dispose();
64
+ };
65
+ }
66
+ //#endregion
67
+ export { configureApplication, deserialize, mountApplication, serverRoute };
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Preact components and typed data hooks. Import from @kasperrt/smolter/preact.
3
+ * @module
4
+ */
5
+ import type { NavigationRouter, 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: NavigationRouter;
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<T extends NavigationRouter = NavigationRouter>(): T;
16
+ export declare function useSnapshot(): Snapshot;
17
+ export declare function useSnapshot<K extends keyof Snapshot>(key: K): Snapshot[K];
18
+ export declare function useSnapshot<K extends keyof Snapshot>(key: K, path: string | undefined): Snapshot[K] | undefined;
@@ -0,0 +1,23 @@
1
+ import { createContext, createElement, useContext, useEffect, useSyncExternalStore } from "./runtime.js";
2
+ //#region .generated/preact/context.ts
3
+ var RouterContext = createContext(void 0);
4
+ /** Provide a router to descendant hooks and connect its browser lifecycle. */
5
+ function RouterProvider({ router, children }) {
6
+ useEffect(router.start, [router]);
7
+ return createElement(RouterContext, { value: router }, children);
8
+ }
9
+ /** Hooks require a RouterProvider. */
10
+ function useRouter() {
11
+ return useContext(RouterContext);
12
+ }
13
+ function useSnapshot(key, path) {
14
+ const router = useRouter();
15
+ const getSnapshot = () => {
16
+ const snapshot = router.getSnapshot();
17
+ if (path !== void 0 && path !== snapshot.path) return void 0;
18
+ return key ? snapshot[key] : snapshot;
19
+ };
20
+ return useSyncExternalStore(router.subscribe, getSnapshot);
21
+ }
22
+ //#endregion
23
+ export { RouterProvider, useRouter, useSnapshot };
@@ -0,0 +1,15 @@
1
+ /** @jsxImportSource preact */
2
+ import type { ComponentProps } from "preact/compat";
3
+ export declare function Meta(): import("preact").JSX.Element;
4
+ export declare function Links(): import("preact").JSX.Element;
5
+ export interface ScriptProps {
6
+ nonce?: string;
7
+ }
8
+ export declare function Scripts({ nonce }: ScriptProps): import("preact").JSX.Element;
9
+ export declare function ScrollRestoration(_props: ScriptProps): null;
10
+ /** HTML attributes for server markup and client document reconciliation. */
11
+ export interface DocumentProps extends ComponentProps<"html"> {
12
+ "data-debug"?: boolean;
13
+ }
14
+ /** Render the server document and preserve client-managed theme classes on hydration. */
15
+ export declare function Document(props: DocumentProps): import("preact").JSX.Element;