@madojs/mado 0.10.1 → 0.11.1

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 +95 -0
  3. package/README.md +22 -47
  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/bindings.js +3 -3
  12. package/dist/src/html/bindings.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 -53
  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 +18 -22
  45. package/docs/en/14-testing.md +4 -4
  46. package/docs/en/16-bake-cookbook.md +11 -12
  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 +61 -42
  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 +7 -10
  61. package/docs/fr/14-testing.md +1 -1
  62. package/docs/fr/16-bake-cookbook.md +2 -2
  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 +61 -41
  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 +7 -11
  78. package/docs/ru/14-testing.md +1 -1
  79. package/docs/ru/16-bake-cookbook.md +12 -6
  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 +74 -63
  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 -621
  107. package/scripts/package-smoke.mjs +4 -1
  108. package/scripts/preview.mjs +13 -37
  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
@@ -3,11 +3,10 @@
3
3
  // mado preview
4
4
  //
5
5
  // What it does:
6
- // 1. Reads `mado.config.json` to discover OUT (default `out/`) and PORT.
6
+ // 1. Serves OUT_DIR or `out/`.
7
7
  // 2. If `out/` is missing AND we are in a project root, refuses to run and
8
- // points the user at `mado release`. (Old auto-build behavior is opt-in
9
- // via PREVIEW_AUTOBUILD=1 to stay backward-compatible for the framework
10
- // repo.)
8
+ // points the user at `mado release`. Auto-build is opt-in via
9
+ // PREVIEW_AUTOBUILD=1.
11
10
  // 3. Starts a static server with:
12
11
  // - immutable cache for hashed bundles;
13
12
  // - SPA fallback to index.html;
@@ -22,8 +21,6 @@ import { readFile, stat, access } from "node:fs/promises";
22
21
  import { extname, join, resolve, sep } from "node:path";
23
22
  import { spawnSync } from "node:child_process";
24
23
 
25
- import { loadConfig } from "./_config.mjs";
26
-
27
24
  // Tiny argv parser. Supports --flag, --flag=value, --flag value.
28
25
  function parsePreviewArgs(argv) {
29
26
  const flags = {};
@@ -51,22 +48,10 @@ function parsePreviewArgs(argv) {
51
48
 
52
49
  const PREVIEW_FLAGS = parsePreviewArgs(process.argv.slice(2));
53
50
 
54
- const cfg = loadConfig({});
55
- const ROOT = cfg.projectRoot;
56
- const OUT = resolve(
57
- ROOT,
58
- process.env.OUT_DIR ?? cfg.build.out ?? "out",
59
- );
60
- // Baked HTML lives in <out>/baked/ by default (see scripts/bake.mjs and
61
- // mado.config.json bake.outDir). `mado release` promotes those HTML files into
62
- // real route paths inside <out>/, so preview can serve exactly what a static
63
- // host sees instead of applying a preview-only virtual mapping.
64
- const BAKED = resolve(
65
- ROOT,
66
- process.env.BAKED_DIR ?? cfg.bake?.outDir ?? join(cfg.build.out ?? "out", "baked"),
67
- );
68
- const PORT = Number(PREVIEW_FLAGS.port ?? process.env.PORT ?? cfg.dev?.port ?? 4173);
69
- const HOST = String(PREVIEW_FLAGS.host ?? process.env.HOST ?? cfg.dev?.host ?? "localhost");
51
+ const ROOT = resolve(process.cwd());
52
+ const OUT = resolve(ROOT, process.env.OUT_DIR ?? "out");
53
+ const PORT = Number(PREVIEW_FLAGS.port ?? process.env.PORT ?? 4173);
54
+ const HOST = String(PREVIEW_FLAGS.host ?? process.env.HOST ?? "localhost");
70
55
  const AUTOBUILD = process.env.PREVIEW_AUTOBUILD === "1";
71
56
  const SKIP_BUILD = process.env.SKIP_BUILD === "1" || !AUTOBUILD;
72
57
 
@@ -87,23 +72,16 @@ const MIME = {
87
72
  ".map": "application/json; charset=utf-8",
88
73
  };
89
74
 
90
- // ---------- 1-3) Full build ----------
75
+ // ---------- Optional release ----------
91
76
 
92
77
  if (!SKIP_BUILD) {
93
- console.log("[preview] step 1/3tsc");
94
- run("npx", ["tsc"]);
95
-
96
- // bake is optional (only when there are pages with bake config)
97
- console.log("[preview] step 2/3 — bake (optional)");
98
- run("node", ["scripts/bake.mjs"], { allowFail: true });
99
-
100
- console.log("[preview] step 3/3 — bundle");
101
- run("node", ["scripts/bundle.mjs"]);
78
+ console.log("[preview] PREVIEW_AUTOBUILD=1 — running mado release");
79
+ run("node", ["scripts/cli.mjs", "release"]);
102
80
  }
103
81
 
104
82
  if (!(await exists(OUT))) {
105
83
  console.error(
106
- `[preview] missing ${OUT}/ — run \`mado release\` (or \`mado bundle\`) first.`,
84
+ `[preview] missing ${OUT}/ — run \`mado release\` first.`,
107
85
  );
108
86
  process.exit(1);
109
87
  }
@@ -111,7 +89,7 @@ if (!(await exists(OUT))) {
111
89
  const spaShell = join(OUT, "index.html");
112
90
  if (!(await exists(spaShell))) {
113
91
  console.error(
114
- `[preview] missing ${spaShell} — \`mado bundle\` did not produce an HTML entry.\n` +
92
+ `[preview] missing ${spaShell} — \`mado release\` did not produce an HTML entry.\n` +
115
93
  `[preview] Without it any non-baked route will 404 instead of falling back to the SPA.`,
116
94
  );
117
95
  process.exit(1);
@@ -120,7 +98,7 @@ if (!(await exists(spaShell))) {
120
98
  // ---------- 4) Server ----------
121
99
 
122
100
  const isImmutable = (filename) =>
123
- /^(main|chunk|asset)-[A-Z0-9]+\.js$/i.test(filename);
101
+ /\.[A-Za-z0-9_-]{6,}\.(js|css|png|jpg|jpeg|webp|svg|woff2?)$/i.test(filename);
124
102
 
125
103
  const server = createServer(async (req, res) => {
126
104
  try {
@@ -178,12 +156,10 @@ server.on("error", (err) => {
178
156
 
179
157
  server.listen(PORT, HOST, async () => {
180
158
  const urlHost = HOST === "0.0.0.0" || HOST === "::" ? "localhost" : HOST;
181
- const bakedReady = await exists(BAKED);
182
159
  console.log("");
183
160
  console.log("Mado preview (production-like)");
184
161
  console.log(` url: http://${urlHost}:${PORT}/`);
185
162
  console.log(` out: ${OUT}`);
186
- console.log(` baked: ${bakedReady ? BAKED : "(none — SPA-only)"}`);
187
163
  console.log(" (Ctrl-C to stop)");
188
164
  console.log("");
189
165
  });
@@ -7,7 +7,7 @@ import { join } from "node:path";
7
7
  import { gzipSync } from "node:zlib";
8
8
 
9
9
  const API_ENTRY = "src/index.ts";
10
- const SAMPLE_ENTRY = "examples/showcase/main.ts";
10
+ const SAMPLE_ENTRY = "starters/default/src/main.ts";
11
11
  const API_GZIP_LIMIT = readLimit("MADO_SIZE_API_GZIP_LIMIT", 16 * 1024);
12
12
  const SAMPLE_GZIP_LIMIT = readLimit("MADO_SIZE_SAMPLE_GZIP_LIMIT", 42 * 1024);
13
13
 
@@ -17,7 +17,7 @@ const api = await bundlePublicApi();
17
17
  report("public API", api.gzip, API_GZIP_LIMIT);
18
18
 
19
19
  const sample = await bundleSampleApp();
20
- report("showcase app", sample.gzip, SAMPLE_GZIP_LIMIT);
20
+ report("starter app", sample.gzip, SAMPLE_GZIP_LIMIT);
21
21
 
22
22
  if (failed) process.exit(1);
23
23
 
@@ -49,6 +49,9 @@ async function bundleSampleApp() {
49
49
  platform: "browser",
50
50
  splitting: true,
51
51
  outdir,
52
+ alias: {
53
+ "@madojs/mado": "./src/index.ts",
54
+ },
52
55
  legalComments: "none",
53
56
  });
54
57
 
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from "vite";
2
+
3
+ export default defineConfig({
4
+ appType: "spa",
5
+ publicDir: "public",
6
+ build: {
7
+ outDir: "out",
8
+ assetsDir: "assets",
9
+ target: "es2022",
10
+ },
11
+ });
@@ -0,0 +1,12 @@
1
+ root = true
2
+
3
+ [*]
4
+ indent_style = space
5
+ indent_size = 2
6
+ end_of_line = lf
7
+ charset = utf-8
8
+ trim_trailing_whitespace = true
9
+ insert_final_newline = true
10
+
11
+ [*.md]
12
+ trim_trailing_whitespace = false
@@ -0,0 +1,74 @@
1
+ # Mado Starter
2
+
3
+ Canonical starter for Mado business apps: admin panels, internal tools and
4
+ long-lived SPAs.
5
+
6
+ ## Commands
7
+
8
+ ```bash
9
+ npm install
10
+ npm run dev
11
+ npm run release
12
+ ```
13
+
14
+ `npm run release` writes the deploy artifact to `out/`.
15
+
16
+ ## Shape
17
+
18
+ ```txt
19
+ public/ static assets copied by Vite
20
+ src/
21
+ main.ts imports global CSS and mounts the router
22
+ app.routes.ts app map: zones, layouts, guards and modules
23
+ layouts/ app-zone shells
24
+ shared/
25
+ http/ HTTP client and interceptors
26
+ lib/ pure utilities
27
+ styles/ tokens, reset, shell and content CSS
28
+ ui/ reusable x-* components
29
+ modules/ business modules
30
+ <name>/
31
+ <name>.routes.ts
32
+ <name>.public.ts
33
+ <name>.types.ts
34
+ pages/
35
+ data/
36
+ api/
37
+ components/
38
+ _contracts/
39
+ ```
40
+
41
+ ## CSS Contract
42
+
43
+ - `tokens.css` defines CSS custom properties and is safe for Shadow DOM
44
+ components through `var(...)`.
45
+ - `reset.css` normalizes the document/light DOM surface.
46
+ - `shell.css` styles app-zone layouts from `src/layouts/`.
47
+ - `content.css` styles page-level light DOM: forms, tables, prose and simple
48
+ states.
49
+ - Reusable leaf components keep their own styles in ``css`...` `` inside
50
+ `component()` options.
51
+
52
+ Vite uses Lightning CSS for CSS transforms/minification in this starter.
53
+
54
+ ## Generate Files
55
+
56
+ ```bash
57
+ npm run new -- module billing
58
+ npm run new -- page billing/pages/invoices-list
59
+ npm run new -- connector billing/api/stripe
60
+ npm run new -- resource billing/data/invoices
61
+ npm run new -- service billing/cart
62
+ npm run new -- form billing/invoice
63
+ npm run new -- component billing/components/invoice-status-badge
64
+ npm run new -- guard billing/billing
65
+ npm run new -- layout admin-shell
66
+ ```
67
+
68
+ The generator writes files only. Wire new routes in `src/app.routes.ts` or the
69
+ module route map by hand.
70
+
71
+ ## More
72
+
73
+ The full architecture guide lives in the framework docs:
74
+ https://github.com/madojs/mado/tree/main/docs/en
@@ -0,0 +1,256 @@
1
+ // ESLint config for the Mado modular starter.
2
+ //
3
+ // Goals:
4
+ // 1. Enforce architectural boundaries (shared/ vs modules/, public surface).
5
+ // 2. Enforce file-form conventions (a *.connector.ts cannot import a *.page.ts, etc.).
6
+ // 3. Forbid barrel files and runtime CSS imports outside main.ts.
7
+ //
8
+ // One ESLint config, always strict. There is no "loose" mode.
9
+
10
+ import tseslint from "@typescript-eslint/eslint-plugin";
11
+ import tsparser from "@typescript-eslint/parser";
12
+ import boundaries from "eslint-plugin-boundaries";
13
+
14
+ export default [
15
+ {
16
+ files: ["src/**/*.ts"],
17
+ languageOptions: {
18
+ parser: tsparser,
19
+ parserOptions: {
20
+ ecmaVersion: 2022,
21
+ sourceType: "module",
22
+ project: "./tsconfig.json",
23
+ },
24
+ },
25
+ plugins: {
26
+ "@typescript-eslint": tseslint,
27
+ boundaries,
28
+ },
29
+ settings: {
30
+ "boundaries/elements": [
31
+ // App composition root — entry points + manifest.
32
+ { type: "app-root", pattern: "src/{main,app.routes}.ts", mode: "file" },
33
+
34
+ // App-level layouts. Live in src/layouts/ because they describe
35
+ // app zones (auth zone, app zone, marketing zone), not domains.
36
+ { type: "app-layout", pattern: "src/layouts/*.layout.ts", mode: "file" },
37
+
38
+ // Cross-cutting layer.
39
+ { type: "shared", pattern: "src/shared/**" },
40
+
41
+ // Module sub-layers (more specific patterns first).
42
+ // NB: there is no `module-layout` — modules don't own layouts.
43
+ { type: "module-public", pattern: "src/modules/*/*.public.ts", mode: "file" },
44
+ { type: "module-types", pattern: "src/modules/*/*.types.ts", mode: "file" },
45
+ { type: "module-routes", pattern: "src/modules/*/*.routes.ts", mode: "file" },
46
+ { type: "module-guard", pattern: "src/modules/*/*.guard.ts", mode: "file" },
47
+ { type: "module-contracts", pattern: "src/modules/*/_contracts/**" },
48
+ { type: "module-connector", pattern: "src/modules/**/*.connector.ts", mode: "file" },
49
+ { type: "module-resource", pattern: "src/modules/**/*.resource.ts", mode: "file" },
50
+ { type: "module-service", pattern: "src/modules/**/*.service.ts", mode: "file" },
51
+ { type: "module-form", pattern: "src/modules/**/*.form.ts", mode: "file" },
52
+ { type: "module-page", pattern: "src/modules/**/*.page.ts", mode: "file" },
53
+ { type: "module-component", pattern: "src/modules/**/*.component.ts", mode: "file" },
54
+ { type: "module-internal", pattern: "src/modules/*/**" },
55
+ ],
56
+ "boundaries/include": ["src/**/*.ts"],
57
+ },
58
+ rules: {
59
+ // ---------------------------------------------------------------------
60
+ // 1. Module boundaries
61
+ // ---------------------------------------------------------------------
62
+ "boundaries/element-types": [
63
+ 2,
64
+ {
65
+ default: "disallow",
66
+ rules: [
67
+ // app-root may import anything (it composes the app)
68
+ { from: "app-root", allow: ["*"] },
69
+
70
+ // app-layout: pure UI wrapper. May read other modules' public
71
+ // for nav state (auth, i18n…). No connectors/resources/services.
72
+ {
73
+ from: "app-layout",
74
+ allow: ["shared", "module-public", "module-types"],
75
+ },
76
+
77
+ // shared/* lives in isolation
78
+ { from: "shared", allow: ["shared"] },
79
+
80
+ // module-public: gateway only re-exports module internals
81
+ {
82
+ from: "module-public",
83
+ allow: [
84
+ "shared",
85
+ "module-internal",
86
+ "module-types",
87
+ "module-service",
88
+ "module-resource",
89
+ "module-form",
90
+ "module-guard",
91
+ ],
92
+ },
93
+
94
+ // module-types are pure types
95
+ { from: "module-types", allow: ["module-types"] },
96
+
97
+ // module-routes: plain map of paths to lazy pages of THIS module.
98
+ // Modules do not own layouts — composition happens in app.routes.ts.
99
+ {
100
+ from: "module-routes",
101
+ allow: ["module-page"],
102
+ },
103
+
104
+ // module-guard: reads service state of its own module
105
+ {
106
+ from: "module-guard",
107
+ allow: ["module-service", "module-public", "module-types", "shared"],
108
+ },
109
+
110
+ // _contracts are private DTOs of one connector
111
+ { from: "module-contracts", allow: ["module-contracts"] },
112
+
113
+ // Connectors talk to HTTP and contracts. No UI, no signals.
114
+ {
115
+ from: "module-connector",
116
+ allow: ["shared", "module-contracts", "module-types"],
117
+ },
118
+
119
+ // Resources/mutations bridge connector ↔ app
120
+ {
121
+ from: "module-resource",
122
+ allow: ["shared", "module-connector", "module-types", "module-public"],
123
+ },
124
+
125
+ // Services hold module state. May talk to other modules' public.
126
+ {
127
+ from: "module-service",
128
+ allow: [
129
+ "shared",
130
+ "module-connector",
131
+ "module-resource",
132
+ "module-types",
133
+ "module-public",
134
+ ],
135
+ },
136
+
137
+ // Forms = schema + validators
138
+ {
139
+ from: "module-form",
140
+ allow: ["shared", "module-types", "module-public"],
141
+ },
142
+
143
+ // Pages may use everything inside their own module + shared + others' public
144
+ {
145
+ from: "module-page",
146
+ allow: [
147
+ "shared",
148
+ "module-component",
149
+ "module-service",
150
+ "module-resource",
151
+ "module-form",
152
+ "module-types",
153
+ "module-public",
154
+ "module-internal",
155
+ ],
156
+ },
157
+
158
+ // Reusable components stay UI-only: no business state.
159
+ {
160
+ from: "module-component",
161
+ allow: ["shared", "module-component", "module-types"],
162
+ },
163
+
164
+ // Fallback for module-internal helpers (_parts/*, etc.)
165
+ {
166
+ from: "module-internal",
167
+ allow: ["shared", "module-internal", "module-types", "module-public"],
168
+ },
169
+ ],
170
+ },
171
+ ],
172
+
173
+ // ---------------------------------------------------------------------
174
+ // 2. No barrel files. Encourages explicit imports, helps tree-shaking
175
+ // and reduces LLM context noise.
176
+ // ---------------------------------------------------------------------
177
+ "no-restricted-syntax": [
178
+ "error",
179
+ {
180
+ selector: "ExportAllDeclaration",
181
+ message:
182
+ "Barrel re-exports are forbidden. Re-export individual symbols in *.public.ts.",
183
+ },
184
+ ],
185
+
186
+ // ---------------------------------------------------------------------
187
+ // 3. Imports
188
+ // ---------------------------------------------------------------------
189
+ "no-restricted-imports": [
190
+ "error",
191
+ {
192
+ patterns: [
193
+ {
194
+ group: ["**/index", "**/index.ts", "**/index.js"],
195
+ message: "Do not import from index files. Import the file directly.",
196
+ },
197
+ ],
198
+ },
199
+ ],
200
+
201
+ // ---------------------------------------------------------------------
202
+ // 4. TypeScript hygiene
203
+ // ---------------------------------------------------------------------
204
+ "@typescript-eslint/consistent-type-imports": ["error", { prefer: "type-imports" }],
205
+ "@typescript-eslint/no-explicit-any": "warn",
206
+ "@typescript-eslint/no-unused-vars": [
207
+ "error",
208
+ { argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
209
+ ],
210
+ },
211
+ },
212
+
213
+ // Only main.ts may import CSS at runtime.
214
+ {
215
+ files: ["src/**/*.ts"],
216
+ ignores: ["src/main.ts"],
217
+ rules: {
218
+ "no-restricted-imports": [
219
+ "error",
220
+ {
221
+ patterns: [
222
+ {
223
+ group: ["*.css", "**/*.css"],
224
+ message:
225
+ "Import CSS only in src/main.ts. Component styles must use css`` inside the component file.",
226
+ },
227
+ {
228
+ group: ["**/index", "**/index.ts", "**/index.js"],
229
+ message: "Do not import from index files. Import the file directly.",
230
+ },
231
+ ],
232
+ },
233
+ ],
234
+ },
235
+ },
236
+
237
+ // _contracts are DTOs for connectors only.
238
+ {
239
+ files: ["src/**/*.ts"],
240
+ ignores: ["src/**/*.connector.ts"],
241
+ rules: {
242
+ "no-restricted-imports": [
243
+ "error",
244
+ {
245
+ patterns: [
246
+ {
247
+ group: ["**/_contracts/*"],
248
+ message:
249
+ "External DTOs (_contracts) are private to the connector. Use the domain type instead.",
250
+ },
251
+ ],
252
+ },
253
+ ],
254
+ },
255
+ },
256
+ ];
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <link rel="icon" href="/favicon.svg" />
7
+ <title>Mado App</title>
8
+ </head>
9
+ <body>
10
+ <div id="app"></div>
11
+ <script type="module" src="/src/main.ts"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "__APP_NAME__",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "mado dev",
8
+ "build": "mado release",
9
+ "typecheck": "mado typecheck",
10
+ "test": "npm run typecheck && npm run lint",
11
+ "release": "mado release",
12
+ "preview": "mado preview",
13
+ "lint": "eslint \"src/**/*.ts\"",
14
+ "lint:fix": "eslint \"src/**/*.ts\" --fix",
15
+ "new": "mado new"
16
+ },
17
+ "dependencies": {
18
+ "@madojs/mado": "__MADOJS_VERSION__"
19
+ },
20
+ "devDependencies": {
21
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
22
+ "@typescript-eslint/parser": "^8.0.0",
23
+ "eslint": "^9.0.0",
24
+ "eslint-plugin-boundaries": "^5.0.0",
25
+ "linkedom": "^0.18.12",
26
+ "lightningcss": "^1.32.0",
27
+ "typescript": "^5.5.0",
28
+ "vite": "^8.0.16"
29
+ }
30
+ }
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
2
+ <rect width="64" height="64" rx="14" fill="#0f172a"/>
3
+ <path fill="#fff" d="M16 45V19h8l8 12 8-12h8v26h-8V31l-8 12-8-12v14z"/>
4
+ </svg>
@@ -0,0 +1,39 @@
1
+ // Single source of truth for the application route table.
2
+ //
3
+ // This file is the APP MAP. Reading it = understanding the app.
4
+ //
5
+ // One rule:
6
+ // Modules export plain `routes` maps. The choice of SHELL and GUARD for
7
+ // each zone of the app is made HERE, by wrapping a module's routes with
8
+ // a `layout({...})` block.
9
+ //
10
+ // `manifest` is exported separately so `mado bake` can discover pages that
11
+ // declare `bake: { paths, data }`.
12
+
13
+ import { layout, routes } from "@madojs/mado";
14
+
15
+ import { requireAuth } from "./modules/auth/auth.public";
16
+ import { authRoutes } from "./modules/auth/auth.routes";
17
+ import { billingRoutes } from "./modules/billing/billing.routes";
18
+
19
+ export const manifest = {
20
+ // Public landing (no shell, no guard).
21
+ "/": () => import("./modules/home/home.page"),
22
+
23
+ // AUTH ZONE — centered card, no guard.
24
+ "/login": layout({
25
+ layout: () => import("./layouts/auth-shell.layout"),
26
+ routes: authRoutes,
27
+ }),
28
+
29
+ // APP ZONE — header + nav, guarded by requireAuth.
30
+ "/billing": layout({
31
+ layout: () => import("./layouts/app-shell.layout"),
32
+ guard: requireAuth,
33
+ routes: billingRoutes,
34
+ }),
35
+
36
+ "*": () => import("./modules/home/not-found.page"),
37
+ };
38
+
39
+ export default routes(manifest);
@@ -0,0 +1,35 @@
1
+ // App-wide shell used by the authenticated zone (every module that lives
2
+ // behind requireAuth).
3
+ //
4
+ // A layout is a STRAIGHTFORWARD wrapper:
5
+ // - it owns the chrome (header / nav / footer);
6
+ // - it does NOT know which page is rendered inside (`child` is anonymous);
7
+ // - it does NOT keep per-route state;
8
+ // - it MAY read cross-cutting modules' public surfaces (auth, i18n…).
9
+ //
10
+ // Layouts live in src/layouts/ because they describe APP ZONES, not domains.
11
+
12
+ import { html, page } from "@madojs/mado";
13
+
14
+ import { isAuthed, logout, user } from "../modules/auth/auth.public";
15
+ import "../shared/ui/x-button.component";
16
+
17
+ export default page({
18
+ title: "Mado App",
19
+ view: ({ child }) => html`
20
+ <div class="layout layout--app">
21
+ <header class="app-header">
22
+ <a href="/" class="brand">Mado App</a>
23
+ <nav>
24
+ <a href="/billing/invoices">Invoices</a>
25
+ ${() =>
26
+ isAuthed()
27
+ ? html`<span class="who">${() => user()?.email ?? ""}</span>
28
+ <x-button variant="ghost" @click=${logout}>Sign out</x-button>`
29
+ : html`<a href="/login">Sign in</a>`}
30
+ </nav>
31
+ </header>
32
+ <main class="app-main">${child}</main>
33
+ </div>
34
+ `,
35
+ });
@@ -0,0 +1,17 @@
1
+ // Centered card shell used by the AUTH zone (login, forgot, reset…).
2
+ //
3
+ // Note this layout is NOT owned by the auth module — it's owned by the
4
+ // auth ZONE. If tomorrow a `password-reset` module appears, it will reuse
5
+ // this same shell.
6
+
7
+ import { html, page } from "@madojs/mado";
8
+
9
+ export default page({
10
+ title: "Sign in",
11
+ view: ({ child }) => html`
12
+ <div class="layout layout--auth">
13
+ <a href="/" class="brand">Mado App</a>
14
+ <main class="auth-main">${child}</main>
15
+ </div>
16
+ `,
17
+ });
@@ -0,0 +1,16 @@
1
+ // App entry point. The ONLY file allowed to import CSS at runtime.
2
+ import "./shared/styles/tokens.css";
3
+ import "./shared/styles/reset.css";
4
+ import "./shared/styles/shell.css";
5
+ import "./shared/styles/content.css";
6
+
7
+ import { html, render } from "@madojs/mado";
8
+
9
+ import { init as initAuth } from "./modules/auth/auth.service";
10
+ import appRoutes from "./app.routes";
11
+
12
+ // One-off boot order: init cross-cutting modules first, then render the
13
+ // router view into #app.
14
+ await initAuth();
15
+
16
+ render(html`${appRoutes.view}`, document.getElementById("app")!);
@@ -0,0 +1,17 @@
1
+ // Raw DTOs of the auth backend. PRIVATE to this module's connector.
2
+ // ESLint prevents these from being imported anywhere else.
3
+
4
+ export interface LoginRequestDTO {
5
+ email: string;
6
+ password: string;
7
+ }
8
+
9
+ export interface LoginResponseDTO {
10
+ token: string;
11
+ user: {
12
+ id: string;
13
+ email: string;
14
+ roles: string[];
15
+ permissions: string[];
16
+ };
17
+ }