@madojs/mado 0.10.1 → 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 (217) hide show
  1. package/AGENTS.md +24 -26
  2. package/CHANGELOG.md +68 -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/index.d.ts +11 -6
  12. package/dist/src/index.js +5 -3
  13. package/dist/src/index.js.map +1 -1
  14. package/dist/src/lazy.d.ts +1 -1
  15. package/dist/src/lazy.js +1 -1
  16. package/dist/src/lazy.js.map +1 -1
  17. package/dist/src/page.d.ts +17 -21
  18. package/dist/src/page.js +7 -12
  19. package/dist/src/page.js.map +1 -1
  20. package/dist/src/router/manifest.d.ts +1 -1
  21. package/dist/src/router/manifest.js +21 -13
  22. package/dist/src/router/manifest.js.map +1 -1
  23. package/dist/src/router/match.d.ts +2 -2
  24. package/dist/src/router/match.js +3 -3
  25. package/dist/src/router/match.js.map +1 -1
  26. package/dist/src/router/navigation.js +1 -1
  27. package/dist/src/router/navigation.js.map +1 -1
  28. package/dist/src/vite/index.d.ts +10 -0
  29. package/dist/src/vite/index.js +33 -0
  30. package/dist/src/vite/index.js.map +1 -0
  31. package/docs/en/00-the-mado-way.md +25 -12
  32. package/docs/en/01-routing.md +90 -142
  33. package/docs/en/02-project-layout.md +59 -53
  34. package/docs/en/03-static-bake.md +5 -6
  35. package/docs/en/05-why-mado.md +6 -6
  36. package/docs/en/06-for-backenders.md +18 -22
  37. package/docs/en/08-llm-zero-history-test.md +9 -14
  38. package/docs/en/09-shadow-vs-light-dom.md +28 -36
  39. package/docs/en/10-app-architecture.md +158 -96
  40. package/docs/en/11-layouts.md +22 -24
  41. package/docs/en/12-auth-and-api.md +89 -182
  42. package/docs/en/13-deployment.md +18 -22
  43. package/docs/en/14-testing.md +4 -4
  44. package/docs/en/16-bake-cookbook.md +11 -12
  45. package/docs/en/18-api-freeze-map.md +6 -4
  46. package/docs/en/20-v1-stability.md +1 -1
  47. package/docs/fr/00-the-mado-way.md +55 -90
  48. package/docs/fr/01-routing.md +70 -152
  49. package/docs/fr/02-project-layout.md +61 -42
  50. package/docs/fr/03-static-bake.md +1 -1
  51. package/docs/fr/05-why-mado.md +6 -6
  52. package/docs/fr/06-for-backenders.md +7 -7
  53. package/docs/fr/08-llm-zero-history-test.md +21 -48
  54. package/docs/fr/09-shadow-vs-light-dom.md +43 -162
  55. package/docs/fr/10-app-architecture.md +110 -33
  56. package/docs/fr/11-layouts.md +24 -12
  57. package/docs/fr/12-auth-and-api.md +63 -22
  58. package/docs/fr/13-deployment.md +7 -10
  59. package/docs/fr/14-testing.md +1 -1
  60. package/docs/fr/16-bake-cookbook.md +2 -2
  61. package/docs/fr/18-api-freeze-map.md +1 -1
  62. package/docs/fr/20-v1-stability.md +1 -1
  63. package/docs/recipes/nginx/README.md +13 -0
  64. package/docs/ru/00-the-mado-way.md +53 -75
  65. package/docs/ru/01-routing.md +68 -143
  66. package/docs/ru/02-project-layout.md +61 -41
  67. package/docs/ru/03-static-bake.md +2 -2
  68. package/docs/ru/05-why-mado.md +6 -6
  69. package/docs/ru/06-for-backenders.md +7 -7
  70. package/docs/ru/08-llm-zero-history-test.md +9 -14
  71. package/docs/ru/09-shadow-vs-light-dom.md +43 -178
  72. package/docs/ru/10-app-architecture.md +115 -63
  73. package/docs/ru/11-layouts.md +24 -24
  74. package/docs/ru/12-auth-and-api.md +57 -35
  75. package/docs/ru/13-deployment.md +7 -11
  76. package/docs/ru/14-testing.md +1 -1
  77. package/docs/ru/16-bake-cookbook.md +12 -6
  78. package/docs/ru/18-api-freeze-map.md +5 -3
  79. package/docs/ru/20-v1-stability.md +1 -1
  80. package/docs/uk/00-the-mado-way.md +70 -44
  81. package/docs/uk/01-routing.md +41 -47
  82. package/docs/uk/02-project-layout.md +68 -41
  83. package/docs/uk/03-static-bake.md +1 -2
  84. package/docs/uk/06-for-backenders.md +3 -3
  85. package/docs/uk/08-llm-zero-history-test.md +22 -24
  86. package/docs/uk/09-shadow-vs-light-dom.md +37 -86
  87. package/docs/uk/10-app-architecture.md +72 -31
  88. package/docs/uk/11-layouts.md +25 -12
  89. package/docs/uk/12-auth-and-api.md +58 -22
  90. package/docs/uk/13-deployment.md +4 -3
  91. package/docs/uk/14-testing.md +1 -1
  92. package/docs/uk/18-api-freeze-map.md +1 -1
  93. package/docs/uk/20-v1-stability.md +1 -1
  94. package/llms.txt +14 -15
  95. package/package.json +18 -11
  96. package/scripts/_config.mjs +15 -161
  97. package/scripts/bake.mjs +67 -57
  98. package/scripts/cli/generate.mjs +348 -0
  99. package/scripts/cli/help.mjs +27 -0
  100. package/scripts/cli/index.mjs +79 -0
  101. package/scripts/cli/init.mjs +153 -0
  102. package/scripts/cli/release.mjs +152 -0
  103. package/scripts/cli/run.mjs +96 -0
  104. package/scripts/cli.mjs +2 -621
  105. package/scripts/package-smoke.mjs +4 -1
  106. package/scripts/preview.mjs +13 -37
  107. package/scripts/size-budget.mjs +5 -2
  108. package/scripts/vite.default.mjs +11 -0
  109. package/starters/default/.editorconfig +12 -0
  110. package/starters/default/README.md +74 -0
  111. package/starters/default/eslint.config.mjs +256 -0
  112. package/starters/default/index.html +13 -0
  113. package/starters/default/package.json +30 -0
  114. package/starters/default/public/favicon.svg +4 -0
  115. package/starters/default/src/app.routes.ts +39 -0
  116. package/starters/default/src/layouts/app-shell.layout.ts +35 -0
  117. package/starters/default/src/layouts/auth-shell.layout.ts +17 -0
  118. package/starters/default/src/main.ts +16 -0
  119. package/starters/default/src/modules/auth/_contracts/auth-api.types.ts +17 -0
  120. package/starters/default/src/modules/auth/auth.connector.ts +45 -0
  121. package/starters/default/src/modules/auth/auth.guard.ts +22 -0
  122. package/starters/default/src/modules/auth/auth.public.ts +9 -0
  123. package/starters/default/src/modules/auth/auth.routes.ts +8 -0
  124. package/starters/default/src/modules/auth/auth.service.ts +71 -0
  125. package/starters/default/src/modules/auth/auth.types.ts +15 -0
  126. package/starters/default/src/modules/auth/login.page.ts +62 -0
  127. package/starters/default/src/modules/billing/_contracts/stripe.types.ts +17 -0
  128. package/starters/default/src/modules/billing/api/stripe.connector.ts +71 -0
  129. package/starters/default/src/modules/billing/billing.public.ts +5 -0
  130. package/starters/default/src/modules/billing/billing.routes.ts +9 -0
  131. package/starters/default/src/modules/billing/billing.types.ts +15 -0
  132. package/starters/default/src/modules/billing/components/invoice-status-badge.component.ts +43 -0
  133. package/starters/default/src/modules/billing/data/invoices.resource.ts +35 -0
  134. package/starters/default/src/modules/billing/pages/invoice-detail.page.ts +70 -0
  135. package/starters/default/src/modules/billing/pages/invoices-list.page.ts +73 -0
  136. package/starters/default/src/modules/home/home.page.ts +34 -0
  137. package/starters/default/src/modules/home/not-found.page.ts +11 -0
  138. package/starters/default/src/shared/http/http-client.ts +86 -0
  139. package/starters/default/src/shared/http/http-error.ts +37 -0
  140. package/starters/default/src/shared/http/interceptors.ts +59 -0
  141. package/starters/default/src/shared/lib/format-date.ts +19 -0
  142. package/starters/default/src/shared/styles/content.css +70 -0
  143. package/starters/default/src/shared/styles/reset.css +32 -0
  144. package/starters/default/src/shared/styles/shell.css +57 -0
  145. package/starters/default/src/shared/styles/tokens.css +44 -0
  146. package/starters/default/src/shared/ui/x-button.component.ts +49 -0
  147. package/starters/default/src/shared/ui/x-spinner.component.ts +22 -0
  148. package/starters/default/src/styles.d.ts +1 -0
  149. package/starters/default/src/vite-env.d.ts +1 -0
  150. package/starters/default/tsconfig.json +24 -0
  151. package/starters/default/vite.config.ts +9 -0
  152. package/MADO_V1_PLAN.md +0 -179
  153. package/ROADMAP.md +0 -178
  154. package/dist/src/html.d.ts +0 -18
  155. package/dist/src/html.js +0 -17
  156. package/dist/src/html.js.map +0 -1
  157. package/dist/src/router.d.ts +0 -13
  158. package/dist/src/router.js +0 -13
  159. package/dist/src/router.js.map +0 -1
  160. package/scripts/bundle.mjs +0 -212
  161. package/scripts/llm-zero-history-smoke.mjs +0 -93
  162. package/scripts/new.mjs +0 -80
  163. package/scripts/showcase-regression.mjs +0 -392
  164. package/server/serve.mjs +0 -455
  165. package/starters/admin/README.md +0 -63
  166. package/starters/admin/index.html +0 -28
  167. package/starters/admin/mado.config.json +0 -22
  168. package/starters/admin/package.json +0 -24
  169. package/starters/admin/public/favicon.svg +0 -4
  170. package/starters/admin/src/components/x-button.ts +0 -82
  171. package/starters/admin/src/components/x-input.ts +0 -105
  172. package/starters/admin/src/layouts/app.ts +0 -101
  173. package/starters/admin/src/layouts/auth.ts +0 -41
  174. package/starters/admin/src/lib/api.ts +0 -184
  175. package/starters/admin/src/lib/auth.ts +0 -83
  176. package/starters/admin/src/main.ts +0 -15
  177. package/starters/admin/src/pages/admin/dashboard.ts +0 -48
  178. package/starters/admin/src/pages/admin/order-detail.ts +0 -80
  179. package/starters/admin/src/pages/admin/orders.ts +0 -117
  180. package/starters/admin/src/pages/home.ts +0 -34
  181. package/starters/admin/src/pages/login.ts +0 -70
  182. package/starters/admin/src/pages/not-found.ts +0 -12
  183. package/starters/admin/src/routes.ts +0 -40
  184. package/starters/admin/src/styles/global.ts +0 -86
  185. package/starters/admin/tsconfig.json +0 -15
  186. package/starters/crud/README.md +0 -33
  187. package/starters/crud/index.html +0 -28
  188. package/starters/crud/mado.config.json +0 -20
  189. package/starters/crud/package.json +0 -24
  190. package/starters/crud/src/components/app-shell.ts +0 -56
  191. package/starters/crud/src/components/ticket-detail.ts +0 -33
  192. package/starters/crud/src/components/ticket-form.ts +0 -69
  193. package/starters/crud/src/components/ticket-list.ts +0 -66
  194. package/starters/crud/src/lib/api.ts +0 -76
  195. package/starters/crud/src/main.ts +0 -9
  196. package/starters/crud/src/pages/home.ts +0 -34
  197. package/starters/crud/src/pages/not-found.ts +0 -12
  198. package/starters/crud/src/pages/ticket-detail.ts +0 -7
  199. package/starters/crud/src/pages/ticket-new.ts +0 -7
  200. package/starters/crud/src/pages/tickets.ts +0 -7
  201. package/starters/crud/src/routes.ts +0 -11
  202. package/starters/crud/src/styles/global.ts +0 -155
  203. package/starters/crud/tsconfig.json +0 -15
  204. package/starters/minimal/README.md +0 -21
  205. package/starters/minimal/index.html +0 -28
  206. package/starters/minimal/mado.config.json +0 -20
  207. package/starters/minimal/package.json +0 -24
  208. package/starters/minimal/src/components/app-counter.ts +0 -31
  209. package/starters/minimal/src/main.ts +0 -9
  210. package/starters/minimal/src/pages/home.ts +0 -35
  211. package/starters/minimal/src/pages/not-found.ts +0 -14
  212. package/starters/minimal/src/routes.ts +0 -8
  213. package/starters/minimal/src/styles/global.ts +0 -60
  214. package/starters/minimal/tsconfig.json +0 -15
  215. package/templates/page-detail.ts +0 -63
  216. package/templates/page-form.ts +0 -94
  217. 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,15 +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/ bake output copy for inspection/debugging
16
- ├── <route>/index.html
17
- │ └── sitemap.xml
18
- ├── <route>/index.html ← prerendered SEO HTML promoted for static hosts
19
- ├── sitemap.xml ← sitemap promoted to the site root
15
+ ├── <route>/index.html prerendered SEO HTML for baked routes
16
+ ├── sitemap.xml ← generated sitemap
20
17
  ├── favicon.svg ← your public/ assets copied verbatim
21
18
  ├── _redirects ← Cloudflare Pages / Netlify SPA fallback
22
19
  └── _headers ← Cloudflare Pages / Netlify cache rules
@@ -41,9 +38,10 @@ mado preview # http://localhost:4173 — serves out/ exactly as a static ho
41
38
 
42
39
  ## Recipe 1: VPS + nginx
43
40
 
44
- The framework ships a production-ready [`nginx.conf`](../../nginx.conf) with
45
- `gzip_static`, immutable cache for hashed bundles, and SPA fallback. Drop
46
- `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.
47
45
 
48
46
  ```bash
49
47
  # Build the artifact locally
@@ -54,16 +52,15 @@ rsync -avz --delete out/ user@server:/var/www/myapp/
54
52
 
55
53
  # On the VPS — first time only:
56
54
  sudo cp /etc/nginx/conf.d/myapp.conf{,.bak}
57
- sudo cp ./nginx.conf /etc/nginx/conf.d/myapp.conf
55
+ sudo cp ./docs/recipes/nginx/nginx.conf /etc/nginx/conf.d/myapp.conf
58
56
  sudo nginx -t && sudo systemctl reload nginx
59
57
  ```
60
58
 
61
- Key lines of the shipped `nginx.conf`:
59
+ Key lines of the recipe `nginx.conf`:
62
60
 
63
61
  - `gzip_static on;` — serves the precompressed `.gz` files written by
64
- `mado bundle`. Zero CPU at request time.
65
- - `location ~* "^/(main|chunk|asset)-[A-Z0-9]+\.js$" { immutable; }`
66
- 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.
67
64
  - `try_files $uri $uri/ /index.html;` — SPA fallback so deep links work
68
65
  after a hard refresh.
69
66
 
@@ -92,9 +89,8 @@ Build command: npm ci && npx mado release
92
89
  Output directory: out
93
90
  ```
94
91
 
95
- There is also a small **edge prerender PoC** in
96
- [`examples/cloudflare`](../../examples/cloudflare/) for catalogs too big to
97
- 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.
98
94
 
99
95
  ---
100
96
 
@@ -141,7 +137,7 @@ add a `404.html` that loads the SPA, or use the
141
137
  | Other static files | `public, max-age=86400` | safe daily cache |
142
138
 
143
139
  `mado release` writes these rules into `out/_headers` for CF / Netlify and
144
- the shipped `nginx.conf` enforces them server-side.
140
+ the nginx recipe enforces them server-side.
145
141
 
146
142
  ---
147
143
 
@@ -184,12 +180,12 @@ jobs:
184
180
  `Cache-Control: public, max-age=...` or you are sitting behind a CDN that
185
181
  ignores `no-cache`. Add an explicit rule mirroring the matrix above.
186
182
  - **`/assets/*` files change but the browser keeps the old one.** They
187
- should not — the filename is hashed by `mado bundle`. If you bypassed
188
- 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.
189
185
  - **Baked SEO page shows `[object Object]`.** Should never happen after the
190
186
  v1 bake update — bake now raises a loud error in that case. If you see it,
191
187
  upgrade `@madojs/mado` and re-run `mado bake`.
192
188
 
193
189
  See also: [`02-project-layout.md`](./02-project-layout.md) for the
194
- `src/`/`dist/`/`public/`/`out/` model and [`03-static-bake.md`](./03-static-bake.md)
190
+ `src/`/`public/`/`out/` model and [`03-static-bake.md`](./03-static-bake.md)
195
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,10 +55,9 @@ export default routes(manifest);
55
55
 
56
56
  ## Output
57
57
 
58
- By default standalone `mado bake` writes baked pages under `out/baked/`.
59
- `mado release` uses the bundled production shell, keeps that `out/baked/` copy
60
- for inspection, promotes baked HTML into real route paths inside `out/`, and
61
- copies the generated sitemap to `out/sitemap.xml`:
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`:
62
61
 
63
62
  ```bash
64
63
  mado release
@@ -67,6 +66,9 @@ tree out
67
66
 
68
67
  The deployable folder is `out/`, not `dist/`.
69
68
 
69
+ Use `mado release --keep-bake-dir` only when you want an extra `out/baked/`
70
+ inspection copy during debugging.
71
+
70
72
  ## Client boot
71
73
 
72
74
  Baked HTML marks `#app` with `data-mado-baked`. This is not hydration: when the
@@ -84,15 +86,12 @@ view throws an unsupported directive error:
84
86
 
85
87
  ## Canonical links
86
88
 
87
- Pass `--base-url` or set `bake.baseUrl` in `mado.config.json` so generated
88
- canonical links and sitemap entries point to production.
89
+ Pass `--base-url` so generated canonical links and sitemap entries point to
90
+ production.
89
91
 
90
- ```json
91
- {
92
- "bake": {
93
- "baseUrl": "https://example.com"
94
- }
95
- }
92
+ ```bash
93
+ mado bake --base-url https://example.com
94
+ mado release --base-url https://example.com
96
95
  ```
97
96
 
98
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