@madojs/mado 0.10.0 → 0.11.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 (219) hide show
  1. package/AGENTS.md +24 -26
  2. package/CHANGELOG.md +98 -0
  3. package/README.md +18 -45
  4. package/TODO.md +52 -48
  5. package/dist/src/component.d.ts +2 -1
  6. package/dist/src/component.js +5 -2
  7. package/dist/src/component.js.map +1 -1
  8. package/dist/src/each.d.ts +1 -1
  9. package/dist/src/each.js +1 -1
  10. package/dist/src/each.js.map +1 -1
  11. package/dist/src/html/template.js +10 -0
  12. package/dist/src/html/template.js.map +1 -1
  13. package/dist/src/index.d.ts +11 -6
  14. package/dist/src/index.js +5 -3
  15. package/dist/src/index.js.map +1 -1
  16. package/dist/src/lazy.d.ts +1 -1
  17. package/dist/src/lazy.js +1 -1
  18. package/dist/src/lazy.js.map +1 -1
  19. package/dist/src/page.d.ts +17 -21
  20. package/dist/src/page.js +7 -12
  21. package/dist/src/page.js.map +1 -1
  22. package/dist/src/router/manifest.d.ts +1 -1
  23. package/dist/src/router/manifest.js +21 -13
  24. package/dist/src/router/manifest.js.map +1 -1
  25. package/dist/src/router/match.d.ts +2 -2
  26. package/dist/src/router/match.js +3 -3
  27. package/dist/src/router/match.js.map +1 -1
  28. package/dist/src/router/navigation.js +1 -1
  29. package/dist/src/router/navigation.js.map +1 -1
  30. package/dist/src/vite/index.d.ts +10 -0
  31. package/dist/src/vite/index.js +33 -0
  32. package/dist/src/vite/index.js.map +1 -0
  33. package/docs/en/00-the-mado-way.md +25 -12
  34. package/docs/en/01-routing.md +90 -142
  35. package/docs/en/02-project-layout.md +59 -52
  36. package/docs/en/03-static-bake.md +5 -6
  37. package/docs/en/05-why-mado.md +6 -6
  38. package/docs/en/06-for-backenders.md +18 -22
  39. package/docs/en/08-llm-zero-history-test.md +9 -14
  40. package/docs/en/09-shadow-vs-light-dom.md +28 -36
  41. package/docs/en/10-app-architecture.md +158 -96
  42. package/docs/en/11-layouts.md +22 -24
  43. package/docs/en/12-auth-and-api.md +89 -182
  44. package/docs/en/13-deployment.md +25 -26
  45. package/docs/en/14-testing.md +4 -4
  46. package/docs/en/16-bake-cookbook.md +17 -10
  47. package/docs/en/18-api-freeze-map.md +6 -4
  48. package/docs/en/20-v1-stability.md +1 -1
  49. package/docs/fr/00-the-mado-way.md +55 -90
  50. package/docs/fr/01-routing.md +70 -152
  51. package/docs/fr/02-project-layout.md +74 -48
  52. package/docs/fr/03-static-bake.md +1 -1
  53. package/docs/fr/05-why-mado.md +6 -6
  54. package/docs/fr/06-for-backenders.md +7 -7
  55. package/docs/fr/08-llm-zero-history-test.md +21 -48
  56. package/docs/fr/09-shadow-vs-light-dom.md +43 -162
  57. package/docs/fr/10-app-architecture.md +110 -33
  58. package/docs/fr/11-layouts.md +24 -12
  59. package/docs/fr/12-auth-and-api.md +63 -22
  60. package/docs/fr/13-deployment.md +30 -12
  61. package/docs/fr/14-testing.md +1 -1
  62. package/docs/fr/16-bake-cookbook.md +57 -4
  63. package/docs/fr/18-api-freeze-map.md +1 -1
  64. package/docs/fr/20-v1-stability.md +1 -1
  65. package/docs/recipes/nginx/README.md +13 -0
  66. package/docs/ru/00-the-mado-way.md +53 -75
  67. package/docs/ru/01-routing.md +68 -143
  68. package/docs/ru/02-project-layout.md +75 -48
  69. package/docs/ru/03-static-bake.md +2 -2
  70. package/docs/ru/05-why-mado.md +6 -6
  71. package/docs/ru/06-for-backenders.md +7 -7
  72. package/docs/ru/08-llm-zero-history-test.md +9 -14
  73. package/docs/ru/09-shadow-vs-light-dom.md +43 -178
  74. package/docs/ru/10-app-architecture.md +115 -63
  75. package/docs/ru/11-layouts.md +24 -24
  76. package/docs/ru/12-auth-and-api.md +57 -35
  77. package/docs/ru/13-deployment.md +19 -13
  78. package/docs/ru/14-testing.md +1 -1
  79. package/docs/ru/16-bake-cookbook.md +48 -8
  80. package/docs/ru/18-api-freeze-map.md +5 -3
  81. package/docs/ru/20-v1-stability.md +1 -1
  82. package/docs/uk/00-the-mado-way.md +70 -44
  83. package/docs/uk/01-routing.md +41 -47
  84. package/docs/uk/02-project-layout.md +68 -41
  85. package/docs/uk/03-static-bake.md +1 -2
  86. package/docs/uk/06-for-backenders.md +3 -3
  87. package/docs/uk/08-llm-zero-history-test.md +22 -24
  88. package/docs/uk/09-shadow-vs-light-dom.md +37 -86
  89. package/docs/uk/10-app-architecture.md +72 -31
  90. package/docs/uk/11-layouts.md +25 -12
  91. package/docs/uk/12-auth-and-api.md +58 -22
  92. package/docs/uk/13-deployment.md +4 -3
  93. package/docs/uk/14-testing.md +1 -1
  94. package/docs/uk/18-api-freeze-map.md +1 -1
  95. package/docs/uk/20-v1-stability.md +1 -1
  96. package/llms.txt +14 -15
  97. package/package.json +18 -11
  98. package/scripts/_config.mjs +15 -161
  99. package/scripts/bake.mjs +71 -58
  100. package/scripts/cli/generate.mjs +348 -0
  101. package/scripts/cli/help.mjs +27 -0
  102. package/scripts/cli/index.mjs +79 -0
  103. package/scripts/cli/init.mjs +153 -0
  104. package/scripts/cli/release.mjs +152 -0
  105. package/scripts/cli/run.mjs +96 -0
  106. package/scripts/cli.mjs +2 -560
  107. package/scripts/package-smoke.mjs +4 -1
  108. package/scripts/preview.mjs +17 -61
  109. package/scripts/size-budget.mjs +5 -2
  110. package/scripts/vite.default.mjs +11 -0
  111. package/starters/default/.editorconfig +12 -0
  112. package/starters/default/README.md +74 -0
  113. package/starters/default/eslint.config.mjs +256 -0
  114. package/starters/default/index.html +13 -0
  115. package/starters/default/package.json +30 -0
  116. package/starters/default/public/favicon.svg +4 -0
  117. package/starters/default/src/app.routes.ts +39 -0
  118. package/starters/default/src/layouts/app-shell.layout.ts +35 -0
  119. package/starters/default/src/layouts/auth-shell.layout.ts +17 -0
  120. package/starters/default/src/main.ts +16 -0
  121. package/starters/default/src/modules/auth/_contracts/auth-api.types.ts +17 -0
  122. package/starters/default/src/modules/auth/auth.connector.ts +45 -0
  123. package/starters/default/src/modules/auth/auth.guard.ts +22 -0
  124. package/starters/default/src/modules/auth/auth.public.ts +9 -0
  125. package/starters/default/src/modules/auth/auth.routes.ts +8 -0
  126. package/starters/default/src/modules/auth/auth.service.ts +71 -0
  127. package/starters/default/src/modules/auth/auth.types.ts +15 -0
  128. package/starters/default/src/modules/auth/login.page.ts +62 -0
  129. package/starters/default/src/modules/billing/_contracts/stripe.types.ts +17 -0
  130. package/starters/default/src/modules/billing/api/stripe.connector.ts +71 -0
  131. package/starters/default/src/modules/billing/billing.public.ts +5 -0
  132. package/starters/default/src/modules/billing/billing.routes.ts +9 -0
  133. package/starters/default/src/modules/billing/billing.types.ts +15 -0
  134. package/starters/default/src/modules/billing/components/invoice-status-badge.component.ts +43 -0
  135. package/starters/default/src/modules/billing/data/invoices.resource.ts +35 -0
  136. package/starters/default/src/modules/billing/pages/invoice-detail.page.ts +70 -0
  137. package/starters/default/src/modules/billing/pages/invoices-list.page.ts +73 -0
  138. package/starters/default/src/modules/home/home.page.ts +34 -0
  139. package/starters/default/src/modules/home/not-found.page.ts +11 -0
  140. package/starters/default/src/shared/http/http-client.ts +86 -0
  141. package/starters/default/src/shared/http/http-error.ts +37 -0
  142. package/starters/default/src/shared/http/interceptors.ts +59 -0
  143. package/starters/default/src/shared/lib/format-date.ts +19 -0
  144. package/starters/default/src/shared/styles/content.css +70 -0
  145. package/starters/default/src/shared/styles/reset.css +32 -0
  146. package/starters/default/src/shared/styles/shell.css +57 -0
  147. package/starters/default/src/shared/styles/tokens.css +44 -0
  148. package/starters/default/src/shared/ui/x-button.component.ts +49 -0
  149. package/starters/default/src/shared/ui/x-spinner.component.ts +22 -0
  150. package/starters/default/src/styles.d.ts +1 -0
  151. package/starters/default/src/vite-env.d.ts +1 -0
  152. package/starters/default/tsconfig.json +24 -0
  153. package/starters/default/vite.config.ts +9 -0
  154. package/MADO_V1_PLAN.md +0 -179
  155. package/ROADMAP.md +0 -178
  156. package/dist/src/html.d.ts +0 -18
  157. package/dist/src/html.js +0 -17
  158. package/dist/src/html.js.map +0 -1
  159. package/dist/src/router.d.ts +0 -13
  160. package/dist/src/router.js +0 -13
  161. package/dist/src/router.js.map +0 -1
  162. package/scripts/bundle.mjs +0 -212
  163. package/scripts/llm-zero-history-smoke.mjs +0 -93
  164. package/scripts/new.mjs +0 -80
  165. package/scripts/showcase-regression.mjs +0 -392
  166. package/server/serve.mjs +0 -455
  167. package/starters/admin/README.md +0 -63
  168. package/starters/admin/index.html +0 -28
  169. package/starters/admin/mado.config.json +0 -22
  170. package/starters/admin/package.json +0 -24
  171. package/starters/admin/public/favicon.svg +0 -4
  172. package/starters/admin/src/components/x-button.ts +0 -82
  173. package/starters/admin/src/components/x-input.ts +0 -105
  174. package/starters/admin/src/layouts/app.ts +0 -101
  175. package/starters/admin/src/layouts/auth.ts +0 -41
  176. package/starters/admin/src/lib/api.ts +0 -184
  177. package/starters/admin/src/lib/auth.ts +0 -83
  178. package/starters/admin/src/main.ts +0 -15
  179. package/starters/admin/src/pages/admin/dashboard.ts +0 -48
  180. package/starters/admin/src/pages/admin/order-detail.ts +0 -80
  181. package/starters/admin/src/pages/admin/orders.ts +0 -117
  182. package/starters/admin/src/pages/home.ts +0 -34
  183. package/starters/admin/src/pages/login.ts +0 -70
  184. package/starters/admin/src/pages/not-found.ts +0 -12
  185. package/starters/admin/src/routes.ts +0 -40
  186. package/starters/admin/src/styles/global.ts +0 -86
  187. package/starters/admin/tsconfig.json +0 -15
  188. package/starters/crud/README.md +0 -33
  189. package/starters/crud/index.html +0 -28
  190. package/starters/crud/mado.config.json +0 -20
  191. package/starters/crud/package.json +0 -24
  192. package/starters/crud/src/components/app-shell.ts +0 -56
  193. package/starters/crud/src/components/ticket-detail.ts +0 -33
  194. package/starters/crud/src/components/ticket-form.ts +0 -69
  195. package/starters/crud/src/components/ticket-list.ts +0 -66
  196. package/starters/crud/src/lib/api.ts +0 -76
  197. package/starters/crud/src/main.ts +0 -9
  198. package/starters/crud/src/pages/home.ts +0 -34
  199. package/starters/crud/src/pages/not-found.ts +0 -12
  200. package/starters/crud/src/pages/ticket-detail.ts +0 -7
  201. package/starters/crud/src/pages/ticket-new.ts +0 -7
  202. package/starters/crud/src/pages/tickets.ts +0 -7
  203. package/starters/crud/src/routes.ts +0 -11
  204. package/starters/crud/src/styles/global.ts +0 -155
  205. package/starters/crud/tsconfig.json +0 -15
  206. package/starters/minimal/README.md +0 -21
  207. package/starters/minimal/index.html +0 -28
  208. package/starters/minimal/mado.config.json +0 -20
  209. package/starters/minimal/package.json +0 -24
  210. package/starters/minimal/src/components/app-counter.ts +0 -31
  211. package/starters/minimal/src/main.ts +0 -9
  212. package/starters/minimal/src/pages/home.ts +0 -35
  213. package/starters/minimal/src/pages/not-found.ts +0 -14
  214. package/starters/minimal/src/routes.ts +0 -8
  215. package/starters/minimal/src/styles/global.ts +0 -60
  216. package/starters/minimal/tsconfig.json +0 -15
  217. package/templates/page-detail.ts +0 -63
  218. package/templates/page-form.ts +0 -94
  219. package/templates/page-list.ts +0 -79
@@ -1,217 +1,124 @@
1
1
  # Auth and API
2
2
 
3
- > Mado has **zero runtime dependencies**, but that does not mean every team
4
- > should reinvent its auth and HTTP boundary. This page is the blessed recipe.
5
- > Copy it into your project, change the URLs and field names to match your
6
- > backend, and stop touching it.
3
+ The default starter is the blessed recipe. It keeps HTTP mechanics in
4
+ `src/shared/http/` and auth state in `src/modules/auth/`.
5
+
6
+ ## Shape
7
+
8
+ ```txt
9
+ src/shared/http/
10
+ http-client.ts # one fetch wrapper + query/body/error handling
11
+ http-error.ts # one error shape
12
+ interceptors.ts # request/response hooks
13
+
14
+ src/modules/auth/
15
+ auth.connector.ts # /api/auth wire contract, DTO -> domain
16
+ auth.service.ts # token/user signals + login/logout/init
17
+ auth.guard.ts # requireAuth(), requirePermission()
18
+ auth.routes.ts # login route map
19
+ auth.public.ts # only public auth surface
20
+ _contracts/ # backend DTOs, private to connector
21
+ ```
7
22
 
8
- The `admin` starter (`mado init my-app --starter admin`) ships with these
9
- files pre-installed in `src/lib/`:
23
+ Every business module follows the same flow:
10
24
 
11
- - `api.ts` — `createApiClient(baseUrl)` + `accessToken` signal + `ApiError`
12
- - `auth.ts` `restoreSession()`, `login()`, `logout()`, `requireAuth` guard
25
+ ```txt
26
+ connector -> resource/mutation -> page
27
+ ```
13
28
 
14
- The complete code is roughly 100 lines. Read it and own it.
29
+ Pages do not import DTOs. Pages do not call `fetch()` directly. Connectors do
30
+ not import Mado reactivity or UI.
15
31
 
16
- ## Mental model
32
+ ## HTTP Client
17
33
 
18
- - One **API boundary** (`api()`): every fetch in your app goes through it.
19
- - One **memory-only access token** (`accessToken` signal): never in
20
- `localStorage`. Renewed silently from an HttpOnly refresh cookie when needed.
21
- - One **route guard** (`requireAuth`): plug it into the layout block that
22
- wraps protected routes. The guard runs before the page is rendered.
34
+ Use one small HTTP client for the app. It owns:
23
35
 
24
- That is the entire surface.
36
+ - base URL handling;
37
+ - JSON request/response defaults;
38
+ - query string serialization;
39
+ - `HttpError`;
40
+ - request/response interceptors.
25
41
 
26
- ## `src/lib/api.ts`
42
+ Module connectors build on it:
27
43
 
28
44
  ```ts
29
- import { signal } from "@madojs/mado";
45
+ import { httpClient } from "../../shared/http/http-client";
46
+ import type { User } from "./auth.types";
47
+ import type { LoginResponseDTO } from "./_contracts/auth-api.types";
48
+
49
+ const toUser = (dto: LoginResponseDTO["user"]): User => ({
50
+ id: dto.id,
51
+ email: dto.email,
52
+ roles: dto.roles,
53
+ permissions: dto.permissions,
54
+ });
55
+
56
+ export const authApi = {
57
+ me: async () => toUser(await httpClient.get<LoginResponseDTO["user"]>("/api/auth/me")),
58
+ };
59
+ ```
30
60
 
31
- export const accessToken = signal<string | null>(null);
61
+ ## Auth Service
32
62
 
33
- export class ApiError extends Error {
34
- constructor(public status: number, public body: unknown, message: string) {
35
- super(message);
36
- this.name = "ApiError";
37
- }
38
- }
63
+ Auth state is an ES module singleton:
39
64
 
40
- export interface ApiInit extends Omit<RequestInit, "body"> {
41
- json?: unknown;
42
- baseUrl?: string;
43
- }
65
+ ```ts
66
+ const _user = signal<User | null>(null);
67
+ const _token = signal<string | null>(null);
44
68
 
45
- export function createApiClient(baseUrl: string) {
46
- let refreshing: Promise<boolean> | null = null;
47
-
48
- async function refresh(): Promise<boolean> {
49
- if (refreshing) return refreshing;
50
- refreshing = (async () => {
51
- try {
52
- const res = await fetch(new URL("/auth/refresh", baseUrl), {
53
- method: "POST",
54
- credentials: "include",
55
- });
56
- if (!res.ok) return false;
57
- const data = (await res.json().catch(() => null)) as
58
- | { accessToken?: string } | null;
59
- if (!data?.accessToken) return false;
60
- accessToken.set(data.accessToken);
61
- return true;
62
- } catch {
63
- return false;
64
- } finally {
65
- refreshing = null;
66
- }
67
- })();
68
- return refreshing;
69
- }
70
-
71
- return async function api<T>(path: string, init: ApiInit = {}): Promise<T> {
72
- const url = new URL(path, init.baseUrl ?? baseUrl);
73
- const headers = new Headers(init.headers);
74
- if (init.json !== undefined && !headers.has("content-type")) {
75
- headers.set("content-type", "application/json");
76
- }
77
- const token = accessToken();
78
- if (token) headers.set("authorization", `Bearer ${token}`);
79
-
80
- const res = await fetch(url, {
81
- ...init,
82
- headers,
83
- credentials: init.credentials ?? "include",
84
- body: init.json !== undefined ? JSON.stringify(init.json) : (init as RequestInit).body,
85
- });
86
-
87
- if (res.status === 401) {
88
- if (await refresh()) return api<T>(path, init);
89
- accessToken.set(null);
90
- throw new ApiError(401, null, "Unauthorized");
91
- }
92
- if (!res.ok) {
93
- const body = await res.json().catch(() => null);
94
- throw new ApiError(res.status, body, `HTTP ${res.status} ${res.statusText}`);
95
- }
96
- if (res.status === 204) return null as unknown as T;
97
- return (await res.json()) as T;
98
- };
99
- }
69
+ export const user = () => _user();
70
+ export const isAuthed = computed(() => _user() !== null);
100
71
 
101
- export const api = createApiClient("/api");
72
+ export async function login(creds: Credentials): Promise<void> {
73
+ const res = await authApi.login(creds);
74
+ _token.set(res.token);
75
+ _user.set(res.user);
76
+ }
102
77
  ```
103
78
 
104
- Key invariants:
79
+ Expose only what other modules need through `auth.public.ts`.
105
80
 
106
- - **Bearer token in memory only.** A page reload destroys it; `restoreSession()`
107
- brings it back from the refresh cookie.
108
- - **Refresh is single-flight.** Five resources hitting 401 at the same time
109
- trigger exactly one refresh request.
110
- - **Errors are typed.** Catch `ApiError` for `.status` and `.body`.
111
- - **`credentials: include`** is the default, because the refresh cookie is
112
- cross-host-safe only with `include`.
81
+ ## Guards
113
82
 
114
- ## `src/lib/auth.ts`
83
+ Guards are plain functions:
115
84
 
116
85
  ```ts
117
- import type { Guard } from "@madojs/mado";
118
- import { accessToken, api, ApiError } from "./api.js";
119
-
120
- let restorePromise: Promise<boolean> | null = null;
121
-
122
- export async function restoreSession(): Promise<boolean> {
123
- if (accessToken()) return true;
124
- if (restorePromise) return restorePromise;
125
- restorePromise = (async () => {
126
- try {
127
- const data = await api<{ accessToken: string }>("/auth/refresh", {
128
- method: "POST",
129
- });
130
- accessToken.set(data.accessToken);
131
- return true;
132
- } catch (e) {
133
- if (e instanceof ApiError && e.status === 401) return false;
134
- return false;
135
- } finally {
136
- restorePromise = null;
137
- }
138
- })();
139
- return restorePromise;
140
- }
141
-
142
- export const requireAuth: Guard = async ({ path }) => {
143
- if (accessToken()) return;
144
- if (await restoreSession()) return;
145
- return { redirect: `/login?return=${encodeURIComponent(path)}`, replace: true };
146
- };
147
-
148
- export async function login(creds: { email: string; password: string }) {
149
- const data = await api<{ accessToken: string }>("/auth/login", {
150
- method: "POST",
151
- json: creds,
152
- });
153
- accessToken.set(data.accessToken);
154
- }
155
-
156
- export async function logout() {
157
- try { await api("/auth/logout", { method: "POST" }); } catch {}
158
- accessToken.set(null);
86
+ export function requireAuth(): boolean | string {
87
+ if (isAuthed()) return true;
88
+ return "/login";
159
89
  }
160
90
  ```
161
91
 
162
- Drop `requireAuth` into your manifest:
92
+ Use them in `src/app.routes.ts`:
163
93
 
164
94
  ```ts
165
- "/admin": layout({
166
- layout: () => import("./layouts/app.js"),
167
- guard: requireAuth, // ← entire group is now protected
168
- routes: { ... },
95
+ "/billing": layout({
96
+ layout: () => import("./layouts/app-shell.layout"),
97
+ guard: requireAuth,
98
+ routes: billingRoutes,
169
99
  }),
170
100
  ```
171
101
 
172
- The guard runs *before* the page is rendered. If the user is not signed in
173
- and the refresh cookie cannot revive the session, they are redirected to
174
- `/login?return=<original>`. After a successful sign-in, the login page reads
175
- `return` and navigates back.
176
-
177
- ## Backend contract
178
-
179
- The recipe assumes three endpoints. Adjust paths to taste:
180
-
181
- | Endpoint | Request | Response (200) | Notes |
182
- |-------------------------|---------------------------|------------------------------|--------------------------------|
183
- | `POST /api/auth/login` | `{ email, password }` | `{ accessToken }` | Sets HttpOnly refresh cookie |
184
- | `POST /api/auth/refresh`| (no body, cookie only) | `{ accessToken }` | Reads HttpOnly refresh cookie |
185
- | `POST /api/auth/logout` | (no body) | `204` | Clears the refresh cookie |
102
+ ## Dev Proxy
186
103
 
187
- If your backend uses a different shape (`{ token }`, `{ access_token, expires_in }`,
188
- etc.), change `api.ts` and `auth.ts` in two places each. The rest of the app
189
- keeps working.
104
+ Configure proxying in `vite.config.ts`:
190
105
 
191
- ## Dev proxy
192
-
193
- In development, point `/api/*` at your backend with `mado.config.json`:
194
-
195
- ```jsonc
196
- {
197
- "dev": {
198
- "port": 5173,
199
- "proxy": { "/api": "http://localhost:3000" }
200
- }
201
- }
106
+ ```ts
107
+ import { defineConfig } from "vite";
108
+ import { mado } from "@madojs/mado/vite";
109
+
110
+ export default defineConfig({
111
+ plugins: [mado()],
112
+ server: {
113
+ proxy: { "/api": "http://localhost:3000" },
114
+ },
115
+ });
202
116
  ```
203
117
 
204
- The dev server forwards requests under `/api/*` to your backend, so both the
205
- SPA and the API can be reached from the same origin — no CORS dance during
206
- development.
207
-
208
- ## When to deviate
209
-
210
- - **SPA + cookies only** (no Bearer tokens). Remove the `authorization`
211
- header and the `refresh()` retry; rely entirely on a session cookie.
212
- - **Public site with optional auth.** Make `restoreSession()` opportunistic
213
- on startup and skip the `requireAuth` guard.
214
- - **Third-party API tokens** that cannot be refreshed. Drop `refresh()` and
215
- fail loudly on 401.
118
+ ## Rule of Thumb
216
119
 
217
- Whatever you change, change it **in `api.ts` only**. Pages stay innocent.
120
+ - `shared/http` knows HTTP.
121
+ - `*.connector.ts` knows one external system.
122
+ - `*.resource.ts` knows cache keys and invalidation.
123
+ - `*.page.ts` knows UI.
124
+ - `*.public.ts` is the only cross-module surface.
@@ -8,13 +8,12 @@
8
8
 
9
9
  ```
10
10
  out/
11
- ├── index.html ← SPA shell (loads the bundle + boots the router)
12
- ├── assets/ ← hashed bundles (main-ABC.js, chunk-XYZ.js, …)
11
+ ├── index.html ← SPA shell or baked HTML for /
12
+ ├── assets/ ← Vite hashed assets
13
13
  │ ├── *.gz ← precompressed gzip (gzip_static / Accept-Encoding)
14
14
  │ └── *.br ← precompressed brotli (brotli_static / Accept-Encoding)
15
- ├── baked/ ← prerendered SEO HTML (mado bake)
16
- ├── <route>/index.html
17
- │ └── sitemap.xml
15
+ ├── <route>/index.html ← prerendered SEO HTML for baked routes
16
+ ├── sitemap.xml ← generated sitemap
18
17
  ├── favicon.svg ← your public/ assets copied verbatim
19
18
  ├── _redirects ← Cloudflare Pages / Netlify SPA fallback
20
19
  └── _headers ← Cloudflare Pages / Netlify cache rules
@@ -31,17 +30,18 @@ mado release
31
30
  mado preview # http://localhost:4173 — serves out/ exactly as a static host would
32
31
  ```
33
32
 
34
- `mado preview` mirrors the behavior described below: it picks `.br` over
35
- `.gz` over raw, prefers baked HTML at `/<route>/`, and falls back to
36
- `index.html` for unknown paths.
33
+ `mado preview` serves the final `out/` directory like a static host: it picks
34
+ `.br` over `.gz` over raw, serves promoted baked HTML when a route has an
35
+ `index.html`, and falls back to `index.html` for unknown SPA paths.
37
36
 
38
37
  ---
39
38
 
40
39
  ## Recipe 1: VPS + nginx
41
40
 
42
- The framework ships a production-ready [`nginx.conf`](../../nginx.conf) with
43
- `gzip_static`, immutable cache for hashed bundles, and SPA fallback. Drop
44
- `out/` into the host and point nginx at it.
41
+ The framework ships an optional nginx recipe in
42
+ [`docs/recipes/nginx`](../recipes/nginx/): `nginx.conf` for static hosting and a
43
+ `Containerfile` you can copy into generated apps. Drop `out/` into the host and
44
+ point nginx at it.
45
45
 
46
46
  ```bash
47
47
  # Build the artifact locally
@@ -52,16 +52,15 @@ rsync -avz --delete out/ user@server:/var/www/myapp/
52
52
 
53
53
  # On the VPS — first time only:
54
54
  sudo cp /etc/nginx/conf.d/myapp.conf{,.bak}
55
- sudo cp ./nginx.conf /etc/nginx/conf.d/myapp.conf
55
+ sudo cp ./docs/recipes/nginx/nginx.conf /etc/nginx/conf.d/myapp.conf
56
56
  sudo nginx -t && sudo systemctl reload nginx
57
57
  ```
58
58
 
59
- Key lines of the shipped `nginx.conf`:
59
+ Key lines of the recipe `nginx.conf`:
60
60
 
61
61
  - `gzip_static on;` — serves the precompressed `.gz` files written by
62
- `mado bundle`. Zero CPU at request time.
63
- - `location ~* "^/(main|chunk|asset)-[A-Z0-9]+\.js$" { immutable; }`
64
- hashed bundles get a one-year cache.
62
+ `mado release`. Zero CPU at request time.
63
+ - `/assets/*` should be cached immutable; Vite filenames are content hashed.
65
64
  - `try_files $uri $uri/ /index.html;` — SPA fallback so deep links work
66
65
  after a hard refresh.
67
66
 
@@ -79,8 +78,9 @@ npx wrangler pages deploy out --project-name=myapp
79
78
  - The generated `_redirects` (`/* /index.html 200`) gives you SPA fallback.
80
79
  - The generated `_headers` (immutable cache for `/assets/*`, `no-cache` for
81
80
  HTML) is honored by CF Pages.
82
- - Baked routes (`out/baked/<route>/index.html`) take priority over the SPA
83
- fallback because CF Pages matches static files first.
81
+ - Baked routes are promoted to real route files (`out/<route>/index.html`),
82
+ so they take priority over the SPA fallback because CF Pages matches static
83
+ files first.
84
84
 
85
85
  For preview branches, set the same build command in the CF Pages project:
86
86
 
@@ -89,9 +89,8 @@ Build command: npm ci && npx mado release
89
89
  Output directory: out
90
90
  ```
91
91
 
92
- There is also a small **edge prerender PoC** in
93
- [`examples/cloudflare`](../../examples/cloudflare/) for catalogs too big to
94
- bake at build time.
92
+ For catalogs too big to bake at build time, keep edge prerender experiments in
93
+ the external examples workspace rather than in the core package.
95
94
 
96
95
  ---
97
96
 
@@ -138,7 +137,7 @@ add a `404.html` that loads the SPA, or use the
138
137
  | Other static files | `public, max-age=86400` | safe daily cache |
139
138
 
140
139
  `mado release` writes these rules into `out/_headers` for CF / Netlify and
141
- the shipped `nginx.conf` enforces them server-side.
140
+ the nginx recipe enforces them server-side.
142
141
 
143
142
  ---
144
143
 
@@ -181,12 +180,12 @@ jobs:
181
180
  `Cache-Control: public, max-age=...` or you are sitting behind a CDN that
182
181
  ignores `no-cache`. Add an explicit rule mirroring the matrix above.
183
182
  - **`/assets/*` files change but the browser keeps the old one.** They
184
- should not — the filename is hashed by `mado bundle`. If you bypassed
185
- bundle and shipped your own `dist/main.js`, give it a hash or short cache.
183
+ should not — the filename is hashed by Vite during `mado release`. If you bypassed
184
+ build and shipped your own unhashed JS, give it a hash or short cache.
186
185
  - **Baked SEO page shows `[object Object]`.** Should never happen after the
187
186
  v1 bake update — bake now raises a loud error in that case. If you see it,
188
187
  upgrade `@madojs/mado` and re-run `mado bake`.
189
188
 
190
189
  See also: [`02-project-layout.md`](./02-project-layout.md) for the
191
- `src/`/`dist/`/`public/`/`out/` model and [`03-static-bake.md`](./03-static-bake.md)
192
- for the SEO bake mechanics.
190
+ `src/`/`public/`/`out/` model and [`03-static-bake.md`](./03-static-bake.md)
191
+ for the SEO bake mechanics.
@@ -27,7 +27,7 @@ globalThis.document = window.document;
27
27
  globalThis.Node = window.Node;
28
28
  globalThis.HTMLElement = window.HTMLElement;
29
29
 
30
- const { html, render } = await import("../dist/src/html.js");
30
+ const { html, render } = await import("../dist/src/html/template.js");
31
31
 
32
32
  test("renders a value", () => {
33
33
  const root = document.createElement("div");
@@ -74,9 +74,9 @@ services from framework tests. For app tests, make the API client injectable via
74
74
  npm run typecheck
75
75
  npm run build
76
76
  npm test
77
- npm run bundle
78
77
  npm run bake
79
78
  ```
80
79
 
81
- `mado release` runs the production path for an app. In the framework repository,
82
- the lower-level commands remain useful when debugging a single stage.
80
+ `mado release` runs the production path for an app: typecheck, Vite build,
81
+ bake, compression and deploy helper files. In the framework repository,
82
+ `npm run build` still emits `dist/src` for package tests and publishing.
@@ -55,8 +55,9 @@ export default routes(manifest);
55
55
 
56
56
  ## Output
57
57
 
58
- By default app-mode writes baked pages under `out/baked/`. `mado release`
59
- produces the final deploy artifact:
58
+ By default standalone `mado bake` writes baked pages directly into `out/`.
59
+ `mado release` first lets Vite build the production shell and assets, then bake
60
+ replaces route HTML in place and writes `out/sitemap.xml`:
60
61
 
61
62
  ```bash
62
63
  mado release
@@ -65,6 +66,15 @@ tree out
65
66
 
66
67
  The deployable folder is `out/`, not `dist/`.
67
68
 
69
+ Use `mado release --keep-bake-dir` only when you want an extra `out/baked/`
70
+ inspection copy during debugging.
71
+
72
+ ## Client boot
73
+
74
+ Baked HTML marks `#app` with `data-mado-baked`. This is not hydration: when the
75
+ client app starts, `render()` replaces the baked DOM with live bindings. The
76
+ first response contains SEO/first-paint HTML, then the SPA takes over normally.
77
+
68
78
  ## Unsupported values
69
79
 
70
80
  Bake intentionally fails loudly instead of writing `[object Object]`. If a baked
@@ -76,15 +86,12 @@ view throws an unsupported directive error:
76
86
 
77
87
  ## Canonical links
78
88
 
79
- Pass `--base-url` or set `bake.baseUrl` in `mado.config.json` so generated
80
- canonical links and sitemap entries point to production.
89
+ Pass `--base-url` so generated canonical links and sitemap entries point to
90
+ production.
81
91
 
82
- ```json
83
- {
84
- "bake": {
85
- "baseUrl": "https://example.com"
86
- }
87
- }
92
+ ```bash
93
+ mado bake --base-url https://example.com
94
+ mado release --base-url https://example.com
88
95
  ```
89
96
 
90
97
  ## When not to bake
@@ -9,10 +9,12 @@ package root:
9
9
  import { component, html, resource, routes, signal } from "@madojs/mado";
10
10
  ```
11
11
 
12
- The only public package subpath is the side-effect devtools module:
12
+ The public package subpaths are the side-effect devtools module and the Vite
13
+ tooling integration:
13
14
 
14
15
  ```ts
15
16
  import "@madojs/mado/devtools.js";
17
+ import { mado } from "@madojs/mado/vite";
16
18
  ```
17
19
 
18
20
  Everything else under `dist/src/` is an implementation detail, even when it is
@@ -27,7 +29,7 @@ These names are public and protected by SemVer once v1 ships:
27
29
  - Templates and directives: `html`, `render`, `each`, `list`, `unsafeHTML`,
28
30
  `ref`, `classMap`, `styleMap`.
29
31
  - Components and CSS: `component`, `css`, `cssVars`.
30
- - Routing and pages: `routes`, `router`, `page`, `layout`, `nested`,
32
+ - Routing and pages: `routes`, `router`, `page`, `layout`,
31
33
  `navigate`, `queryParam`, `prefetchPath`.
32
34
  - Data: `resource`, `mutation`, `invalidate`, `jsonFetcher`, `HttpError`.
33
35
  - Forms: `useForm`.
@@ -41,8 +43,8 @@ These names are public and protected by SemVer once v1 ships:
41
43
 
42
44
  These are not public API:
43
45
 
44
- - Package subpaths other than `@madojs/mado` and
45
- `@madojs/mado/devtools.js`.
46
+ - Package subpaths other than `@madojs/mado`, `@madojs/mado/devtools.js`,
47
+ and `@madojs/mado/vite`.
46
48
  - Template parser/binding internals such as `html/parser.js`,
47
49
  `html/bindings.js`, `ChildState`, and `EachEntry`.
48
50
  - Router implementation modules such as `router/match.js`,
@@ -25,7 +25,7 @@ After v1, Mado treats these as SemVer-protected:
25
25
  teardown for same-tick moves, cleanup via `ctx.onDispose`.
26
26
  - Router/page/resource/form contracts documented in the English docs.
27
27
  - CLI command names and broad command intent (`build`, `dev`, `release`,
28
- `bake`, `bundle`, `preview`, `init`, `new`).
28
+ `bake`, `preview`, `init`, `new`).
29
29
 
30
30
  Breaking these requires a major version.
31
31