@madojs/mado 0.5.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 +291 -0
- package/CHANGELOG.md +23 -0
- package/LICENSE +21 -0
- package/README.md +371 -0
- package/ROADMAP.md +52 -0
- package/dist/src/component.d.ts +48 -0
- package/dist/src/component.js +140 -0
- package/dist/src/component.js.map +1 -0
- package/dist/src/context.d.ts +40 -0
- package/dist/src/context.js +67 -0
- package/dist/src/context.js.map +1 -0
- package/dist/src/css.d.ts +54 -0
- package/dist/src/css.js +137 -0
- package/dist/src/css.js.map +1 -0
- package/dist/src/devtools.d.ts +22 -0
- package/dist/src/devtools.js +63 -0
- package/dist/src/devtools.js.map +1 -0
- package/dist/src/diagnostics.d.ts +11 -0
- package/dist/src/diagnostics.js +28 -0
- package/dist/src/diagnostics.js.map +1 -0
- package/dist/src/each.d.ts +39 -0
- package/dist/src/each.js +35 -0
- package/dist/src/each.js.map +1 -0
- package/dist/src/forms.d.ts +71 -0
- package/dist/src/forms.js +161 -0
- package/dist/src/forms.js.map +1 -0
- package/dist/src/head.d.ts +19 -0
- package/dist/src/head.js +97 -0
- package/dist/src/head.js.map +1 -0
- package/dist/src/html/bindings.d.ts +78 -0
- package/dist/src/html/bindings.js +304 -0
- package/dist/src/html/bindings.js.map +1 -0
- package/dist/src/html/parser.d.ts +64 -0
- package/dist/src/html/parser.js +521 -0
- package/dist/src/html/parser.js.map +1 -0
- package/dist/src/html/template-types.d.ts +27 -0
- package/dist/src/html/template-types.js +8 -0
- package/dist/src/html/template-types.js.map +1 -0
- package/dist/src/html/template.d.ts +45 -0
- package/dist/src/html/template.js +119 -0
- package/dist/src/html/template.js.map +1 -0
- package/dist/src/html.d.ts +16 -0
- package/dist/src/html.js +16 -0
- package/dist/src/html.js.map +1 -0
- package/dist/src/index.d.ts +35 -0
- package/dist/src/index.js +39 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lazy.d.ts +38 -0
- package/dist/src/lazy.js +73 -0
- package/dist/src/lazy.js.map +1 -0
- package/dist/src/lifecycle.d.ts +45 -0
- package/dist/src/lifecycle.js +66 -0
- package/dist/src/lifecycle.js.map +1 -0
- package/dist/src/page.d.ts +161 -0
- package/dist/src/page.js +38 -0
- package/dist/src/page.js.map +1 -0
- package/dist/src/persisted.d.ts +47 -0
- package/dist/src/persisted.js +119 -0
- package/dist/src/persisted.js.map +1 -0
- package/dist/src/resource.d.ts +120 -0
- package/dist/src/resource.js +275 -0
- package/dist/src/resource.js.map +1 -0
- package/dist/src/router/manifest.d.ts +56 -0
- package/dist/src/router/manifest.js +302 -0
- package/dist/src/router/manifest.js.map +1 -0
- package/dist/src/router/match.d.ts +62 -0
- package/dist/src/router/match.js +117 -0
- package/dist/src/router/match.js.map +1 -0
- package/dist/src/router/navigation.d.ts +89 -0
- package/dist/src/router/navigation.js +263 -0
- package/dist/src/router/navigation.js.map +1 -0
- package/dist/src/router.d.ts +13 -0
- package/dist/src/router.js +13 -0
- package/dist/src/router.js.map +1 -0
- package/dist/src/signal.d.ts +67 -0
- package/dist/src/signal.js +238 -0
- package/dist/src/signal.js.map +1 -0
- package/docs/README.md +12 -0
- package/docs/en/00-the-mado-way.md +106 -0
- package/docs/en/01-routing.md +204 -0
- package/docs/en/02-project-layout.md +58 -0
- package/docs/en/03-static-bake.md +251 -0
- package/docs/en/04-ide-setup.md +162 -0
- package/docs/en/05-why-mado.md +193 -0
- package/docs/en/06-for-backenders.md +422 -0
- package/docs/en/07-llm-pitfalls.md +486 -0
- package/docs/en/08-llm-zero-history-test.md +56 -0
- package/docs/en/09-shadow-vs-light-dom.md +122 -0
- package/docs/en/README.md +16 -0
- package/docs/fr/00-the-mado-way.md +108 -0
- package/docs/fr/01-routing.md +202 -0
- package/docs/fr/02-project-layout.md +58 -0
- package/docs/fr/03-static-bake.md +290 -0
- package/docs/fr/04-ide-setup.md +162 -0
- package/docs/fr/05-why-mado.md +193 -0
- package/docs/fr/06-for-backenders.md +432 -0
- package/docs/fr/07-llm-pitfalls.md +487 -0
- package/docs/fr/08-llm-zero-history-test.md +60 -0
- package/docs/fr/09-shadow-vs-light-dom.md +121 -0
- package/docs/fr/README.md +16 -0
- package/docs/ru/00-the-mado-way.md +93 -0
- package/docs/ru/01-routing.md +194 -0
- package/docs/ru/02-project-layout.md +57 -0
- package/docs/ru/03-static-bake.md +251 -0
- package/docs/ru/04-ide-setup.md +144 -0
- package/docs/ru/05-why-mado.md +193 -0
- package/docs/ru/06-for-backenders.md +422 -0
- package/docs/ru/07-llm-pitfalls.md +485 -0
- package/docs/ru/08-llm-zero-history-test.md +56 -0
- package/docs/ru/09-shadow-vs-light-dom.md +122 -0
- package/docs/ru/README.md +14 -0
- package/docs/uk/00-the-mado-way.md +54 -0
- package/docs/uk/01-routing.md +82 -0
- package/docs/uk/02-project-layout.md +46 -0
- package/docs/uk/03-static-bake.md +49 -0
- package/docs/uk/04-ide-setup.md +26 -0
- package/docs/uk/05-why-mado.md +34 -0
- package/docs/uk/06-for-backenders.md +50 -0
- package/docs/uk/07-llm-pitfalls.md +82 -0
- package/docs/uk/08-llm-zero-history-test.md +31 -0
- package/docs/uk/09-shadow-vs-light-dom.md +40 -0
- package/docs/uk/README.md +16 -0
- package/llms.txt +155 -0
- package/package.json +81 -0
- package/scripts/bake.mjs +406 -0
- package/scripts/bundle.mjs +146 -0
- package/scripts/cli.mjs +382 -0
- package/scripts/new.mjs +80 -0
- package/scripts/preview.mjs +176 -0
- package/scripts/release-notes.mjs +66 -0
- package/scripts/showcase-regression.mjs +392 -0
- package/server/serve.mjs +292 -0
- package/starters/crud/README.md +21 -0
- package/starters/crud/index.html +20 -0
- package/starters/crud/package.json +17 -0
- package/starters/crud/src/components/app-shell.ts +51 -0
- package/starters/crud/src/components/ticket-detail.ts +33 -0
- package/starters/crud/src/components/ticket-form.ts +69 -0
- package/starters/crud/src/components/ticket-list.ts +66 -0
- package/starters/crud/src/lib/api.ts +76 -0
- package/starters/crud/src/main.ts +12 -0
- package/starters/crud/src/pages/home.ts +18 -0
- package/starters/crud/src/pages/not-found.ts +12 -0
- package/starters/crud/src/pages/ticket-detail.ts +6 -0
- package/starters/crud/src/pages/ticket-new.ts +6 -0
- package/starters/crud/src/pages/tickets.ts +6 -0
- package/starters/crud/src/routes.ts +9 -0
- package/starters/crud/src/styles/global.ts +155 -0
- package/starters/crud/tsconfig.json +15 -0
- package/starters/minimal/README.md +19 -0
- package/starters/minimal/index.html +20 -0
- package/starters/minimal/package.json +17 -0
- package/starters/minimal/src/components/app-counter.ts +31 -0
- package/starters/minimal/src/main.ts +9 -0
- package/starters/minimal/src/pages/home.ts +18 -0
- package/starters/minimal/src/pages/not-found.ts +14 -0
- package/starters/minimal/src/routes.ts +6 -0
- package/starters/minimal/src/styles/global.ts +60 -0
- package/starters/minimal/tsconfig.json +15 -0
- package/templates/page-detail.ts +63 -0
- package/templates/page-form.ts +94 -0
- package/templates/page-list.ts +79 -0
package/dist/src/page.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page contract — a uniform format for describing a page.
|
|
3
|
+
*
|
|
4
|
+
* // pages/user-profile.ts
|
|
5
|
+
* import { page, html, resource, jsonFetcher } from '@madojs/mado';
|
|
6
|
+
*
|
|
7
|
+
* export default page({
|
|
8
|
+
* title: ({ id }) => `User #${id}`,
|
|
9
|
+
* load: ({ id }) => resource(() => `/api/users/${id}`, jsonFetcher()),
|
|
10
|
+
* view: ({ params, data }) => html`
|
|
11
|
+
* <h1>${() => data?.data()?.name ?? '\u2026'}</h1>
|
|
12
|
+
* `,
|
|
13
|
+
* });
|
|
14
|
+
*
|
|
15
|
+
* // routes.ts
|
|
16
|
+
* import { routes } from '@madojs/mado';
|
|
17
|
+
*
|
|
18
|
+
* export default routes({
|
|
19
|
+
* '/users/:id': () => import('./pages/user-profile.js'),
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* The contract is strict: exactly three slots — title / load / view.
|
|
23
|
+
* If a page exports something other than Page — tsc will stop it before build.
|
|
24
|
+
* "One right way" — as in Rust: fewer branches, fewer bugs.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Page factory. A typed wrapper over a plain object —
|
|
28
|
+
* needed only for type inference and the single "_page" stamp.
|
|
29
|
+
*/
|
|
30
|
+
export function page(spec) {
|
|
31
|
+
return { _page: true, ...spec };
|
|
32
|
+
}
|
|
33
|
+
export const isPage = (v) => typeof v === "object" && v !== null && v._page === true;
|
|
34
|
+
export function nested(spec) {
|
|
35
|
+
return { _nested: true, ...spec };
|
|
36
|
+
}
|
|
37
|
+
export const isNested = (v) => typeof v === "object" && v !== null && v._nested === true;
|
|
38
|
+
//# sourceMappingURL=page.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page.js","sourceRoot":"","sources":["../../src/page.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AA0GH;;;GAGG;AACH,MAAM,UAAU,IAAI,CAClB,IAA+B;IAE/B,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,CAAC;AAClC,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,CAAU,EAAa,EAAE,CAC9C,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAK,CAAU,CAAC,KAAK,KAAK,IAAI,CAAC;AA8BpE,MAAM,UAAU,MAAM,CAAC,IAAmC;IACxD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,CAAC;AACpC,CAAC;AAED,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,CAAU,EAAqB,EAAE,CACxD,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAK,CAAkB,CAAC,OAAO,KAAK,IAAI,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* persisted() wraps a signal in localStorage / sessionStorage.
|
|
3
|
+
*
|
|
4
|
+
* Useful for:
|
|
5
|
+
* - theme (light/dark)
|
|
6
|
+
* - selected language
|
|
7
|
+
* - last viewed product
|
|
8
|
+
* - form drafts
|
|
9
|
+
*
|
|
10
|
+
* const theme = persisted('theme', signal<'light'|'dark'>('light'));
|
|
11
|
+
* const draft = persisted('newPost.draft', signal(''),
|
|
12
|
+
* { storage: 'session', debounce: 300 });
|
|
13
|
+
*
|
|
14
|
+
* Returns the same Signal API. Reads from storage on startup; writes on every
|
|
15
|
+
* change (optionally debounced). Synchronizes across tabs via BroadcastChannel.
|
|
16
|
+
*
|
|
17
|
+
* Notes:
|
|
18
|
+
* - JSON.parse/stringify; Date/Map/Set need a custom serializer.
|
|
19
|
+
* - On QuotaExceeded or private-mode failures it silently falls back to memory.
|
|
20
|
+
* - destroy() optionally closes the BroadcastChannel subscription.
|
|
21
|
+
*/
|
|
22
|
+
import { type Signal } from "./signal.js";
|
|
23
|
+
export interface PersistedOptions<T> {
|
|
24
|
+
/** "local" (default) or "session". */
|
|
25
|
+
storage?: "local" | "session";
|
|
26
|
+
/** Write delay in ms. Default 0 (synchronous). */
|
|
27
|
+
debounce?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Key prefix. Default "mado:". Helps avoid collisions
|
|
30
|
+
* with other scripts on the page.
|
|
31
|
+
*/
|
|
32
|
+
keyPrefix?: string;
|
|
33
|
+
/** Custom serialiser. Default JSON.stringify. */
|
|
34
|
+
serialize?: (value: T) => string;
|
|
35
|
+
/** Custom deserialiser. Default JSON.parse. */
|
|
36
|
+
deserialize?: (raw: string) => T;
|
|
37
|
+
/**
|
|
38
|
+
* Cross-tab synchronisation via BroadcastChannel.
|
|
39
|
+
* Default true for "local", false for "session".
|
|
40
|
+
*/
|
|
41
|
+
syncTabs?: boolean;
|
|
42
|
+
}
|
|
43
|
+
export interface PersistedSignal<T> extends Signal<T> {
|
|
44
|
+
/** Remove the value from storage and unsubscribe from the bus. */
|
|
45
|
+
destroy(): void;
|
|
46
|
+
}
|
|
47
|
+
export declare function persisted<T>(key: string, base: Signal<T>, options?: PersistedOptions<T>): PersistedSignal<T>;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* persisted() wraps a signal in localStorage / sessionStorage.
|
|
3
|
+
*
|
|
4
|
+
* Useful for:
|
|
5
|
+
* - theme (light/dark)
|
|
6
|
+
* - selected language
|
|
7
|
+
* - last viewed product
|
|
8
|
+
* - form drafts
|
|
9
|
+
*
|
|
10
|
+
* const theme = persisted('theme', signal<'light'|'dark'>('light'));
|
|
11
|
+
* const draft = persisted('newPost.draft', signal(''),
|
|
12
|
+
* { storage: 'session', debounce: 300 });
|
|
13
|
+
*
|
|
14
|
+
* Returns the same Signal API. Reads from storage on startup; writes on every
|
|
15
|
+
* change (optionally debounced). Synchronizes across tabs via BroadcastChannel.
|
|
16
|
+
*
|
|
17
|
+
* Notes:
|
|
18
|
+
* - JSON.parse/stringify; Date/Map/Set need a custom serializer.
|
|
19
|
+
* - On QuotaExceeded or private-mode failures it silently falls back to memory.
|
|
20
|
+
* - destroy() optionally closes the BroadcastChannel subscription.
|
|
21
|
+
*/
|
|
22
|
+
import { effect } from "./signal.js";
|
|
23
|
+
export function persisted(key, base, options = {}) {
|
|
24
|
+
const prefix = options.keyPrefix ?? "mado:";
|
|
25
|
+
const fullKey = prefix + key;
|
|
26
|
+
const storage = options.storage === "session"
|
|
27
|
+
? safeStorage("sessionStorage")
|
|
28
|
+
: safeStorage("localStorage");
|
|
29
|
+
const serialize = options.serialize ?? JSON.stringify;
|
|
30
|
+
const deserialize = options.deserialize ?? ((s) => JSON.parse(s));
|
|
31
|
+
// 1) Read initial value
|
|
32
|
+
if (storage) {
|
|
33
|
+
try {
|
|
34
|
+
const raw = storage.getItem(fullKey);
|
|
35
|
+
if (raw != null)
|
|
36
|
+
base.set(deserialize(raw));
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
/* corrupt JSON — ignore */
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// 2) Write on each change (optionally debounced)
|
|
43
|
+
let writeTimer = null;
|
|
44
|
+
const flushWrite = (v) => {
|
|
45
|
+
if (!storage)
|
|
46
|
+
return;
|
|
47
|
+
try {
|
|
48
|
+
storage.setItem(fullKey, serialize(v));
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
/* QuotaExceeded — skip */
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
effect(() => {
|
|
55
|
+
const v = base();
|
|
56
|
+
if (options.debounce && options.debounce > 0) {
|
|
57
|
+
if (writeTimer)
|
|
58
|
+
clearTimeout(writeTimer);
|
|
59
|
+
writeTimer = setTimeout(() => flushWrite(v), options.debounce);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
flushWrite(v);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
// 3) Cross-tab synchronisation via BroadcastChannel
|
|
66
|
+
const wantSync = options.syncTabs ?? options.storage !== "session";
|
|
67
|
+
let bc = null;
|
|
68
|
+
if (wantSync && typeof BroadcastChannel !== "undefined") {
|
|
69
|
+
try {
|
|
70
|
+
bc = new BroadcastChannel(`mado:persisted:${key}`);
|
|
71
|
+
bc.addEventListener("message", (e) => {
|
|
72
|
+
try {
|
|
73
|
+
base.set(e.data);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
/* noop */
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
// publish changes
|
|
80
|
+
effect(() => {
|
|
81
|
+
const v = base();
|
|
82
|
+
// peek-exclusion to avoid infinite loop: we don't read from bc-source
|
|
83
|
+
bc?.postMessage(v);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
bc = null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const out = base;
|
|
91
|
+
out.destroy = () => {
|
|
92
|
+
if (storage) {
|
|
93
|
+
try {
|
|
94
|
+
storage.removeItem(fullKey);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
/* noop */
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
bc?.close();
|
|
101
|
+
};
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
104
|
+
function safeStorage(name) {
|
|
105
|
+
try {
|
|
106
|
+
const s = globalThis[name];
|
|
107
|
+
if (!s)
|
|
108
|
+
return null;
|
|
109
|
+
// Some browsers throw on storage access in private mode.
|
|
110
|
+
const probe = "__madoprobe__";
|
|
111
|
+
s.setItem(probe, "1");
|
|
112
|
+
s.removeItem(probe);
|
|
113
|
+
return s;
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=persisted.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persisted.js","sourceRoot":"","sources":["../../src/persisted.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactive fetch + cache. Replaces React-Query / SWR.
|
|
3
|
+
*
|
|
4
|
+
* Core idea:
|
|
5
|
+
* 1. resource(keyFn, fetcher) — keyFn reads signals like a normal effect.
|
|
6
|
+
* When dependencies change the key is recomputed, and if the key
|
|
7
|
+
* actually differs — a new fetch starts (the old one is cancelled via
|
|
8
|
+
* AbortController).
|
|
9
|
+
* 2. Data is cached by key in a global Map.
|
|
10
|
+
* 3. resource returns three signals: data/error/loading, plus
|
|
11
|
+
* refresh()/mutate()/invalidate().
|
|
12
|
+
*
|
|
13
|
+
* mutation(fetcher) — a wrapper for POST/PUT/DELETE. After a successful run
|
|
14
|
+
* it can invalidate specified resource keys (exact match or prefix-glob 'users/*').
|
|
15
|
+
*
|
|
16
|
+
* No runtime dependencies: only fetch + AbortController + signals.
|
|
17
|
+
*/
|
|
18
|
+
import { type Signal } from "./signal.js";
|
|
19
|
+
/**
|
|
20
|
+
* Remove from cache all keys matching the pattern, and force
|
|
21
|
+
* all live resource() instances with that key to re-fetch.
|
|
22
|
+
*
|
|
23
|
+
* Only a trailing `*` is supported for prefix-match:
|
|
24
|
+
* invalidate('users/42') → exact match
|
|
25
|
+
* invalidate('users/*') → everything starting with 'users/'
|
|
26
|
+
* invalidate('*') → drop the ENTIRE cache (use intentionally)
|
|
27
|
+
*
|
|
28
|
+
* NOT supported: glob-in-middle (`users/* /posts`), regex, multi-star.
|
|
29
|
+
* For more complex cases call invalidate() multiple times with different prefixes,
|
|
30
|
+
* or iterate your own keys manually.
|
|
31
|
+
*/
|
|
32
|
+
export declare function invalidate(pattern: string): void;
|
|
33
|
+
export interface ResourceOptions {
|
|
34
|
+
/** How many ms the data is considered fresh (fetch is skipped). */
|
|
35
|
+
staleTime?: number;
|
|
36
|
+
/** Initial value shown immediately. */
|
|
37
|
+
initialData?: unknown;
|
|
38
|
+
}
|
|
39
|
+
export interface Resource<T> {
|
|
40
|
+
/** Signal: data or undefined */
|
|
41
|
+
data: () => T | undefined;
|
|
42
|
+
/** Signal: error or null */
|
|
43
|
+
error: () => Error | null;
|
|
44
|
+
/** Signal: whether a request is in progress */
|
|
45
|
+
loading: () => boolean;
|
|
46
|
+
/** Signal: current key (useful for debugging and DI) */
|
|
47
|
+
key: () => string;
|
|
48
|
+
/** Force re-run the request. */
|
|
49
|
+
refresh(): void;
|
|
50
|
+
/**
|
|
51
|
+
* Locally replace the data (optimistic update).
|
|
52
|
+
* The cache is also updated.
|
|
53
|
+
*/
|
|
54
|
+
mutate(next: T | ((prev: T | undefined) => T)): void;
|
|
55
|
+
}
|
|
56
|
+
export declare function resource<T>(keyFn: () => string, fetcher: (key: string, signal: AbortSignal) => Promise<T>, options?: ResourceOptions): Resource<T>;
|
|
57
|
+
export interface MutationOptions<TArgs = unknown, TResult = unknown> {
|
|
58
|
+
/**
|
|
59
|
+
* Invalidate cache by these patterns after success.
|
|
60
|
+
*
|
|
61
|
+
* Can be:
|
|
62
|
+
* - a static array: `['users/*']`
|
|
63
|
+
* - a function of result and args:
|
|
64
|
+
* `(result, args) => [${'`'}posts/${result.id}${'`'}, 'feed/*']`
|
|
65
|
+
*
|
|
66
|
+
* The function is called AFTER a successful request. If it throws — the error
|
|
67
|
+
* is logged to console, but the mutation success is preserved (invalidation is best-effort).
|
|
68
|
+
*
|
|
69
|
+
* Only `*` at the END of a pattern is supported (see invalidate()).
|
|
70
|
+
*/
|
|
71
|
+
invalidates?: readonly string[] | ((result: TResult, args: TArgs) => readonly string[]);
|
|
72
|
+
}
|
|
73
|
+
export interface Mutation<TArgs, TResult> {
|
|
74
|
+
/** Signal: request in progress */
|
|
75
|
+
loading: Signal<boolean>;
|
|
76
|
+
/** Signal: error */
|
|
77
|
+
error: Signal<Error | null>;
|
|
78
|
+
/** Signal: last received data */
|
|
79
|
+
data: Signal<TResult | undefined>;
|
|
80
|
+
/** Execute. Returns a Promise. */
|
|
81
|
+
run(args: TArgs): Promise<TResult>;
|
|
82
|
+
/** Reset error/data state. */
|
|
83
|
+
reset(): void;
|
|
84
|
+
}
|
|
85
|
+
export declare function mutation<TArgs, TResult>(fetcher: (args: TArgs, signal: AbortSignal) => Promise<TResult>, options?: MutationOptions<TArgs, TResult>): Mutation<TArgs, TResult>;
|
|
86
|
+
/**
|
|
87
|
+
* Extended HTTP error thrown by jsonFetcher() on `!response.ok`.
|
|
88
|
+
*
|
|
89
|
+
* Unlike a plain `Error("HTTP 422")`, it preserves:
|
|
90
|
+
* - `status` / `statusText` — for UI discrimination ("422 → show form errors")
|
|
91
|
+
* - `url` — which endpoint failed
|
|
92
|
+
* - `body` — parsed response body (JSON if possible, then text, then null)
|
|
93
|
+
*
|
|
94
|
+
* try {
|
|
95
|
+
* await api.save(user);
|
|
96
|
+
* } catch (err) {
|
|
97
|
+
* if (err instanceof HttpError && err.status === 422) {
|
|
98
|
+
* // err.body may contain { errors: { email: 'taken' } }
|
|
99
|
+
* }
|
|
100
|
+
* }
|
|
101
|
+
*/
|
|
102
|
+
export declare class HttpError extends Error {
|
|
103
|
+
readonly status: number;
|
|
104
|
+
readonly statusText: string;
|
|
105
|
+
readonly url: string;
|
|
106
|
+
readonly body: unknown;
|
|
107
|
+
constructor(status: number, statusText: string, url: string, body: unknown);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Simple JSON fetcher for resource. Throws HttpError on `!response.ok`,
|
|
111
|
+
* with a parsed body (JSON → text → null) for proper UI error handling.
|
|
112
|
+
*
|
|
113
|
+
* const user = resource(() => `/api/users/${id()}`, jsonFetcher());
|
|
114
|
+
*/
|
|
115
|
+
export declare function jsonFetcher<T>(init?: RequestInit): (url: string, signal: AbortSignal) => Promise<T>;
|
|
116
|
+
export declare const _testHooks: {
|
|
117
|
+
invalidatorsSize(): number;
|
|
118
|
+
cacheSize(): number;
|
|
119
|
+
clearCache(): void;
|
|
120
|
+
};
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactive fetch + cache. Replaces React-Query / SWR.
|
|
3
|
+
*
|
|
4
|
+
* Core idea:
|
|
5
|
+
* 1. resource(keyFn, fetcher) — keyFn reads signals like a normal effect.
|
|
6
|
+
* When dependencies change the key is recomputed, and if the key
|
|
7
|
+
* actually differs — a new fetch starts (the old one is cancelled via
|
|
8
|
+
* AbortController).
|
|
9
|
+
* 2. Data is cached by key in a global Map.
|
|
10
|
+
* 3. resource returns three signals: data/error/loading, plus
|
|
11
|
+
* refresh()/mutate()/invalidate().
|
|
12
|
+
*
|
|
13
|
+
* mutation(fetcher) — a wrapper for POST/PUT/DELETE. After a successful run
|
|
14
|
+
* it can invalidate specified resource keys (exact match or prefix-glob 'users/*').
|
|
15
|
+
*
|
|
16
|
+
* No runtime dependencies: only fetch + AbortController + signals.
|
|
17
|
+
*/
|
|
18
|
+
import { signal, effect, untracked } from "./signal.js";
|
|
19
|
+
import { getCurrentLifecycle } from "./lifecycle.js";
|
|
20
|
+
import { warnOnce } from "./diagnostics.js";
|
|
21
|
+
const cache = new Map();
|
|
22
|
+
const invalidators = new Set();
|
|
23
|
+
/**
|
|
24
|
+
* Remove from cache all keys matching the pattern, and force
|
|
25
|
+
* all live resource() instances with that key to re-fetch.
|
|
26
|
+
*
|
|
27
|
+
* Only a trailing `*` is supported for prefix-match:
|
|
28
|
+
* invalidate('users/42') → exact match
|
|
29
|
+
* invalidate('users/*') → everything starting with 'users/'
|
|
30
|
+
* invalidate('*') → drop the ENTIRE cache (use intentionally)
|
|
31
|
+
*
|
|
32
|
+
* NOT supported: glob-in-middle (`users/* /posts`), regex, multi-star.
|
|
33
|
+
* For more complex cases call invalidate() multiple times with different prefixes,
|
|
34
|
+
* or iterate your own keys manually.
|
|
35
|
+
*/
|
|
36
|
+
export function invalidate(pattern) {
|
|
37
|
+
const isGlob = pattern.endsWith("*");
|
|
38
|
+
const prefix = isGlob ? pattern.slice(0, -1) : pattern;
|
|
39
|
+
const toDelete = [];
|
|
40
|
+
for (const key of cache.keys()) {
|
|
41
|
+
if (isGlob ? key.startsWith(prefix) : key === pattern) {
|
|
42
|
+
toDelete.push(key);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
for (const k of toDelete)
|
|
46
|
+
cache.delete(k);
|
|
47
|
+
for (const fn of invalidators) {
|
|
48
|
+
for (const k of toDelete)
|
|
49
|
+
fn(k);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function resource(keyFn, fetcher, options = {}) {
|
|
53
|
+
const data = signal(options.initialData);
|
|
54
|
+
const error = signal(null);
|
|
55
|
+
const loading = signal(false);
|
|
56
|
+
const keySig = signal("");
|
|
57
|
+
let abort = null;
|
|
58
|
+
let lastKey = "";
|
|
59
|
+
let force = false;
|
|
60
|
+
// if inside component-setup — auto-cleanup on unmount.
|
|
61
|
+
// if outside — print a warning so the developer knows
|
|
62
|
+
// about the potential leak.
|
|
63
|
+
const lifecycle = getCurrentLifecycle();
|
|
64
|
+
if (!lifecycle) {
|
|
65
|
+
warnOnce("resource-outside-lifecycle", "resource() called outside of component-setup. " +
|
|
66
|
+
"Invalidator subscriptions will not be cleaned up automatically — " +
|
|
67
|
+
"this is a leak. Use resource() inside component(...), or " +
|
|
68
|
+
"manage the lifecycle manually via createLifecycle()/runInLifecycle().");
|
|
69
|
+
}
|
|
70
|
+
const run = (key) => {
|
|
71
|
+
abort?.abort();
|
|
72
|
+
const ac = new AbortController();
|
|
73
|
+
abort = ac;
|
|
74
|
+
// if there is a fresh cache and not forced — use it
|
|
75
|
+
const cached = cache.get(key);
|
|
76
|
+
if (cached &&
|
|
77
|
+
!force &&
|
|
78
|
+
(!options.staleTime || Date.now() - cached.timestamp < options.staleTime)) {
|
|
79
|
+
data.set(cached.data);
|
|
80
|
+
error.set(null);
|
|
81
|
+
loading.set(false);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
loading.set(true);
|
|
85
|
+
error.set(null);
|
|
86
|
+
force = false;
|
|
87
|
+
fetcher(key, ac.signal).then((result) => {
|
|
88
|
+
if (ac.signal.aborted)
|
|
89
|
+
return;
|
|
90
|
+
cache.set(key, { data: result, timestamp: Date.now() });
|
|
91
|
+
data.set(result);
|
|
92
|
+
loading.set(false);
|
|
93
|
+
}, (err) => {
|
|
94
|
+
if (ac.signal.aborted)
|
|
95
|
+
return;
|
|
96
|
+
error.set(err instanceof Error ? err : new Error(String(err)));
|
|
97
|
+
loading.set(false);
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
// subscribe to key changes
|
|
101
|
+
const stopKeyEffect = effect(() => {
|
|
102
|
+
const key = keyFn();
|
|
103
|
+
keySig.set(key);
|
|
104
|
+
if (key !== lastKey || force) {
|
|
105
|
+
lastKey = key;
|
|
106
|
+
run(key);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
// subscribe to global invalidation
|
|
110
|
+
const onInv = (invKey) => {
|
|
111
|
+
if (invKey === lastKey) {
|
|
112
|
+
force = true;
|
|
113
|
+
run(lastKey);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
invalidators.add(onInv);
|
|
117
|
+
// auto-cleanup if inside a component
|
|
118
|
+
if (lifecycle) {
|
|
119
|
+
lifecycle.onDispose(() => {
|
|
120
|
+
stopKeyEffect();
|
|
121
|
+
invalidators.delete(onInv);
|
|
122
|
+
abort?.abort();
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
data,
|
|
127
|
+
error,
|
|
128
|
+
loading,
|
|
129
|
+
key: keySig,
|
|
130
|
+
refresh() {
|
|
131
|
+
force = true;
|
|
132
|
+
// read key without tracking — otherwise we'd end up inside someone else's effect
|
|
133
|
+
const key = untracked(keyFn);
|
|
134
|
+
run(key);
|
|
135
|
+
},
|
|
136
|
+
mutate(next) {
|
|
137
|
+
const prev = data.peek();
|
|
138
|
+
const value = typeof next === "function"
|
|
139
|
+
? next(prev)
|
|
140
|
+
: next;
|
|
141
|
+
data.set(value);
|
|
142
|
+
if (lastKey)
|
|
143
|
+
cache.set(lastKey, { data: value, timestamp: Date.now() });
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
export function mutation(fetcher, options = {}) {
|
|
148
|
+
const loading = signal(false);
|
|
149
|
+
const error = signal(null);
|
|
150
|
+
const data = signal(undefined);
|
|
151
|
+
let abort = null;
|
|
152
|
+
return {
|
|
153
|
+
loading,
|
|
154
|
+
error,
|
|
155
|
+
data,
|
|
156
|
+
async run(args) {
|
|
157
|
+
abort?.abort();
|
|
158
|
+
const ac = new AbortController();
|
|
159
|
+
abort = ac;
|
|
160
|
+
loading.set(true);
|
|
161
|
+
error.set(null);
|
|
162
|
+
try {
|
|
163
|
+
const result = await fetcher(args, ac.signal);
|
|
164
|
+
if (ac.signal.aborted) {
|
|
165
|
+
throw new DOMException("aborted", "AbortError");
|
|
166
|
+
}
|
|
167
|
+
data.set(result);
|
|
168
|
+
loading.set(false);
|
|
169
|
+
const inv = options.invalidates;
|
|
170
|
+
if (inv) {
|
|
171
|
+
let patterns = [];
|
|
172
|
+
try {
|
|
173
|
+
patterns = typeof inv === "function" ? inv(result, args) : inv;
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
// Invalidation is best-effort. Don't fail the mutation because of it.
|
|
177
|
+
// eslint-disable-next-line no-console
|
|
178
|
+
console.error("[mado] mutation.invalidates threw:", err);
|
|
179
|
+
}
|
|
180
|
+
for (const p of patterns)
|
|
181
|
+
invalidate(p);
|
|
182
|
+
}
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
if (!ac.signal.aborted) {
|
|
187
|
+
error.set(err instanceof Error ? err : new Error(String(err)));
|
|
188
|
+
loading.set(false);
|
|
189
|
+
}
|
|
190
|
+
throw err;
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
reset() {
|
|
194
|
+
abort?.abort();
|
|
195
|
+
loading.set(false);
|
|
196
|
+
error.set(null);
|
|
197
|
+
data.set(undefined);
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
// ---------- Utilities ----------
|
|
202
|
+
/**
|
|
203
|
+
* Extended HTTP error thrown by jsonFetcher() on `!response.ok`.
|
|
204
|
+
*
|
|
205
|
+
* Unlike a plain `Error("HTTP 422")`, it preserves:
|
|
206
|
+
* - `status` / `statusText` — for UI discrimination ("422 → show form errors")
|
|
207
|
+
* - `url` — which endpoint failed
|
|
208
|
+
* - `body` — parsed response body (JSON if possible, then text, then null)
|
|
209
|
+
*
|
|
210
|
+
* try {
|
|
211
|
+
* await api.save(user);
|
|
212
|
+
* } catch (err) {
|
|
213
|
+
* if (err instanceof HttpError && err.status === 422) {
|
|
214
|
+
* // err.body may contain { errors: { email: 'taken' } }
|
|
215
|
+
* }
|
|
216
|
+
* }
|
|
217
|
+
*/
|
|
218
|
+
export class HttpError extends Error {
|
|
219
|
+
status;
|
|
220
|
+
statusText;
|
|
221
|
+
url;
|
|
222
|
+
body;
|
|
223
|
+
constructor(status, statusText, url, body) {
|
|
224
|
+
super(`HTTP ${status} ${statusText} ${url}`);
|
|
225
|
+
this.name = "HttpError";
|
|
226
|
+
this.status = status;
|
|
227
|
+
this.statusText = statusText;
|
|
228
|
+
this.url = url;
|
|
229
|
+
this.body = body;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Simple JSON fetcher for resource. Throws HttpError on `!response.ok`,
|
|
234
|
+
* with a parsed body (JSON → text → null) for proper UI error handling.
|
|
235
|
+
*
|
|
236
|
+
* const user = resource(() => `/api/users/${id()}`, jsonFetcher());
|
|
237
|
+
*/
|
|
238
|
+
export function jsonFetcher(init = {}) {
|
|
239
|
+
return async (url, signal) => {
|
|
240
|
+
const res = await fetch(url, { ...init, signal });
|
|
241
|
+
if (!res.ok) {
|
|
242
|
+
let body = null;
|
|
243
|
+
// Try to read the body: JSON first, then text. Don't fail if that doesn't work.
|
|
244
|
+
try {
|
|
245
|
+
const ct = res.headers.get("content-type") ?? "";
|
|
246
|
+
if (ct.includes("application/json")) {
|
|
247
|
+
body = await res.json();
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
body = await res.text();
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
body = null;
|
|
255
|
+
}
|
|
256
|
+
throw new HttpError(res.status, res.statusText, url, body);
|
|
257
|
+
}
|
|
258
|
+
return (await res.json());
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
// ---------- Test hooks ----------
|
|
262
|
+
//
|
|
263
|
+
// Not public API. Used by tests to inspect lifecycle cleanup.
|
|
264
|
+
export const _testHooks = {
|
|
265
|
+
invalidatorsSize() {
|
|
266
|
+
return invalidators.size;
|
|
267
|
+
},
|
|
268
|
+
cacheSize() {
|
|
269
|
+
return cache.size;
|
|
270
|
+
},
|
|
271
|
+
clearCache() {
|
|
272
|
+
cache.clear();
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
//# sourceMappingURL=resource.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource.js","sourceRoot":"","sources":["../../src/resource.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* routes() — high-level manifest router with lazy loading, layouts,
|
|
3
|
+
* prefetch and a sync-fast-path for already-loaded pages.
|
|
4
|
+
*
|
|
5
|
+
* On top of the raw router() from navigation.ts this adds:
|
|
6
|
+
* - dynamic-import loaders (code splitting via `() => import(...)`),
|
|
7
|
+
* - nested routes with layouts (via page.ts: nested({ layout, routes })),
|
|
8
|
+
* - per-instance module cache (not global — two routes() calls
|
|
9
|
+
* in the same process do NOT interfere),
|
|
10
|
+
* - hover prefetch and programmatic prefetchPath(),
|
|
11
|
+
* - smart loadingDelay (no progress-bar flicker on fast networks),
|
|
12
|
+
* - sync-fast-path: if the page is already in cache — renders synchronously,
|
|
13
|
+
* without loading state and without a microtask. Removes flicker on back/forward.
|
|
14
|
+
*/
|
|
15
|
+
import type { TemplateResult } from "../html/template-types.js";
|
|
16
|
+
import { type RoutesMap } from "./match.js";
|
|
17
|
+
import { type RouterApi } from "./navigation.js";
|
|
18
|
+
export interface RoutesOptions {
|
|
19
|
+
/**
|
|
20
|
+
* TemplateResult while the module is loading. Default — thin
|
|
21
|
+
* progress bar at the top (see defaultLoadingView). If the page is in cache,
|
|
22
|
+
* loading is not shown at all (sync render).
|
|
23
|
+
*/
|
|
24
|
+
loading?: () => TemplateResult;
|
|
25
|
+
/** TemplateResult if the import threw. */
|
|
26
|
+
error?: (err: Error) => TemplateResult;
|
|
27
|
+
/** Prefix for document.title (e.g. ' · MyApp'). */
|
|
28
|
+
titleSuffix?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Delay before showing the loading view in ms. If loading
|
|
31
|
+
* finishes faster — loading is not shown, the ready page renders immediately.
|
|
32
|
+
* Guards against flicker on fast connections.
|
|
33
|
+
* Default 100ms. Set to 0 to disable.
|
|
34
|
+
*/
|
|
35
|
+
loadingDelay?: number;
|
|
36
|
+
/**
|
|
37
|
+
* Use View Transitions API on navigation (smooth crossfade).
|
|
38
|
+
* Default true.
|
|
39
|
+
*/
|
|
40
|
+
viewTransitions?: boolean;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Create a router from a manifest. Returns the same RouterApi as router().
|
|
44
|
+
*/
|
|
45
|
+
export declare function routes(manifest: RoutesMap, options?: RoutesOptions): RouterApi;
|
|
46
|
+
/**
|
|
47
|
+
* Prefetch-load modules for a path (hover, programmatic).
|
|
48
|
+
* Safe to call repeatedly — cached.
|
|
49
|
+
*
|
|
50
|
+
* Iterates all active routes() and starts loaders for matched entries.
|
|
51
|
+
* If there is no active routes() — no-op.
|
|
52
|
+
*/
|
|
53
|
+
export declare function prefetchPath(pathname: string): void;
|
|
54
|
+
export declare const _testHooks: {
|
|
55
|
+
activeRoutesSize(): number;
|
|
56
|
+
};
|