@kasperrt/smolter 0.0.1 → 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 (246) 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 +2 -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/.generated/core/serialize.ts +20 -0
  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 +4 -17
  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/internal → .generated}/vite/index.ts +4 -2
  55. package/.generated/vite/preact.ts +24 -0
  56. package/.generated/vite/react.ts +18 -0
  57. package/{src/internal → .generated}/vite/transport.ts +2 -1
  58. package/CHANGELOG.md +54 -0
  59. package/README.md +269 -55
  60. package/dist/client/deserialize.js +10 -0
  61. package/dist/{internal/client → client}/index.d.ts +1 -0
  62. package/dist/client/index.js +46 -0
  63. package/dist/core/environment.d.ts +12 -0
  64. package/dist/core/environment.js +23 -0
  65. package/dist/core/errors.js +7 -0
  66. package/dist/{internal/core → core}/index.d.ts +2 -0
  67. package/dist/core/match.js +33 -0
  68. package/dist/core/navigation-router.d.ts +15 -0
  69. package/dist/core/navigation-router.js +64 -0
  70. package/dist/core/navigation.d.ts +15 -0
  71. package/dist/core/navigation.js +92 -0
  72. package/dist/core/presentation.d.ts +5 -0
  73. package/dist/core/presentation.js +69 -0
  74. package/dist/core/resource-types.d.ts +37 -0
  75. package/dist/core/resources.d.ts +19 -0
  76. package/dist/core/resources.js +345 -0
  77. package/dist/core/responses.d.ts +15 -0
  78. package/dist/core/responses.js +48 -0
  79. package/dist/{internal/core → core}/router.d.ts +1 -1
  80. package/dist/core/router.js +204 -0
  81. package/dist/core/serialize.d.ts +5 -0
  82. package/dist/core/serialize.js +17 -0
  83. package/dist/core/store.d.ts +6 -0
  84. package/dist/core/store.js +21 -0
  85. package/dist/core/submission.d.ts +30 -0
  86. package/dist/core/submission.js +41 -0
  87. package/dist/{internal/core → core}/types.d.ts +16 -1
  88. package/dist/core/url.d.ts +7 -0
  89. package/dist/core/url.js +12 -0
  90. package/dist/core/utils.d.ts +5 -0
  91. package/dist/core/utils.js +5 -0
  92. package/dist/files/index.js +64 -0
  93. package/dist/preact/await.d.ts +7 -0
  94. package/dist/preact/await.js +19 -0
  95. package/dist/preact/client.d.ts +7 -0
  96. package/dist/preact/client.js +67 -0
  97. package/dist/preact/context.d.ts +18 -0
  98. package/dist/preact/context.js +23 -0
  99. package/dist/preact/document.d.ts +15 -0
  100. package/dist/preact/document.js +155 -0
  101. package/dist/{internal/preact → preact}/fetcher.d.ts +9 -5
  102. package/dist/preact/fetcher.js +45 -0
  103. package/dist/{internal/preact → preact}/form.d.ts +5 -6
  104. package/dist/preact/form.js +21 -0
  105. package/dist/{internal/preact → preact}/hooks.d.ts +7 -6
  106. package/dist/preact/hooks.js +25 -0
  107. package/dist/preact/index.d.ts +18 -0
  108. package/dist/preact/index.js +15 -0
  109. package/dist/preact/lazy-route.d.ts +19 -0
  110. package/dist/preact/lazy-route.js +51 -0
  111. package/dist/preact/navigation-state.d.ts +2 -0
  112. package/dist/preact/navigation-state.js +9 -0
  113. package/dist/preact/navigation.d.ts +28 -0
  114. package/dist/preact/navigation.js +60 -0
  115. package/dist/preact/root-context.d.ts +13 -0
  116. package/dist/preact/root-context.js +9 -0
  117. package/dist/preact/root-data.d.ts +8 -0
  118. package/dist/preact/root-data.js +28 -0
  119. package/dist/preact/route-hooks.d.ts +32 -0
  120. package/dist/preact/route-hooks.js +94 -0
  121. package/dist/preact/route-types.d.ts +54 -0
  122. package/dist/preact/router.d.ts +16 -0
  123. package/dist/preact/router.js +63 -0
  124. package/dist/{internal/preact → preact}/routes.d.ts +1 -1
  125. package/dist/preact/routes.js +12 -0
  126. package/dist/{internal/preact → preact}/runtime.js +1 -1
  127. package/dist/react/context.d.ts +18 -0
  128. package/dist/react/context.js +23 -0
  129. package/dist/react/data.d.ts +5 -0
  130. package/dist/react/data.js +3 -0
  131. package/dist/{internal/react → react}/fetcher.d.ts +2 -2
  132. package/dist/react/fetcher.js +34 -0
  133. package/dist/{internal/react → react}/form.d.ts +5 -6
  134. package/dist/react/form.js +21 -0
  135. package/dist/react/hooks.js +25 -0
  136. package/dist/{internal/react → react}/index.d.ts +1 -1
  137. package/dist/react/index.js +10 -0
  138. package/dist/{internal/react → react}/link.d.ts +2 -2
  139. package/dist/react/link.js +27 -0
  140. package/dist/react/resources/components.d.ts +64 -0
  141. package/dist/react/resources/components.js +239 -0
  142. package/dist/{internal/react → react}/routes.d.ts +1 -1
  143. package/dist/react/routes.js +12 -0
  144. package/dist/react/runtime.js +1 -0
  145. package/dist/server/application.d.ts +4 -0
  146. package/dist/server/application.js +119 -0
  147. package/dist/server/assets.d.ts +5 -0
  148. package/dist/server/assets.js +7 -0
  149. package/dist/server/handler.d.ts +19 -0
  150. package/dist/server/handler.js +22 -0
  151. package/dist/server/http.d.ts +4 -0
  152. package/dist/server/http.js +27 -0
  153. package/dist/{internal/server → server}/index.d.ts +2 -3
  154. package/dist/server/index.js +76 -0
  155. package/dist/server/node.d.ts +3 -0
  156. package/dist/server/node.js +3 -0
  157. package/dist/server/preact.d.ts +3 -0
  158. package/dist/server/preact.js +4 -0
  159. package/dist/vite/application/browser-boundary.d.ts +3 -0
  160. package/dist/vite/application/browser-boundary.js +35 -0
  161. package/dist/vite/application/lazy-files.d.ts +2 -0
  162. package/dist/vite/application/lazy-files.js +87 -0
  163. package/dist/vite/application/vite.d.ts +9 -0
  164. package/dist/vite/application/vite.js +101 -0
  165. package/dist/{internal/vite → vite}/index.d.ts +2 -1
  166. package/dist/vite/index.js +104 -0
  167. package/dist/vite/preact.d.ts +11 -0
  168. package/dist/vite/preact.js +15 -0
  169. package/dist/vite/react.d.ts +9 -0
  170. package/dist/vite/react.js +14 -0
  171. package/dist/{internal/vite → vite}/transport.d.ts +1 -1
  172. package/dist/vite/transport.js +35 -0
  173. package/jsr.json +28 -11
  174. package/package.json +119 -38
  175. package/dist/client.d.ts +0 -2
  176. package/dist/client.js +0 -2
  177. package/dist/core.d.ts +0 -2
  178. package/dist/core.js +0 -2
  179. package/dist/files.d.ts +0 -2
  180. package/dist/files.js +0 -2
  181. package/dist/internal/client/deserialize.js +0 -11
  182. package/dist/internal/client/index.js +0 -44
  183. package/dist/internal/core/errors.js +0 -6
  184. package/dist/internal/core/index.js +0 -6
  185. package/dist/internal/core/match.js +0 -27
  186. package/dist/internal/core/router.js +0 -189
  187. package/dist/internal/core/types.js +0 -5
  188. package/dist/internal/core/url.d.ts +0 -2
  189. package/dist/internal/core/url.js +0 -6
  190. package/dist/internal/files/index.js +0 -77
  191. package/dist/internal/preact/context.d.ts +0 -18
  192. package/dist/internal/preact/context.js +0 -20
  193. package/dist/internal/preact/fetcher.js +0 -28
  194. package/dist/internal/preact/form.js +0 -41
  195. package/dist/internal/preact/hooks.js +0 -35
  196. package/dist/internal/preact/index.d.ts +0 -9
  197. package/dist/internal/preact/index.js +0 -7
  198. package/dist/internal/preact/link.d.ts +0 -12
  199. package/dist/internal/preact/link.js +0 -28
  200. package/dist/internal/preact/routes.js +0 -22
  201. package/dist/internal/react/context.d.ts +0 -18
  202. package/dist/internal/react/context.js +0 -20
  203. package/dist/internal/react/fetcher.js +0 -28
  204. package/dist/internal/react/form.js +0 -41
  205. package/dist/internal/react/hooks.js +0 -35
  206. package/dist/internal/react/index.js +0 -7
  207. package/dist/internal/react/link.js +0 -28
  208. package/dist/internal/react/routes.js +0 -22
  209. package/dist/internal/react/runtime.js +0 -2
  210. package/dist/internal/server/index.js +0 -81
  211. package/dist/internal/vite/index.js +0 -132
  212. package/dist/internal/vite/transport.js +0 -42
  213. package/dist/preact.d.ts +0 -2
  214. package/dist/preact.js +0 -2
  215. package/dist/react.d.ts +0 -2
  216. package/dist/react.js +0 -2
  217. package/dist/server.d.ts +0 -2
  218. package/dist/server.js +0 -2
  219. package/dist/vite.d.ts +0 -2
  220. package/dist/vite.js +0 -2
  221. package/src/client.ts +0 -2
  222. package/src/core.ts +0 -2
  223. package/src/files.ts +0 -2
  224. package/src/internal/core/url.ts +0 -6
  225. package/src/internal/preact/context.ts +0 -46
  226. package/src/internal/preact/form.ts +0 -79
  227. package/src/internal/preact/link.ts +0 -46
  228. package/src/internal/react/context.ts +0 -46
  229. package/src/internal/react/form.ts +0 -79
  230. package/src/internal/react/index.ts +0 -13
  231. package/src/preact.ts +0 -2
  232. package/src/react.ts +0 -2
  233. package/src/server.ts +0 -2
  234. package/src/vite.ts +0 -2
  235. /package/{src/internal → .generated}/client/deserialize.ts +0 -0
  236. /package/{src/internal → .generated}/core/match.ts +0 -0
  237. /package/{src/internal → .generated}/files/index.ts +0 -0
  238. /package/{src/internal → .generated}/preact/runtime.ts +0 -0
  239. /package/{src/internal → .generated}/react/runtime.ts +0 -0
  240. /package/dist/{internal/client → client}/deserialize.d.ts +0 -0
  241. /package/dist/{internal/core → core}/errors.d.ts +0 -0
  242. /package/dist/{internal/core → core}/match.d.ts +0 -0
  243. /package/dist/{internal/files → files}/index.d.ts +0 -0
  244. /package/dist/{internal/preact → preact}/runtime.d.ts +0 -0
  245. /package/dist/{internal/react → react}/hooks.d.ts +0 -0
  246. /package/dist/{internal/react → react}/runtime.d.ts +0 -0
@@ -0,0 +1,345 @@
1
+ import { safeWrapAsync } from "./errors.js";
2
+ //#region .generated/core/resources.ts
3
+ var IDLE_ROUTE_STATE = {
4
+ actionData: null,
5
+ actionError: "",
6
+ actionState: "idle",
7
+ loaderData: null,
8
+ loaderError: "",
9
+ loaderInitialized: false,
10
+ loaderState: "idle",
11
+ navigationState: "idle",
12
+ unexpectedError: false
13
+ };
14
+ var ResourceRouter = class {
15
+ #cleanupTimers = /* @__PURE__ */ new Map();
16
+ #controllers = /* @__PURE__ */ new Map();
17
+ #inFlightLoads = /* @__PURE__ */ new Map();
18
+ #listeners = /* @__PURE__ */ new Map();
19
+ #persistentRoutes = /* @__PURE__ */ new Set();
20
+ #routes;
21
+ #states = /* @__PURE__ */ new Map();
22
+ constructor(routes) {
23
+ this.#routes = routes;
24
+ }
25
+ dispose() {
26
+ for (const controller of this.#controllers.values()) controller.abort();
27
+ this.#controllers.clear();
28
+ for (const timer of this.#cleanupTimers.values()) clearTimeout(timer);
29
+ this.#cleanupTimers.clear();
30
+ this.#inFlightLoads.clear();
31
+ this.#listeners.clear();
32
+ this.#persistentRoutes.clear();
33
+ }
34
+ getRoute(routeId) {
35
+ return this.#routes.get(routeId) ?? null;
36
+ }
37
+ getSnapshot(routeId, params) {
38
+ return this.#states.get(createRouteKey(routeId, params)) ?? IDLE_ROUTE_STATE;
39
+ }
40
+ hydrateRoute(routeId, data, params = {}) {
41
+ const routeKey = createRouteKey(routeId, params);
42
+ const currentState = this.#states.get(routeKey) ?? IDLE_ROUTE_STATE;
43
+ this.#setState(routeKey, {
44
+ ...currentState,
45
+ loaderData: data,
46
+ loaderError: "",
47
+ loaderInitialized: true,
48
+ loaderState: "idle",
49
+ navigationState: currentState.actionState === "submitting" ? "submitting" : "idle",
50
+ unexpectedError: false
51
+ });
52
+ }
53
+ persist(routeId, params = {}) {
54
+ const routeKey = createRouteKey(routeId, params);
55
+ this.#persistentRoutes.add(routeKey);
56
+ const cleanupTimer = this.#cleanupTimers.get(routeKey);
57
+ if (!cleanupTimer) return;
58
+ clearTimeout(cleanupTimer);
59
+ this.#cleanupTimers.delete(routeKey);
60
+ }
61
+ disposeRoute(routeId, params = {}) {
62
+ this.#disposeRouteKey(createRouteKey(routeId, params));
63
+ }
64
+ #disposeRouteKey(routeKey) {
65
+ this.#persistentRoutes.delete(routeKey);
66
+ const cleanupTimer = this.#cleanupTimers.get(routeKey);
67
+ if (cleanupTimer) clearTimeout(cleanupTimer);
68
+ this.#cleanupTimers.delete(routeKey);
69
+ this.#controllers.get(routeKey)?.abort();
70
+ this.#controllers.get(`${routeKey}:action`)?.abort();
71
+ this.#controllers.delete(routeKey);
72
+ this.#controllers.delete(`${routeKey}:action`);
73
+ this.#inFlightLoads.delete(routeKey);
74
+ this.#states.delete(routeKey);
75
+ this.#notify(routeKey);
76
+ }
77
+ load(routeId, params = {}, force = false) {
78
+ const routeKey = createRouteKey(routeId, params);
79
+ const route = this.#routes.get(routeId);
80
+ if (!route?.loader) return Promise.resolve({
81
+ data: null,
82
+ error: "Route loader not found."
83
+ });
84
+ const currentState = this.#states.get(routeKey);
85
+ if (!force && currentState?.loaderInitialized) return Promise.resolve({
86
+ data: currentState?.loaderData,
87
+ error: currentState?.loaderError ?? ""
88
+ });
89
+ const currentLoad = this.#inFlightLoads.get(routeKey);
90
+ if (force && currentLoad) {
91
+ this.#controllers.get(routeKey)?.abort();
92
+ this.#inFlightLoads.delete(routeKey);
93
+ }
94
+ if (!force && currentLoad) return currentLoad;
95
+ const controller = this.#replaceController(routeKey);
96
+ this.#setState(routeKey, {
97
+ ...currentState ?? IDLE_ROUTE_STATE,
98
+ loaderError: "",
99
+ loaderState: "loading",
100
+ navigationState: currentState?.actionState === "submitting" ? "submitting" : "loading",
101
+ unexpectedError: false
102
+ });
103
+ const load = this.#runLoader(route, routeKey, params, controller);
104
+ this.#inFlightLoads.set(routeKey, load);
105
+ return load;
106
+ }
107
+ subscribe(routeId, params, listener) {
108
+ const routeKey = createRouteKey(routeId, params);
109
+ const cleanupTimer = this.#cleanupTimers.get(routeKey);
110
+ if (cleanupTimer) {
111
+ clearTimeout(cleanupTimer);
112
+ this.#cleanupTimers.delete(routeKey);
113
+ }
114
+ const listeners = this.#listeners.get(routeKey) ?? /* @__PURE__ */ new Set();
115
+ listeners.add(listener);
116
+ this.#listeners.set(routeKey, listeners);
117
+ return () => {
118
+ if (!listeners.delete(listener) || listeners.size > 0) return;
119
+ this.#listeners.delete(routeKey);
120
+ if (this.#persistentRoutes.has(routeKey)) return;
121
+ const timer = setTimeout(() => this.#disposeRouteKey(routeKey), routeCleanupGraceMs);
122
+ this.#cleanupTimers.set(routeKey, timer);
123
+ };
124
+ }
125
+ async submit(routeId, input, params = {}) {
126
+ const routeKey = createRouteKey(routeId, params);
127
+ const route = this.#routes.get(routeId);
128
+ if (!route?.action) return {
129
+ data: null,
130
+ error: "Route action not found."
131
+ };
132
+ const controllerKey = `${routeKey}:action`;
133
+ const controller = this.#replaceController(controllerKey);
134
+ const currentState = this.#states.get(routeKey) ?? IDLE_ROUTE_STATE;
135
+ this.#setState(routeKey, {
136
+ ...currentState,
137
+ actionError: "",
138
+ actionState: "submitting",
139
+ navigationState: "submitting",
140
+ unexpectedError: false
141
+ });
142
+ const { error, data: result } = await safeWrapAsync(() => route.action?.({
143
+ input,
144
+ params,
145
+ signal: controller.signal
146
+ }));
147
+ if (this.#controllers.get(controllerKey) === controller) this.#controllers.delete(controllerKey);
148
+ if (controller.signal.aborted) return {
149
+ data: null,
150
+ error: ""
151
+ };
152
+ const latestState = this.#states.get(routeKey) ?? IDLE_ROUTE_STATE;
153
+ if (error || !result) {
154
+ const publicError = "The request could not be completed.";
155
+ this.#setState(routeKey, {
156
+ ...latestState,
157
+ actionData: null,
158
+ actionError: publicError,
159
+ actionState: "idle",
160
+ navigationState: latestState.loaderState === "loading" ? "loading" : "idle",
161
+ unexpectedError: true
162
+ });
163
+ return {
164
+ data: null,
165
+ error: publicError,
166
+ unexpected: true
167
+ };
168
+ }
169
+ this.#setState(routeKey, {
170
+ ...latestState,
171
+ actionData: result.data,
172
+ actionError: result.error,
173
+ actionState: "idle",
174
+ navigationState: latestState.loaderState === "loading" ? "loading" : "idle",
175
+ unexpectedError: false
176
+ });
177
+ const actionResult = {
178
+ data: result.data,
179
+ error: result.error
180
+ };
181
+ const { error: revalidationError, data: shouldRevalidate } = await safeWrapAsync(() => route.shouldRevalidate?.({
182
+ actionResult: result,
183
+ defaultShouldRevalidate: Boolean(route.loader),
184
+ params
185
+ }) ?? Boolean(route.loader));
186
+ if (revalidationError) {
187
+ const publicError = "The request could not be completed.";
188
+ this.#setState(routeKey, {
189
+ ...this.#states.get(routeKey) ?? IDLE_ROUTE_STATE,
190
+ actionError: publicError,
191
+ actionState: "idle",
192
+ unexpectedError: true
193
+ });
194
+ return {
195
+ data: null,
196
+ error: publicError,
197
+ unexpected: true
198
+ };
199
+ }
200
+ if (shouldRevalidate) await this.load(routeId, params, true);
201
+ return actionResult;
202
+ }
203
+ async fetchLoader(routeId, params, signal) {
204
+ const route = this.#routes.get(routeId);
205
+ if (!route?.loader) return {
206
+ data: null,
207
+ error: "Route loader not found."
208
+ };
209
+ const { error, data: result } = await safeWrapAsync(() => route.loader?.({
210
+ params,
211
+ signal
212
+ }));
213
+ if (signal.aborted) return {
214
+ data: null,
215
+ error: ""
216
+ };
217
+ if (error || !result) return {
218
+ data: null,
219
+ error: "The route could not be loaded.",
220
+ unexpected: true
221
+ };
222
+ return result;
223
+ }
224
+ async fetchAction(routeId, input, params, signal) {
225
+ const route = this.#routes.get(routeId);
226
+ if (!route?.action) return {
227
+ data: null,
228
+ error: "Route action not found."
229
+ };
230
+ const { error, data: result } = await safeWrapAsync(() => route.action?.({
231
+ input,
232
+ params,
233
+ signal
234
+ }));
235
+ if (signal.aborted) return {
236
+ data: null,
237
+ error: ""
238
+ };
239
+ if (error || !result) return {
240
+ data: null,
241
+ error: "The request could not be completed.",
242
+ unexpected: true
243
+ };
244
+ const { error: revalidationError, data: shouldRevalidate } = await safeWrapAsync(() => route.shouldRevalidate?.({
245
+ actionResult: result,
246
+ defaultShouldRevalidate: Boolean(route.loader),
247
+ params
248
+ }) ?? Boolean(route.loader));
249
+ if (signal.aborted) return {
250
+ data: null,
251
+ error: ""
252
+ };
253
+ if (revalidationError) return {
254
+ data: null,
255
+ error: "The request could not be completed.",
256
+ unexpected: true
257
+ };
258
+ if (shouldRevalidate) await this.load(routeId, params, true);
259
+ return result;
260
+ }
261
+ retry(routeId, params) {
262
+ const routeKey = createRouteKey(routeId, params);
263
+ this.#states.set(routeKey, IDLE_ROUTE_STATE);
264
+ this.#notify(routeKey);
265
+ return this.load(routeId, params, true);
266
+ }
267
+ reportUnexpected(routeId, params) {
268
+ const routeKey = createRouteKey(routeId, params);
269
+ const currentState = this.#states.get(routeKey) ?? IDLE_ROUTE_STATE;
270
+ this.#setState(routeKey, {
271
+ ...currentState,
272
+ unexpectedError: true
273
+ });
274
+ }
275
+ async #runLoader(route, routeKey, params, controller) {
276
+ const { error, data: result } = await safeWrapAsync(() => route.loader?.({
277
+ params,
278
+ signal: controller.signal
279
+ }) ?? Promise.resolve({
280
+ data: null,
281
+ error: ""
282
+ }));
283
+ if (this.#controllers.get(routeKey) === controller) {
284
+ this.#controllers.delete(routeKey);
285
+ this.#inFlightLoads.delete(routeKey);
286
+ }
287
+ if (controller.signal.aborted) return {
288
+ data: null,
289
+ error: ""
290
+ };
291
+ if (error || !result) {
292
+ const publicError = "The route could not be loaded.";
293
+ this.#setState(routeKey, {
294
+ actionData: null,
295
+ actionError: "",
296
+ actionState: "idle",
297
+ loaderData: null,
298
+ loaderError: publicError,
299
+ loaderInitialized: true,
300
+ loaderState: "idle",
301
+ navigationState: "idle",
302
+ unexpectedError: true
303
+ });
304
+ return {
305
+ data: null,
306
+ error: publicError,
307
+ unexpected: true
308
+ };
309
+ }
310
+ const currentState = this.#states.get(routeKey) ?? IDLE_ROUTE_STATE;
311
+ this.#setState(routeKey, {
312
+ ...currentState,
313
+ loaderData: result.data,
314
+ loaderError: result.error,
315
+ loaderInitialized: true,
316
+ loaderState: "idle",
317
+ navigationState: currentState.actionState === "submitting" ? "submitting" : "idle",
318
+ unexpectedError: false
319
+ });
320
+ return result;
321
+ }
322
+ #replaceController(key) {
323
+ this.#controllers.get(key)?.abort();
324
+ const controller = new AbortController();
325
+ this.#controllers.set(key, controller);
326
+ return controller;
327
+ }
328
+ #setState(routeKey, state) {
329
+ this.#states.set(routeKey, state);
330
+ this.#notify(routeKey);
331
+ }
332
+ #notify(routeKey) {
333
+ for (const listener of this.#listeners.get(routeKey) ?? []) listener();
334
+ }
335
+ };
336
+ function createResourceRouter(routes) {
337
+ return new ResourceRouter(routes);
338
+ }
339
+ function createRouteKey(routeId, params) {
340
+ const normalizedParams = Object.entries(params).sort(([left], [right]) => left.localeCompare(right));
341
+ return `${routeId}:${JSON.stringify(normalizedParams)}`;
342
+ }
343
+ var routeCleanupGraceMs = 100;
344
+ //#endregion
345
+ export { ResourceRouter, createResourceRouter };
@@ -0,0 +1,15 @@
1
+ export interface RouteError extends Error {
2
+ status: number;
3
+ statusText: string;
4
+ data?: unknown;
5
+ }
6
+ export interface DataResponse<T> {
7
+ value: T;
8
+ init: ResponseInit;
9
+ __routeData: true;
10
+ }
11
+ export declare function data<T>(value: T, init?: ResponseInit): DataResponse<T>;
12
+ export declare function redirect(location: string, init?: number | ResponseInit): Response;
13
+ export declare function isRouteErrorResponse(value: unknown): value is RouteError;
14
+ export declare function normalizeResult(value: unknown): Promise<unknown>;
15
+ export declare function resolveData(value: unknown): Promise<unknown>;
@@ -0,0 +1,48 @@
1
+ //#region .generated/core/responses.ts
2
+ function data(value, init = {}) {
3
+ return {
4
+ value,
5
+ init,
6
+ __routeData: true
7
+ };
8
+ }
9
+ function redirect(location, init = 302) {
10
+ const options = typeof init === "number" ? { status: init } : init;
11
+ const headers = new Headers(options.headers);
12
+ headers.set("Location", location);
13
+ return new Response(null, {
14
+ ...options,
15
+ status: options.status ?? 302,
16
+ headers
17
+ });
18
+ }
19
+ function isRouteErrorResponse(value) {
20
+ return value instanceof Error && "status" in value;
21
+ }
22
+ async function normalizeResult(value) {
23
+ if (value && typeof value === "object" && "__routeError" in value) {
24
+ const error = value.__routeError;
25
+ return Object.assign(Error(error.message), error);
26
+ }
27
+ if (value instanceof Response) {
28
+ if (value.headers.has("Location")) return value;
29
+ if (!value.ok) return Object.assign(Error(await value.text()), {
30
+ status: value.status,
31
+ statusText: value.statusText
32
+ });
33
+ return value.json();
34
+ }
35
+ if (value && typeof value === "object" && "__routeData" in value) return resolveData(value.value);
36
+ return resolveData(value);
37
+ }
38
+ async function resolveData(value) {
39
+ const resolved = await value;
40
+ if (Array.isArray(resolved)) return Promise.all(resolved.map(resolveData));
41
+ if (resolved && typeof resolved === "object" && Object.getPrototypeOf(resolved) === Object.prototype) {
42
+ const entries = await Promise.all(Object.entries(resolved).map(async ([key, field]) => [key, await resolveData(field)]));
43
+ return Object.fromEntries(entries);
44
+ }
45
+ return resolved;
46
+ }
47
+ //#endregion
48
+ export { data, isRouteErrorResponse, normalizeResult, redirect, resolveData };
@@ -1,3 +1,3 @@
1
1
  import type { Route, Router, RouterOptions } from "./types.ts";
2
2
  /** Create an isolated router. Use browser: true for browser history, or loadRouter for SSR. */
3
- export declare function createRouter(routes: readonly Route[], options?: RouterOptions): Router;
3
+ export declare function createDataRouter(routes: readonly Route[], options?: RouterOptions): Router;
@@ -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 };
@@ -0,0 +1,5 @@
1
+ import type { Snapshot } from "./types.ts";
2
+ /** Safe inside a script element. Data must be JSON serializable; error stacks and causes are omitted. */
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 };