@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.
- package/AGENTS.md +24 -26
- package/CHANGELOG.md +68 -0
- package/README.md +18 -45
- package/TODO.md +52 -48
- package/dist/src/component.d.ts +2 -1
- package/dist/src/component.js +5 -2
- package/dist/src/component.js.map +1 -1
- package/dist/src/each.d.ts +1 -1
- package/dist/src/each.js +1 -1
- package/dist/src/each.js.map +1 -1
- package/dist/src/index.d.ts +11 -6
- package/dist/src/index.js +5 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/lazy.d.ts +1 -1
- package/dist/src/lazy.js +1 -1
- package/dist/src/lazy.js.map +1 -1
- package/dist/src/page.d.ts +17 -21
- package/dist/src/page.js +7 -12
- package/dist/src/page.js.map +1 -1
- package/dist/src/router/manifest.d.ts +1 -1
- package/dist/src/router/manifest.js +21 -13
- package/dist/src/router/manifest.js.map +1 -1
- package/dist/src/router/match.d.ts +2 -2
- package/dist/src/router/match.js +3 -3
- package/dist/src/router/match.js.map +1 -1
- package/dist/src/router/navigation.js +1 -1
- package/dist/src/router/navigation.js.map +1 -1
- package/dist/src/vite/index.d.ts +10 -0
- package/dist/src/vite/index.js +33 -0
- package/dist/src/vite/index.js.map +1 -0
- package/docs/en/00-the-mado-way.md +25 -12
- package/docs/en/01-routing.md +90 -142
- package/docs/en/02-project-layout.md +59 -53
- package/docs/en/03-static-bake.md +5 -6
- package/docs/en/05-why-mado.md +6 -6
- package/docs/en/06-for-backenders.md +18 -22
- package/docs/en/08-llm-zero-history-test.md +9 -14
- package/docs/en/09-shadow-vs-light-dom.md +28 -36
- package/docs/en/10-app-architecture.md +158 -96
- package/docs/en/11-layouts.md +22 -24
- package/docs/en/12-auth-and-api.md +89 -182
- package/docs/en/13-deployment.md +18 -22
- package/docs/en/14-testing.md +4 -4
- package/docs/en/16-bake-cookbook.md +11 -12
- package/docs/en/18-api-freeze-map.md +6 -4
- package/docs/en/20-v1-stability.md +1 -1
- package/docs/fr/00-the-mado-way.md +55 -90
- package/docs/fr/01-routing.md +70 -152
- package/docs/fr/02-project-layout.md +61 -42
- package/docs/fr/03-static-bake.md +1 -1
- package/docs/fr/05-why-mado.md +6 -6
- package/docs/fr/06-for-backenders.md +7 -7
- package/docs/fr/08-llm-zero-history-test.md +21 -48
- package/docs/fr/09-shadow-vs-light-dom.md +43 -162
- package/docs/fr/10-app-architecture.md +110 -33
- package/docs/fr/11-layouts.md +24 -12
- package/docs/fr/12-auth-and-api.md +63 -22
- package/docs/fr/13-deployment.md +7 -10
- package/docs/fr/14-testing.md +1 -1
- package/docs/fr/16-bake-cookbook.md +2 -2
- package/docs/fr/18-api-freeze-map.md +1 -1
- package/docs/fr/20-v1-stability.md +1 -1
- package/docs/recipes/nginx/README.md +13 -0
- package/docs/ru/00-the-mado-way.md +53 -75
- package/docs/ru/01-routing.md +68 -143
- package/docs/ru/02-project-layout.md +61 -41
- package/docs/ru/03-static-bake.md +2 -2
- package/docs/ru/05-why-mado.md +6 -6
- package/docs/ru/06-for-backenders.md +7 -7
- package/docs/ru/08-llm-zero-history-test.md +9 -14
- package/docs/ru/09-shadow-vs-light-dom.md +43 -178
- package/docs/ru/10-app-architecture.md +115 -63
- package/docs/ru/11-layouts.md +24 -24
- package/docs/ru/12-auth-and-api.md +57 -35
- package/docs/ru/13-deployment.md +7 -11
- package/docs/ru/14-testing.md +1 -1
- package/docs/ru/16-bake-cookbook.md +12 -6
- package/docs/ru/18-api-freeze-map.md +5 -3
- package/docs/ru/20-v1-stability.md +1 -1
- package/docs/uk/00-the-mado-way.md +70 -44
- package/docs/uk/01-routing.md +41 -47
- package/docs/uk/02-project-layout.md +68 -41
- package/docs/uk/03-static-bake.md +1 -2
- package/docs/uk/06-for-backenders.md +3 -3
- package/docs/uk/08-llm-zero-history-test.md +22 -24
- package/docs/uk/09-shadow-vs-light-dom.md +37 -86
- package/docs/uk/10-app-architecture.md +72 -31
- package/docs/uk/11-layouts.md +25 -12
- package/docs/uk/12-auth-and-api.md +58 -22
- package/docs/uk/13-deployment.md +4 -3
- package/docs/uk/14-testing.md +1 -1
- package/docs/uk/18-api-freeze-map.md +1 -1
- package/docs/uk/20-v1-stability.md +1 -1
- package/llms.txt +14 -15
- package/package.json +18 -11
- package/scripts/_config.mjs +15 -161
- package/scripts/bake.mjs +67 -57
- package/scripts/cli/generate.mjs +348 -0
- package/scripts/cli/help.mjs +27 -0
- package/scripts/cli/index.mjs +79 -0
- package/scripts/cli/init.mjs +153 -0
- package/scripts/cli/release.mjs +152 -0
- package/scripts/cli/run.mjs +96 -0
- package/scripts/cli.mjs +2 -621
- package/scripts/package-smoke.mjs +4 -1
- package/scripts/preview.mjs +13 -37
- package/scripts/size-budget.mjs +5 -2
- package/scripts/vite.default.mjs +11 -0
- package/starters/default/.editorconfig +12 -0
- package/starters/default/README.md +74 -0
- package/starters/default/eslint.config.mjs +256 -0
- package/starters/default/index.html +13 -0
- package/starters/default/package.json +30 -0
- package/starters/default/public/favicon.svg +4 -0
- package/starters/default/src/app.routes.ts +39 -0
- package/starters/default/src/layouts/app-shell.layout.ts +35 -0
- package/starters/default/src/layouts/auth-shell.layout.ts +17 -0
- package/starters/default/src/main.ts +16 -0
- package/starters/default/src/modules/auth/_contracts/auth-api.types.ts +17 -0
- package/starters/default/src/modules/auth/auth.connector.ts +45 -0
- package/starters/default/src/modules/auth/auth.guard.ts +22 -0
- package/starters/default/src/modules/auth/auth.public.ts +9 -0
- package/starters/default/src/modules/auth/auth.routes.ts +8 -0
- package/starters/default/src/modules/auth/auth.service.ts +71 -0
- package/starters/default/src/modules/auth/auth.types.ts +15 -0
- package/starters/default/src/modules/auth/login.page.ts +62 -0
- package/starters/default/src/modules/billing/_contracts/stripe.types.ts +17 -0
- package/starters/default/src/modules/billing/api/stripe.connector.ts +71 -0
- package/starters/default/src/modules/billing/billing.public.ts +5 -0
- package/starters/default/src/modules/billing/billing.routes.ts +9 -0
- package/starters/default/src/modules/billing/billing.types.ts +15 -0
- package/starters/default/src/modules/billing/components/invoice-status-badge.component.ts +43 -0
- package/starters/default/src/modules/billing/data/invoices.resource.ts +35 -0
- package/starters/default/src/modules/billing/pages/invoice-detail.page.ts +70 -0
- package/starters/default/src/modules/billing/pages/invoices-list.page.ts +73 -0
- package/starters/default/src/modules/home/home.page.ts +34 -0
- package/starters/default/src/modules/home/not-found.page.ts +11 -0
- package/starters/default/src/shared/http/http-client.ts +86 -0
- package/starters/default/src/shared/http/http-error.ts +37 -0
- package/starters/default/src/shared/http/interceptors.ts +59 -0
- package/starters/default/src/shared/lib/format-date.ts +19 -0
- package/starters/default/src/shared/styles/content.css +70 -0
- package/starters/default/src/shared/styles/reset.css +32 -0
- package/starters/default/src/shared/styles/shell.css +57 -0
- package/starters/default/src/shared/styles/tokens.css +44 -0
- package/starters/default/src/shared/ui/x-button.component.ts +49 -0
- package/starters/default/src/shared/ui/x-spinner.component.ts +22 -0
- package/starters/default/src/styles.d.ts +1 -0
- package/starters/default/src/vite-env.d.ts +1 -0
- package/starters/default/tsconfig.json +24 -0
- package/starters/default/vite.config.ts +9 -0
- package/MADO_V1_PLAN.md +0 -179
- package/ROADMAP.md +0 -178
- package/dist/src/html.d.ts +0 -18
- package/dist/src/html.js +0 -17
- package/dist/src/html.js.map +0 -1
- package/dist/src/router.d.ts +0 -13
- package/dist/src/router.js +0 -13
- package/dist/src/router.js.map +0 -1
- package/scripts/bundle.mjs +0 -212
- package/scripts/llm-zero-history-smoke.mjs +0 -93
- package/scripts/new.mjs +0 -80
- package/scripts/showcase-regression.mjs +0 -392
- package/server/serve.mjs +0 -455
- package/starters/admin/README.md +0 -63
- package/starters/admin/index.html +0 -28
- package/starters/admin/mado.config.json +0 -22
- package/starters/admin/package.json +0 -24
- package/starters/admin/public/favicon.svg +0 -4
- package/starters/admin/src/components/x-button.ts +0 -82
- package/starters/admin/src/components/x-input.ts +0 -105
- package/starters/admin/src/layouts/app.ts +0 -101
- package/starters/admin/src/layouts/auth.ts +0 -41
- package/starters/admin/src/lib/api.ts +0 -184
- package/starters/admin/src/lib/auth.ts +0 -83
- package/starters/admin/src/main.ts +0 -15
- package/starters/admin/src/pages/admin/dashboard.ts +0 -48
- package/starters/admin/src/pages/admin/order-detail.ts +0 -80
- package/starters/admin/src/pages/admin/orders.ts +0 -117
- package/starters/admin/src/pages/home.ts +0 -34
- package/starters/admin/src/pages/login.ts +0 -70
- package/starters/admin/src/pages/not-found.ts +0 -12
- package/starters/admin/src/routes.ts +0 -40
- package/starters/admin/src/styles/global.ts +0 -86
- package/starters/admin/tsconfig.json +0 -15
- package/starters/crud/README.md +0 -33
- package/starters/crud/index.html +0 -28
- package/starters/crud/mado.config.json +0 -20
- package/starters/crud/package.json +0 -24
- package/starters/crud/src/components/app-shell.ts +0 -56
- package/starters/crud/src/components/ticket-detail.ts +0 -33
- package/starters/crud/src/components/ticket-form.ts +0 -69
- package/starters/crud/src/components/ticket-list.ts +0 -66
- package/starters/crud/src/lib/api.ts +0 -76
- package/starters/crud/src/main.ts +0 -9
- package/starters/crud/src/pages/home.ts +0 -34
- package/starters/crud/src/pages/not-found.ts +0 -12
- package/starters/crud/src/pages/ticket-detail.ts +0 -7
- package/starters/crud/src/pages/ticket-new.ts +0 -7
- package/starters/crud/src/pages/tickets.ts +0 -7
- package/starters/crud/src/routes.ts +0 -11
- package/starters/crud/src/styles/global.ts +0 -155
- package/starters/crud/tsconfig.json +0 -15
- package/starters/minimal/README.md +0 -21
- package/starters/minimal/index.html +0 -28
- package/starters/minimal/mado.config.json +0 -20
- package/starters/minimal/package.json +0 -24
- package/starters/minimal/src/components/app-counter.ts +0 -31
- package/starters/minimal/src/main.ts +0 -9
- package/starters/minimal/src/pages/home.ts +0 -35
- package/starters/minimal/src/pages/not-found.ts +0 -14
- package/starters/minimal/src/routes.ts +0 -8
- package/starters/minimal/src/styles/global.ts +0 -60
- package/starters/minimal/tsconfig.json +0 -15
- package/templates/page-detail.ts +0 -63
- package/templates/page-form.ts +0 -94
- package/templates/page-list.ts +0 -79
|
@@ -1,217 +1,124 @@
|
|
|
1
1
|
# Auth and API
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
9
|
-
files pre-installed in `src/lib/`:
|
|
23
|
+
Every business module follows the same flow:
|
|
10
24
|
|
|
11
|
-
|
|
12
|
-
|
|
25
|
+
```txt
|
|
26
|
+
connector -> resource/mutation -> page
|
|
27
|
+
```
|
|
13
28
|
|
|
14
|
-
|
|
29
|
+
Pages do not import DTOs. Pages do not call `fetch()` directly. Connectors do
|
|
30
|
+
not import Mado reactivity or UI.
|
|
15
31
|
|
|
16
|
-
##
|
|
32
|
+
## HTTP Client
|
|
17
33
|
|
|
18
|
-
|
|
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
|
-
|
|
36
|
+
- base URL handling;
|
|
37
|
+
- JSON request/response defaults;
|
|
38
|
+
- query string serialization;
|
|
39
|
+
- `HttpError`;
|
|
40
|
+
- request/response interceptors.
|
|
25
41
|
|
|
26
|
-
|
|
42
|
+
Module connectors build on it:
|
|
27
43
|
|
|
28
44
|
```ts
|
|
29
|
-
import {
|
|
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
|
-
|
|
61
|
+
## Auth Service
|
|
32
62
|
|
|
33
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
65
|
+
```ts
|
|
66
|
+
const _user = signal<User | null>(null);
|
|
67
|
+
const _token = signal<string | null>(null);
|
|
44
68
|
|
|
45
|
-
export
|
|
46
|
-
|
|
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
|
|
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
|
-
|
|
79
|
+
Expose only what other modules need through `auth.public.ts`.
|
|
105
80
|
|
|
106
|
-
|
|
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
|
-
|
|
83
|
+
Guards are plain functions:
|
|
115
84
|
|
|
116
85
|
```ts
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
92
|
+
Use them in `src/app.routes.ts`:
|
|
163
93
|
|
|
164
94
|
```ts
|
|
165
|
-
"/
|
|
166
|
-
layout:
|
|
167
|
-
guard:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
"
|
|
199
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
package/docs/en/13-deployment.md
CHANGED
|
@@ -8,15 +8,12 @@
|
|
|
8
8
|
|
|
9
9
|
```
|
|
10
10
|
out/
|
|
11
|
-
├── index.html ← SPA shell
|
|
12
|
-
├── assets/ ← hashed
|
|
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
|
-
├──
|
|
16
|
-
|
|
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
|
|
45
|
-
`
|
|
46
|
-
`out/` into the host and
|
|
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
|
|
59
|
+
Key lines of the recipe `nginx.conf`:
|
|
62
60
|
|
|
63
61
|
- `gzip_static on;` — serves the precompressed `.gz` files written by
|
|
64
|
-
`mado
|
|
65
|
-
-
|
|
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
|
-
|
|
96
|
-
|
|
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
|
|
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
|
|
188
|
-
|
|
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/`/`
|
|
190
|
+
`src/`/`public/`/`out/` model and [`03-static-bake.md`](./03-static-bake.md)
|
|
195
191
|
for the SEO bake mechanics.
|
package/docs/en/14-testing.md
CHANGED
|
@@ -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
|
|
82
|
-
|
|
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
|
|
59
|
-
`mado release`
|
|
60
|
-
|
|
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`
|
|
88
|
-
|
|
89
|
+
Pass `--base-url` so generated canonical links and sitemap entries point to
|
|
90
|
+
production.
|
|
89
91
|
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
|
|
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
|
|
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`,
|
|
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
|
|
45
|
-
`@madojs/mado/
|
|
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`, `
|
|
28
|
+
`bake`, `preview`, `init`, `new`).
|
|
29
29
|
|
|
30
30
|
Breaking these requires a major version.
|
|
31
31
|
|