@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,35 +1,76 @@
1
1
  # Auth et API
2
2
 
3
- Le starter `admin` contient la recette recommandée :
3
+ Le starter par défaut est la recette recommandée. La mécanique HTTP vit dans
4
+ `src/shared/http/`, l'état auth dans `src/modules/auth/`.
4
5
 
5
- - `src/lib/api.ts` — un seul client HTTP, `ApiError`, refresh après 401 ;
6
- - `src/lib/auth.ts` — `accessToken`, `restoreSession()`, `login()`,
7
- `logout()`, `requireAuth`.
6
+ ```txt
7
+ src/shared/http/
8
+ http-client.ts
9
+ http-error.ts
10
+ interceptors.ts
8
11
 
9
- Modèle :
12
+ src/modules/auth/
13
+ auth.connector.ts
14
+ auth.service.ts
15
+ auth.guard.ts
16
+ auth.routes.ts
17
+ auth.public.ts
18
+ _contracts/
19
+ ```
20
+
21
+ Flow pour un business module :
22
+
23
+ ```txt
24
+ connector -> resource/mutation -> page
25
+ ```
26
+
27
+ Les pages n'importent pas de DTOs et n'appellent pas `fetch()` directement. Les
28
+ connectors n'importent pas la réactivité Mado ou l'UI.
10
29
 
11
- - access token en mémoire via `signal`, pas dans `localStorage` ;
12
- - refresh cookie HttpOnly pour restaurer la session ;
13
- - toutes les requêtes passent par le client API ;
14
- - les routes protégées utilisent un group guard.
30
+ ## Auth Service
31
+
32
+ Auth state est un ES module singleton :
15
33
 
16
34
  ```ts
17
- export const requireAuth: Guard = async ({ path }) => {
18
- if (accessToken()) return;
19
- if (await restoreSession()) return;
20
- return { redirect: `/login?return=${encodeURIComponent(path)}`, replace: true };
21
- };
35
+ const _user = signal<User | null>(null);
36
+ const _token = signal<string | null>(null);
37
+
38
+ export const user = () => _user();
39
+ export const isAuthed = computed(() => _user() !== null);
22
40
  ```
23
41
 
24
- Dev proxy :
42
+ Exposez seulement la surface nécessaire via `auth.public.ts`.
25
43
 
26
- ```jsonc
27
- {
28
- "dev": {
29
- "proxy": { "/api": "http://localhost:3000" }
30
- }
44
+ ## Guards
45
+
46
+ ```ts
47
+ export function requireAuth(): boolean | string {
48
+ if (isAuthed()) return true;
49
+ return "/login";
31
50
  }
32
51
  ```
33
52
 
34
- Si ton backend a une autre forme, modifie `api.ts` et `auth.ts`. Les pages ne
35
- doivent pas connaître les détails d'authentification.
53
+ Use in `src/app.routes.ts`:
54
+
55
+ ```ts
56
+ "/billing": layout({
57
+ layout: () => import("./layouts/app-shell.layout"),
58
+ guard: requireAuth,
59
+ routes: billingRoutes,
60
+ }),
61
+ ```
62
+
63
+ ## Dev Proxy
64
+
65
+ ```ts
66
+ export default defineConfig({
67
+ plugins: [mado()],
68
+ server: {
69
+ proxy: { "/api": "http://localhost:3000" },
70
+ },
71
+ });
72
+ ```
73
+
74
+ Rule: `shared/http` connaît HTTP, connectors connaissent un système externe,
75
+ resources connaissent les cache keys, pages connaissent l'UI, `*.public.ts` est
76
+ la surface cross-module.
@@ -10,14 +10,11 @@ Résultat :
10
10
 
11
11
  ```txt
12
12
  out/
13
- ├── index.html ← shell SPA ou HTML baked promu pour /
14
- ├── assets/ ← bundles hashés (main-ABC.js, chunk-XYZ.js, ...)
13
+ ├── index.html ← shell SPA ou HTML baked pour /
14
+ ├── assets/ ← assets Vite hashés
15
15
  │ ├── *.gz ← gzip précompressé
16
16
  │ └── *.br ← brotli précompressé
17
- ├── baked/ copie de bake pour inspection/debugging
18
- │ ├── <route>/index.html
19
- │ └── sitemap.xml
20
- ├── <route>/index.html ← HTML baked promu pour les hébergeurs statiques
17
+ ├── <route>/index.html HTML baked pour les hébergeurs statiques
21
18
  ├── sitemap.xml ← sitemap à la racine du site
22
19
  ├── _redirects ← fallback SPA Cloudflare Pages / Netlify
23
20
  └── _headers ← règles de cache
@@ -35,8 +32,7 @@ mado preview
35
32
 
36
33
  `mado preview` sert le `out/` final comme un hébergeur statique : fichiers réels
37
34
  d'abord (`/<route>/index.html` si la route est baked), fallback SPA ensuite.
38
- Preview ne fait plus de mapping virtuel depuis `out/baked/`, donc il vérifie
39
- exactement ce qui sera déployé.
35
+ Preview vérifie exactement ce qui sera déployé.
40
36
 
41
37
  ## VPS + nginx
42
38
 
@@ -45,8 +41,9 @@ mado release
45
41
  rsync -avz --delete out/ user@server:/var/www/myapp/
46
42
  ```
47
43
 
48
- Le `nginx.conf` fourni gère le cache immutable pour les bundles hashés, no-cache
49
- pour HTML, et le fallback SPA pour les deep links.
44
+ La recette nginx optionnelle vit dans `docs/recipes/nginx/`. Elle gère le cache
45
+ immutable pour `/assets/*`, no-cache pour HTML, et le fallback SPA pour les deep
46
+ links.
50
47
 
51
48
  ## Cloudflare / Netlify
52
49
 
@@ -22,7 +22,7 @@ globalThis.document = window.document;
22
22
  globalThis.Node = window.Node;
23
23
  globalThis.HTMLElement = window.HTMLElement;
24
24
 
25
- const { html, render } = await import("../dist/src/html.js");
25
+ const { html, render } = await import("../dist/src/html/template.js");
26
26
 
27
27
  test("renders", () => {
28
28
  const root = document.createElement("div");
@@ -84,5 +84,5 @@ signale une directive non supportée :
84
84
 
85
85
  ## Canonical Links
86
86
 
87
- Passe `--base-url` ou configure `bake.baseUrl` dans `mado.config.json` pour que
88
- les liens canonical et le sitemap pointent vers la production.
87
+ Passe `--base-url` pour que les liens canonical et le sitemap pointent vers la
88
+ production.
@@ -27,7 +27,7 @@ Ces noms sont publics et protégés par SemVer une fois v1 publiée :
27
27
  - Templates et directives : `html`, `render`, `each`, `list`, `unsafeHTML`,
28
28
  `ref`, `classMap`, `styleMap`.
29
29
  - Composants et CSS : `component`, `css`, `cssVars`.
30
- - Routage et pages : `routes`, `router`, `page`, `layout`, `nested`,
30
+ - Routage et pages : `routes`, `router`, `page`, `layout`,
31
31
  `navigate`, `queryParam`, `prefetchPath`.
32
32
  - Data : `resource`, `mutation`, `invalidate`, `jsonFetcher`, `HttpError`.
33
33
  - Formulaires : `useForm`.
@@ -26,7 +26,7 @@ Après v1, Mado considère comme protégés par SemVer :
26
26
  `ctx.onDispose`.
27
27
  - Les contrats router/page/resource/form documentés dans les docs anglaises.
28
28
  - Les noms de commandes CLI et leur intention générale (`build`, `dev`,
29
- `release`, `bake`, `bundle`, `preview`, `init`, `new`).
29
+ `release`, `bake`, `preview`, `init`, `new`).
30
30
 
31
31
  Casser cela nécessite une version majeure.
32
32
 
@@ -0,0 +1,13 @@
1
+ # Nginx Container Recipe
2
+
3
+ Optional static deployment recipe for a generated Mado app.
4
+
5
+ Copy this directory into your app as `docker/`, then build from the app root:
6
+
7
+ ```bash
8
+ docker build -f docker/Containerfile -t myapp .
9
+ docker run --rm -p 8080:80 myapp
10
+ ```
11
+
12
+ The container runs `npm run release` and serves the resulting `out/` directory
13
+ with nginx. This is a recipe, not a framework runtime requirement.
@@ -2,105 +2,83 @@
2
2
 
3
3
  > Один правильный путь. Жёсткие контракты. Никакой магии.
4
4
 
5
- Mado — фреймворк для команд, которые строят админки, внутренние инструменты
6
- и бизнес-SPA приложения, которые должны быть просты в разработке и скучны
7
- в поддержке. Для этого он задаёт **набор соглашений**. Если ты следуешь им,
8
- проект остаётся понятным даже когда в нём 200 экранов и 5 разработчиков. Если
9
- нарушаешь — типы и линтер скажут об этом сразу.
5
+ Mado — фреймворк для команд, которые строят админки, внутренние инструменты и
6
+ business SPA. Такие приложения должны быть простыми в разработке и скучными в
7
+ поддержке. Поэтому Mado задает соглашения, а не предлагает пять равноценных
8
+ стилей.
10
9
 
11
10
  ## Принципы
12
11
 
13
- 1. **Один способ.** Для каждой задачи — один правильный путь, не пять. Если ты пишешь странное спроси себя, не существует ли уже идиоматичный хелпер.
14
- 2. **Явность над магией.** Никаких сканеров файлов, неявных глобалов, скрытых side-effects. Всё, что делает фреймворк, можно прочитать в одном файле.
15
- 3. **Платформа сначала.** Если в браузере уже есть фича — используем её напрямую. Своих абстракций над `fetch`, `<form>`, History API, Shadow DOM не плодим.
16
- 4. **Жёсткие типы.** `tsc --strict --noUncheckedIndexedAccess` всегда. Если что-то не типизируется — это сигнал что API кривой.
17
- 5. **Никаких рантайм-зависимостей.** Любая зависимость обязательство на годы; экосистема Web Components этого не требует.
12
+ 1. **Один способ.** Если пишешь что-то необычное, сначала проверь, нет ли уже
13
+ каноничного helper/API.
14
+ 2. **Явность над магией.** Никаких file-system scanners, implicit globals и
15
+ скрытых side effects.
16
+ 3. **Платформа сначала.** Web Components, History API, `<form>`, `fetch` и
17
+ Shadow DOM остаются платформой, а не прячутся под тяжелыми абстракциями.
18
+ 4. **Strict types.** `tsc --strict --noUncheckedIndexedAccess` всегда.
19
+ 5. **No runtime dependencies.** Dev/build tooling допустим, runtime Mado
20
+ остается нативным.
18
21
 
19
- ## Соглашения
22
+ ## Структура проекта
20
23
 
21
- ### Структура проекта
22
-
23
- ```
24
+ ```txt
24
25
  src/
25
- ├── routes.ts манифест маршрутов, один файл на проект
26
- ├── main.ts точка входа: провайдеры + монтаж <x-app>
27
- ├── pages/ одна страница = один файл = `export default page({...})`
28
- ├── components/ переиспользуемые компоненты, side-effect-регистрация
29
- ├── lib/ контексты, API-клиенты, бизнес-логика без UI
30
- └── styles/ ← общие стили (если нужны), .ts с css``
31
- ```
32
-
33
- Это **обязательно**, не "по желанию". Если у проекта будет 10 разработчиков — они все должны писать одинаково.
34
-
35
- ### Один компонент = один файл
36
-
37
- ```ts
38
- // src/components/user-card.ts
39
- import { component, html, css } from "@madojs/mado";
40
-
41
- component(
42
- "x-user-card",
43
- () => {
44
- return () => html`<div class="card"><slot /></div>`;
45
- },
46
- {
47
- styles: css`
48
- .card {
49
- padding: 1rem;
50
- }
51
- `,
52
- },
53
- );
26
+ ├── main.ts boot: global CSS/providers + render router
27
+ ├── app.routes.ts readable app map, exports `manifest` + default routes()
28
+ ├── layouts/ app-zone wrappers (`page({ view: ({ child }) => ... })`)
29
+ ├── shared/ UI bricks, http client, pure lib, global CSS
30
+ └── modules/ bounded contexts
31
+ └── billing/
32
+ ├── billing.routes.ts
33
+ ├── billing.public.ts
34
+ ├── billing.types.ts
35
+ ├── pages/
36
+ ├── data/
37
+ ├── api/
38
+ └── _contracts/
54
39
  ```
55
40
 
56
- Импорт `import './components/user-card.js'` **регистрирует** компонент через `customElements.define`. Это side-effect. Где компонент нужен — там и импортируем.
57
-
58
- ### Один способ загрузки данных
41
+ Default starter каноничная версия этой формы. Если docs и старые примеры
42
+ расходятся, starter и `docs/10-app-architecture.md` главнее.
59
43
 
60
- Не зовём `fetch()` напрямую из компонента. Всегда через:
44
+ ## Один компонент = один файл
61
45
 
62
46
  ```ts
63
- // чтение resource
64
- const user = resource(() => `/api/users/${id()}`, jsonFetcher());
47
+ import { component, css, html } from "@madojs/mado";
65
48
 
66
- // запись mutation
67
- const save = mutation(api.save, { invalidates: ["/api/users*"] });
49
+ component("x-user-card", () => () => html`<div class="card"><slot></slot></div>`, {
50
+ styles: css`
51
+ .card { padding: 1rem; }
52
+ `,
53
+ });
68
54
  ```
69
55
 
70
- Это даёт кеш, отмену, обработку ошибок, авто-инвалидацию.
56
+ Import component file registers the element. Import it where the tag is used.
71
57
 
72
- ### Один способ описать страницу
58
+ ## Один способ описать страницу
73
59
 
74
60
  ```ts
75
- // src/pages/user-profile.ts
76
- import { page, html, resource, jsonFetcher } from "@madojs/mado";
61
+ import { html, page, resource, jsonFetcher } from "@madojs/mado";
77
62
 
78
63
  export default page({
79
64
  title: ({ id }) => `User #${id}`,
80
- view: ({ params }) => html`...`,
65
+ view: ({ params }) => {
66
+ const user = resource(() => `/api/users/${params.id}`, jsonFetcher());
67
+ return html`...`;
68
+ },
81
69
  });
82
70
  ```
83
71
 
84
- Три слота `title`, `load`, `view`. Других нет. Хочешь что-то ещё — это уже компонент или хелпер.
85
-
86
- ### Один способ объявить роуты
87
-
88
- См. [`01-routing.md`](./01-routing.md).
72
+ Page-local signals, resources and forms live inside `view()`. Module-wide state
73
+ belongs in `*.service.ts`.
89
74
 
90
75
  ## Чего НЕ делаем
91
76
 
92
- - Не пишем компоненты без дефиса. Это правило браузера для custom elements: `user-card` ок, `usercard` нет.
93
- - `x-*` только convention для примеров и тестов Mado, не брендовый стандарт. В production лучше брать префикс домена: `app-*`, `crm-*`, `ticket-*`, `admin-*`.
94
- - Не используем `innerHTML` напрямую. Только через `html\`\``.
95
- - Не вызываем `setTimeout`/`setInterval` без cleanup. Только внутри `effect()`.
96
- - Не храним глобальный мутируемый state. Используем сигналы и `context`.
97
- - ❌ Не подключаем pkg без обсуждения. Каждая зависимость — обязательство.
98
-
99
- ## Когда сомневаешься
100
-
101
- Если ты задаёшься вопросом "а как тут лучше?" — это сигнал, что:
102
-
103
- 1. Либо есть встроенный хелпер, который ты не знаешь (загляни в `docs/`).
104
- 2. Либо это новая ситуация — её надо обсудить и **зафиксировать** в этом документе как ещё одно соглашение.
77
+ - Не используем JSX/Vue/Svelte syntax.
78
+ - Не пишем custom elements без дефиса.
79
+ - Не читаем signals через `.value`; signal читается как function.
80
+ - Не используем `innerHTML` напрямую.
81
+ - Не добавляем runtime packages без обсуждения.
105
82
 
106
- «Лучше единый-окей, чем разный-идеальный.»
83
+ Когда сомневаешься, лучше записать один честный рецепт в docs, чем добавить
84
+ новый primitive в core.
@@ -1,194 +1,119 @@
1
1
  # Routing
2
2
 
3
- > Один файл-манифест. Никаких сканеров папок. Никаких спецсимволов.
3
+ > Один app map. Никаких folder scanners. Никаких специальных path symbols.
4
4
 
5
- ## Зачем не file-based
5
+ Mado не выводит routes из файлов. Composition должна читаться в одном месте.
6
6
 
7
- В Next/SvelteKit/SolidStart роут возникает «магически» по имени файла. У этого есть плюсы (видно структуру URL по `pages/`), но в проде это означает:
7
+ ## App Manifest
8
8
 
9
- - Невидимый плагин-сканер в билде. Без него файлы — просто файлы.
10
- - Спецсимволы в путях: `[id]`, `(group)`, `_layout`, `+page.svelte`, `...slug`.
11
- - Server-route vs client-route путаются.
12
- - Тестировать роутинг — мука: нужен эмулятор сборщика.
13
-
14
- Mado считает это **слишком магией**. Мы делаем иначе.
15
-
16
- ## Манифест
17
-
18
- Один файл — `src/routes.ts`. В нём один объект. Читается сверху вниз.
9
+ Используйте `src/app.routes.ts` как карту приложения:
19
10
 
20
11
  ```ts
21
- // src/routes.ts
22
- import { routes } from '@madojs/mado';
23
-
24
- export default routes({
25
- '/': () => import('./pages/home.js'),
26
- '/about': () => import('./pages/about.js'),
27
- '/users/:id': () => import('./pages/user-profile.js'),
28
- '/users/:id/edit':() => import('./pages/user-edit.js'),
29
- '*': () => import('./pages/not-found.js'),
30
- });
12
+ import { layout, routes } from "@madojs/mado";
13
+ import { requireAuth } from "./modules/auth/auth.public";
14
+ import { authRoutes } from "./modules/auth/auth.routes";
15
+ import { billingRoutes } from "./modules/billing/billing.routes";
16
+
17
+ export const manifest = {
18
+ "/": () => import("./modules/home/home.page.js"),
19
+ "/login": layout({
20
+ layout: () => import("./layouts/auth-shell.layout.js"),
21
+ routes: authRoutes,
22
+ }),
23
+ "/billing": layout({
24
+ layout: () => import("./layouts/app-shell.layout.js"),
25
+ guard: requireAuth,
26
+ routes: billingRoutes,
27
+ }),
28
+ "*": () => import("./modules/home/not-found.page.js"),
29
+ };
30
+
31
+ export default routes(manifest);
31
32
  ```
32
33
 
33
- Хочешь увидеть все роуты? Открой `routes.ts`. Никаких сюрпризов.
34
+ Открываешь `app.routes.ts` и видишь все зоны приложения: public pages, auth,
35
+ protected app zones, guards и shells.
34
36
 
35
- ## Что справа от пути
37
+ `manifest` экспортируется отдельно, чтобы `mado bake` мог его прочитать.
36
38
 
37
- Любая запись — это **одно из трёх**:
39
+ ## Module Routes
38
40
 
39
- ### 1. Lazy import (рекомендовано)
41
+ Modules экспортируют plain route maps. Они не вызывают `layout()` и не решают,
42
+ какой shell их оборачивает.
40
43
 
41
44
  ```ts
42
- '/posts': () => import('./pages/posts.js'),
45
+ export const billingRoutes = {
46
+ "/invoices": () => import("./pages/invoices-list.page.js"),
47
+ "/invoices/:id": () => import("./pages/invoice-detail.page.js"),
48
+ };
43
49
  ```
44
50
 
45
- - Браузер сам сделает chunk при бандлинге (esbuild --bundle --splitting).
46
- - Модуль загрузится только при заходе на роут.
47
- - Между навигациями результат кэшируется.
51
+ Prefix применяет `src/app.routes.ts`, когда module монтируется под
52
+ `"/billing"`.
48
53
 
49
- ### 2. Готовая Page (eager)
54
+ ## Layout Group
50
55
 
51
56
  ```ts
52
- import about from './pages/about.js';
53
-
54
- '/about': about,
57
+ "/admin": layout({
58
+ layout: () => import("./layouts/app-shell.layout.js"),
59
+ guard: requireAuth,
60
+ routes: adminRoutes,
61
+ }),
55
62
  ```
56
63
 
57
- Сразу в графе, без задержек. Используй для критичных страниц (home, login).
58
-
59
- ### 3. Nested с layout
64
+ Layout обычный `page({...})` file:
60
65
 
61
66
  ```ts
62
- import { routes, nested } from '@madojs/mado';
63
-
64
- export default routes({
65
- '/': () => import('./pages/home.js'),
66
-
67
- '/admin/*': nested({
68
- layout: () => import('./layouts/admin.js'),
69
- routes: {
70
- '': () => import('./pages/admin/dashboard.js'),
71
- 'users': () => import('./pages/admin/users.js'),
72
- 'logs': () => import('./pages/admin/logs.js'),
73
- },
74
- }),
75
- });
76
- ```
77
-
78
- Layout — это **обычный** `page({...})`, который рендерит `ctx.child` куда хочет:
79
-
80
- ```ts
81
- // src/layouts/admin.ts
82
- import { page, html, css, component } from '@madojs/mado';
67
+ import { html, page } from "@madojs/mado";
83
68
 
84
69
  export default page({
85
70
  view: ({ child }) => html`
86
- <div class="admin">
87
- <aside><nav>...</nav></aside>
88
- <main>${child}</main>
71
+ <div class="layout layout--app">
72
+ <main class="app-main">${child}</main>
89
73
  </div>
90
74
  `,
91
75
  });
92
76
  ```
93
77
 
94
- ## Контракт страницы
95
-
96
- ```ts
97
- import { page, html, resource, jsonFetcher } from '@madojs/mado';
98
-
99
- export default page({
100
- title: ({ id }) => `User #${id}`, // string | (params) => string
101
- load: ({ id }) => resource(...), // опц., возвращает Resource или данные
102
- view: ({ params, data, path, child }) => html`...`, // ОБЯЗАТЕЛЬНО
103
- });
104
- ```
105
-
106
- Три слота, всё. Если ты экспортируешь не `page({...})`, а просто функцию — `routes()` кинет понятную ошибку:
107
-
108
- ```
109
- [Mado] Lazy-роут не вернул page({...}) как default-экспорт.
110
- ```
78
+ Layout view держим stateless. Page-specific signals, resources и forms живут в
79
+ pages/components/resources, не в layout locals.
111
80
 
112
- ## Параметры URL
81
+ ## Page Contract
113
82
 
114
83
  ```ts
115
- '/users/:id': () => import('./pages/user.js'),
116
- ```
84
+ import { html, page } from "@madojs/mado";
117
85
 
118
- ```ts
119
86
  export default page<{ id: string }>({
120
87
  title: ({ id }) => `User ${id}`,
121
- view: ({ params }) => html`<h1>${params.id}</h1>`,
88
+ view: ({ params }) => html`<h1>${params.id}</h1>`,
122
89
  });
123
90
  ```
124
91
 
125
- Типы передаются в `page<Params>` — `tsc` проверит что вы не обратились к `params.foo`, которого нет в роуте.
126
-
127
- ## Глобальные опции
128
-
129
- ```ts
130
- export default routes(
131
- { '/': home, '/about': about, '*': nf },
132
- {
133
- titleSuffix: ' · MyApp', // → "Главная · MyApp"
134
- loading: () => html`<x-spinner/>`, // пока модуль грузится
135
- error: (err) => html`<x-fatal-error .err=${err}/>`,
136
- },
137
- );
138
- ```
139
-
140
- ## Программная навигация
92
+ ## Navigation
141
93
 
142
94
  ```ts
143
- import route from './routes.js';
95
+ import appRoutes from "./app.routes.js";
144
96
 
145
- route.navigate('/posts');
146
- route.navigate('/posts?page=2');
147
- route.navigate('/posts', { replace: true });
97
+ appRoutes.navigate("/billing/invoices");
98
+ appRoutes.navigate("/billing/invoices?page=2");
99
+ appRoutes.navigate("/login", { replace: true });
148
100
  ```
149
101
 
150
- Клики по `<a href="/foo" data-link>` перехватываются глобально (без атрибута — браузер сделает full reload, как и положено для внешних ссылок).
151
-
152
- ## Query-параметры
102
+ ## Query Parameters
153
103
 
154
104
  ```ts
155
- import { queryParam } from '@madojs/mado';
156
-
157
- const page = queryParam('page', '1');
158
- page(); // '1'
159
- page.set('2'); // history.replaceState + перерисовка
160
- page.set(null); // удалить параметр
161
- page.set('3', { push: true }); // history.pushState
162
- ```
163
-
164
- `queryParam` — обычный сигнал. Использовать можно где угодно: в страницах, компонентах, computed.
165
-
166
- ## Что осознанно отсутствует
105
+ import { queryParam } from "@madojs/mado";
167
106
 
168
- - Авто-сканирование `pages/`. **Один файл-манифест явный**.
169
- - ❌ Спецсимволы в путях (`[id]`, `(group)`, `_layout`). **Параметры — только `:name`, ничего больше**.
170
- - ❌ Server-side роутинг в этом же манифесте. Mado — клиентский фреймворк.
171
- - ❌ Auto-prefetch при наведении. Если очень нужно — можно сделать вручную: `link.addEventListener('mouseenter', loader)`. Но обычно лишнее.
172
-
173
- ## FAQ
174
-
175
- **А если у меня 100 роутов? Не разрастётся ли файл?**
176
- Разрастётся до ~150 строк. Это всё ещё **один источник правды** против сотни файлов в `pages/` с магическими именами. На практике даже у больших проектов (1000+ страниц) можно бить на feature-манифесты:
177
-
178
- ```ts
179
- import { routes } from '@madojs/mado';
180
- import adminRoutes from './features/admin/routes.js';
181
- import billingRoutes from './features/billing/routes.js';
182
-
183
- export default routes({
184
- ...adminRoutes,
185
- ...billingRoutes,
186
- '*': () => import('./pages/not-found.js'),
187
- });
107
+ const page = queryParam("page", "1");
108
+ page();
109
+ page.set("2");
110
+ page.set(null);
111
+ page.set("3", { push: true });
188
112
  ```
189
113
 
190
- **Как тестировать роутинг?**
191
- Импортируешь `routes.ts` — это просто объект. Подставляешь свой mock-router. Никакой эмуляции сборщика не нужно.
114
+ ## Чего нет намеренно
192
115
 
193
- **Code splitting работает?**
194
- Да. При `esbuild --bundle --splitting --format=esm` каждый `() => import('./pages/x.js')` становится отдельным chunk'ом.
116
+ - Auto-scan of page folders.
117
+ - Filesystem syntax вроде `[id]`, `(group)`, `_layout`.
118
+ - Server routes в client manifest.
119
+ - Hidden layout discovery.