@madojs/mado 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +24 -26
- package/CHANGELOG.md +98 -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/html/template.js +10 -0
- package/dist/src/html/template.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 -52
- 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 +25 -26
- package/docs/en/14-testing.md +4 -4
- package/docs/en/16-bake-cookbook.md +17 -10
- 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 +74 -48
- 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 +30 -12
- package/docs/fr/14-testing.md +1 -1
- package/docs/fr/16-bake-cookbook.md +57 -4
- 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 +75 -48
- 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 +19 -13
- package/docs/ru/14-testing.md +1 -1
- package/docs/ru/16-bake-cookbook.md +48 -8
- 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 +71 -58
- 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 -560
- package/scripts/package-smoke.mjs +4 -1
- package/scripts/preview.mjs +17 -61
- 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,13 +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
|
|
15
|
+
├── <route>/index.html ← prerendered SEO HTML for baked routes
|
|
16
|
+
├── sitemap.xml ← generated sitemap
|
|
18
17
|
├── favicon.svg ← your public/ assets copied verbatim
|
|
19
18
|
├── _redirects ← Cloudflare Pages / Netlify SPA fallback
|
|
20
19
|
└── _headers ← Cloudflare Pages / Netlify cache rules
|
|
@@ -31,17 +30,18 @@ mado release
|
|
|
31
30
|
mado preview # http://localhost:4173 — serves out/ exactly as a static host would
|
|
32
31
|
```
|
|
33
32
|
|
|
34
|
-
`mado preview`
|
|
35
|
-
`.gz` over raw,
|
|
36
|
-
`index.html` for unknown paths.
|
|
33
|
+
`mado preview` serves the final `out/` directory like a static host: it picks
|
|
34
|
+
`.br` over `.gz` over raw, serves promoted baked HTML when a route has an
|
|
35
|
+
`index.html`, and falls back to `index.html` for unknown SPA paths.
|
|
37
36
|
|
|
38
37
|
---
|
|
39
38
|
|
|
40
39
|
## Recipe 1: VPS + nginx
|
|
41
40
|
|
|
42
|
-
The framework ships
|
|
43
|
-
`
|
|
44
|
-
`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.
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
47
|
# Build the artifact locally
|
|
@@ -52,16 +52,15 @@ rsync -avz --delete out/ user@server:/var/www/myapp/
|
|
|
52
52
|
|
|
53
53
|
# On the VPS — first time only:
|
|
54
54
|
sudo cp /etc/nginx/conf.d/myapp.conf{,.bak}
|
|
55
|
-
sudo cp ./nginx.conf /etc/nginx/conf.d/myapp.conf
|
|
55
|
+
sudo cp ./docs/recipes/nginx/nginx.conf /etc/nginx/conf.d/myapp.conf
|
|
56
56
|
sudo nginx -t && sudo systemctl reload nginx
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
Key lines of the
|
|
59
|
+
Key lines of the recipe `nginx.conf`:
|
|
60
60
|
|
|
61
61
|
- `gzip_static on;` — serves the precompressed `.gz` files written by
|
|
62
|
-
`mado
|
|
63
|
-
-
|
|
64
|
-
hashed bundles get a one-year cache.
|
|
62
|
+
`mado release`. Zero CPU at request time.
|
|
63
|
+
- `/assets/*` should be cached immutable; Vite filenames are content hashed.
|
|
65
64
|
- `try_files $uri $uri/ /index.html;` — SPA fallback so deep links work
|
|
66
65
|
after a hard refresh.
|
|
67
66
|
|
|
@@ -79,8 +78,9 @@ npx wrangler pages deploy out --project-name=myapp
|
|
|
79
78
|
- The generated `_redirects` (`/* /index.html 200`) gives you SPA fallback.
|
|
80
79
|
- The generated `_headers` (immutable cache for `/assets/*`, `no-cache` for
|
|
81
80
|
HTML) is honored by CF Pages.
|
|
82
|
-
- Baked routes (`out
|
|
83
|
-
fallback because CF Pages matches static
|
|
81
|
+
- Baked routes are promoted to real route files (`out/<route>/index.html`),
|
|
82
|
+
so they take priority over the SPA fallback because CF Pages matches static
|
|
83
|
+
files first.
|
|
84
84
|
|
|
85
85
|
For preview branches, set the same build command in the CF Pages project:
|
|
86
86
|
|
|
@@ -89,9 +89,8 @@ Build command: npm ci && npx mado release
|
|
|
89
89
|
Output directory: out
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
bake at build time.
|
|
92
|
+
For catalogs too big to bake at build time, keep edge prerender experiments in
|
|
93
|
+
the external examples workspace rather than in the core package.
|
|
95
94
|
|
|
96
95
|
---
|
|
97
96
|
|
|
@@ -138,7 +137,7 @@ add a `404.html` that loads the SPA, or use the
|
|
|
138
137
|
| Other static files | `public, max-age=86400` | safe daily cache |
|
|
139
138
|
|
|
140
139
|
`mado release` writes these rules into `out/_headers` for CF / Netlify and
|
|
141
|
-
the
|
|
140
|
+
the nginx recipe enforces them server-side.
|
|
142
141
|
|
|
143
142
|
---
|
|
144
143
|
|
|
@@ -181,12 +180,12 @@ jobs:
|
|
|
181
180
|
`Cache-Control: public, max-age=...` or you are sitting behind a CDN that
|
|
182
181
|
ignores `no-cache`. Add an explicit rule mirroring the matrix above.
|
|
183
182
|
- **`/assets/*` files change but the browser keeps the old one.** They
|
|
184
|
-
should not — the filename is hashed by `mado
|
|
185
|
-
|
|
183
|
+
should not — the filename is hashed by Vite during `mado release`. If you bypassed
|
|
184
|
+
build and shipped your own unhashed JS, give it a hash or short cache.
|
|
186
185
|
- **Baked SEO page shows `[object Object]`.** Should never happen after the
|
|
187
186
|
v1 bake update — bake now raises a loud error in that case. If you see it,
|
|
188
187
|
upgrade `@madojs/mado` and re-run `mado bake`.
|
|
189
188
|
|
|
190
189
|
See also: [`02-project-layout.md`](./02-project-layout.md) for the
|
|
191
|
-
`src/`/`
|
|
192
|
-
for the SEO bake mechanics.
|
|
190
|
+
`src/`/`public/`/`out/` model and [`03-static-bake.md`](./03-static-bake.md)
|
|
191
|
+
for the SEO bake mechanics.
|
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,8 +55,9 @@ export default routes(manifest);
|
|
|
55
55
|
|
|
56
56
|
## Output
|
|
57
57
|
|
|
58
|
-
By default
|
|
59
|
-
|
|
58
|
+
By default standalone `mado bake` writes baked pages directly into `out/`.
|
|
59
|
+
`mado release` first lets Vite build the production shell and assets, then bake
|
|
60
|
+
replaces route HTML in place and writes `out/sitemap.xml`:
|
|
60
61
|
|
|
61
62
|
```bash
|
|
62
63
|
mado release
|
|
@@ -65,6 +66,15 @@ tree out
|
|
|
65
66
|
|
|
66
67
|
The deployable folder is `out/`, not `dist/`.
|
|
67
68
|
|
|
69
|
+
Use `mado release --keep-bake-dir` only when you want an extra `out/baked/`
|
|
70
|
+
inspection copy during debugging.
|
|
71
|
+
|
|
72
|
+
## Client boot
|
|
73
|
+
|
|
74
|
+
Baked HTML marks `#app` with `data-mado-baked`. This is not hydration: when the
|
|
75
|
+
client app starts, `render()` replaces the baked DOM with live bindings. The
|
|
76
|
+
first response contains SEO/first-paint HTML, then the SPA takes over normally.
|
|
77
|
+
|
|
68
78
|
## Unsupported values
|
|
69
79
|
|
|
70
80
|
Bake intentionally fails loudly instead of writing `[object Object]`. If a baked
|
|
@@ -76,15 +86,12 @@ view throws an unsupported directive error:
|
|
|
76
86
|
|
|
77
87
|
## Canonical links
|
|
78
88
|
|
|
79
|
-
Pass `--base-url`
|
|
80
|
-
|
|
89
|
+
Pass `--base-url` so generated canonical links and sitemap entries point to
|
|
90
|
+
production.
|
|
81
91
|
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
"baseUrl": "https://example.com"
|
|
86
|
-
}
|
|
87
|
-
}
|
|
92
|
+
```bash
|
|
93
|
+
mado bake --base-url https://example.com
|
|
94
|
+
mado release --base-url https://example.com
|
|
88
95
|
```
|
|
89
96
|
|
|
90
97
|
## When not to bake
|
|
@@ -9,10 +9,12 @@ package root:
|
|
|
9
9
|
import { component, html, resource, routes, signal } from "@madojs/mado";
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
The
|
|
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
|
|