@madojs/mado 0.11.1 → 0.13.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 +116 -32
- package/CHANGELOG.md +216 -1
- package/README.md +158 -173
- package/dist/src/component.d.ts +6 -3
- package/dist/src/component.d.ts.map +1 -0
- package/dist/src/component.js +163 -36
- package/dist/src/component.js.map +1 -1
- package/dist/src/context.d.ts +13 -32
- package/dist/src/context.d.ts.map +1 -0
- package/dist/src/context.js +73 -48
- package/dist/src/context.js.map +1 -1
- package/dist/src/css.d.ts +1 -0
- package/dist/src/css.d.ts.map +1 -0
- package/dist/src/css.js +34 -5
- package/dist/src/css.js.map +1 -1
- package/dist/src/devtools-hook.d.ts +14 -0
- package/dist/src/devtools-hook.d.ts.map +1 -0
- package/dist/src/devtools-hook.js +27 -0
- package/dist/src/devtools-hook.js.map +1 -0
- package/dist/src/devtools.d.ts +24 -22
- package/dist/src/devtools.d.ts.map +1 -0
- package/dist/src/devtools.js +230 -58
- package/dist/src/devtools.js.map +1 -1
- package/dist/src/diagnostics.d.ts +12 -0
- package/dist/src/diagnostics.d.ts.map +1 -0
- package/dist/src/diagnostics.js +68 -8
- package/dist/src/diagnostics.js.map +1 -1
- package/dist/src/each.d.ts +1 -5
- package/dist/src/each.d.ts.map +1 -0
- package/dist/src/each.js +0 -8
- package/dist/src/each.js.map +1 -1
- package/dist/src/forms.d.ts +27 -96
- package/dist/src/forms.d.ts.map +1 -0
- package/dist/src/forms.js +184 -394
- package/dist/src/forms.js.map +1 -1
- package/dist/src/head.d.ts +7 -6
- package/dist/src/head.d.ts.map +1 -0
- package/dist/src/head.js +8 -7
- package/dist/src/head.js.map +1 -1
- package/dist/src/html/bindings.d.ts +1 -0
- package/dist/src/html/bindings.d.ts.map +1 -0
- package/dist/src/html/bindings.js.map +1 -1
- package/dist/src/html/parser.d.ts +4 -5
- package/dist/src/html/parser.d.ts.map +1 -0
- package/dist/src/html/parser.js +64 -15
- package/dist/src/html/parser.js.map +1 -1
- package/dist/src/html/template-types.d.ts +1 -0
- package/dist/src/html/template-types.d.ts.map +1 -0
- package/dist/src/html/template-types.js.map +1 -1
- package/dist/src/html/template.d.ts +5 -1
- package/dist/src/html/template.d.ts.map +1 -0
- package/dist/src/html/template.js +102 -13
- package/dist/src/html/template.js.map +1 -1
- package/dist/src/index.d.ts +15 -12
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +18 -10
- package/dist/src/index.js.map +1 -1
- package/dist/src/json.d.ts +3 -0
- package/dist/src/json.d.ts.map +1 -0
- package/dist/src/json.js +14 -0
- package/dist/src/json.js.map +1 -0
- package/dist/src/lifecycle.d.ts +1 -0
- package/dist/src/lifecycle.d.ts.map +1 -0
- package/dist/src/lifecycle.js +3 -2
- package/dist/src/lifecycle.js.map +1 -1
- package/dist/src/page.d.ts +63 -28
- package/dist/src/page.d.ts.map +1 -0
- package/dist/src/page.js +5 -0
- package/dist/src/page.js.map +1 -1
- package/dist/src/persisted.d.ts +6 -1
- package/dist/src/persisted.d.ts.map +1 -0
- package/dist/src/persisted.js +17 -11
- package/dist/src/persisted.js.map +1 -1
- package/dist/src/resource.d.ts +2 -1
- package/dist/src/resource.d.ts.map +1 -0
- package/dist/src/resource.js +128 -50
- package/dist/src/resource.js.map +1 -1
- package/dist/src/router/base.d.ts +66 -0
- package/dist/src/router/base.d.ts.map +1 -0
- package/dist/src/router/base.js +168 -0
- package/dist/src/router/base.js.map +1 -0
- package/dist/src/router/manifest.d.ts +1 -0
- package/dist/src/router/manifest.d.ts.map +1 -0
- package/dist/src/router/manifest.js +156 -171
- package/dist/src/router/manifest.js.map +1 -1
- package/dist/src/router/match.d.ts +17 -2
- package/dist/src/router/match.d.ts.map +1 -0
- package/dist/src/router/match.js +60 -6
- package/dist/src/router/match.js.map +1 -1
- package/dist/src/router/navigation.d.ts +1 -0
- package/dist/src/router/navigation.d.ts.map +1 -0
- package/dist/src/router/navigation.js +58 -9
- package/dist/src/router/navigation.js.map +1 -1
- package/dist/src/signal.d.ts +1 -0
- package/dist/src/signal.d.ts.map +1 -0
- package/dist/src/signal.js +46 -17
- package/dist/src/signal.js.map +1 -1
- package/dist/src/static-runtime.d.ts +82 -0
- package/dist/src/static-runtime.d.ts.map +1 -0
- package/dist/src/static-runtime.js +209 -0
- package/dist/src/static-runtime.js.map +1 -0
- package/dist/src/vite/index.d.ts +32 -3
- package/dist/src/vite/index.d.ts.map +1 -0
- package/dist/src/vite/index.js +49 -17
- package/dist/src/vite/index.js.map +1 -1
- package/docs/README.md +5 -8
- package/docs/architecture/adr/0001-browser-static-snapshots.md +132 -0
- package/docs/architecture/v1-roadmap.md +19 -0
- package/docs/en/00-the-mado-way.md +1 -1
- package/docs/en/01-quickstart.md +183 -0
- package/docs/en/10-pages-and-components.md +279 -0
- package/docs/en/11-templates-and-signals.md +211 -0
- package/docs/en/12-routing.md +229 -0
- package/docs/en/13-data.md +227 -0
- package/docs/en/14-forms.md +91 -0
- package/docs/en/15-static-snapshots.md +181 -0
- package/docs/en/{10-app-architecture.md → 16-app-architecture.md} +56 -4
- package/docs/en/{13-deployment.md → 20-deployment.md} +79 -17
- package/docs/en/{15-error-handling.md → 21-error-handling.md} +9 -6
- package/docs/en/{14-testing.md → 22-testing.md} +6 -5
- package/docs/en/23-cookbook.md +292 -0
- package/docs/en/24-devtools-and-diagnostics.md +45 -0
- package/docs/en/{18-api-freeze-map.md → 30-api-freeze-map.md} +17 -9
- package/docs/en/{20-v1-stability.md → 32-v1-stability.md} +5 -5
- package/docs/en/33-migration-0.12-0.13.md +56 -0
- package/docs/en/40-llm-guide.md +774 -0
- package/docs/en/{06-for-backenders.md → 41-for-backenders.md} +28 -25
- package/docs/en/{05-why-mado.md → 42-why-mado.md} +3 -3
- package/docs/en/README.md +99 -27
- package/docs/recipes/nginx/Containerfile +26 -0
- package/docs/recipes/nginx/nginx.conf +42 -0
- package/llms.txt +263 -210
- package/package.json +51 -23
- package/scripts/clean.mjs +8 -0
- package/scripts/cli/generate.mjs +57 -10
- package/scripts/cli/help.mjs +13 -5
- package/scripts/cli/index.mjs +31 -7
- package/scripts/cli/init.mjs +19 -19
- package/scripts/cli/release.mjs +38 -83
- package/scripts/cli/run.mjs +5 -3
- package/scripts/logger.mjs +117 -0
- package/scripts/output-guard.mjs +128 -0
- package/scripts/preview.mjs +105 -24
- package/scripts/static/browser.mjs +695 -0
- package/scripts/static/discover.mjs +284 -0
- package/scripts/static/output.mjs +141 -0
- package/scripts/static/serialize.mjs +212 -0
- package/scripts/static/server.mjs +167 -0
- package/scripts/static.mjs +205 -0
- package/starters/default/README.md +57 -55
- package/starters/default/package.json +6 -12
- package/starters/default/src/app.routes.ts +20 -33
- package/starters/default/src/components/feature-card.component.ts +40 -0
- package/starters/default/src/components/live-counter.component.ts +57 -0
- package/starters/default/src/content/guides.ts +55 -0
- package/starters/default/src/main.ts +9 -13
- package/starters/default/src/pages/app.page.ts +41 -0
- package/starters/default/src/pages/guide.page.ts +40 -0
- package/starters/default/src/pages/home.page.ts +49 -0
- package/starters/default/src/pages/not-found.page.ts +18 -0
- package/starters/default/src/styles/document.css +39 -0
- package/starters/default/src/styles/reset.css +37 -0
- package/starters/default/src/styles/tokens.css +30 -0
- package/starters/default/src/styles.d.ts +3 -1
- package/starters/default/src/vite-env.d.ts +1 -1
- package/starters/default/tsconfig.node.json +13 -0
- package/starters/default/vite.config.ts +14 -4
- package/starters/modular/README.md +142 -0
- package/starters/modular/index.html +13 -0
- package/starters/modular/package.json +30 -0
- package/starters/modular/src/app.routes.ts +39 -0
- package/starters/{default → modular}/src/layouts/app-shell.layout.ts +9 -7
- package/starters/{default → modular}/src/layouts/auth-shell.layout.ts +3 -3
- package/starters/modular/src/main.ts +16 -0
- package/starters/{default → modular}/src/modules/auth/login.page.ts +5 -12
- package/starters/{default → modular}/src/modules/billing/pages/invoice-detail.page.ts +3 -4
- package/starters/{default → modular}/src/modules/billing/pages/invoices-list.page.ts +4 -4
- package/starters/{default → modular}/src/modules/home/home.page.ts +5 -7
- package/starters/modular/src/modules/home/not-found.page.ts +14 -0
- package/starters/{default → modular}/src/shared/styles/reset.css +21 -1
- package/starters/modular/src/styles.d.ts +1 -0
- package/starters/modular/src/vite-env.d.ts +1 -0
- package/starters/modular/tsconfig.json +33 -0
- package/starters/modular/tsconfig.node.json +13 -0
- package/starters/modular/vite.config.ts +132 -0
- package/TODO.md +0 -79
- package/dist/src/lazy.d.ts +0 -38
- package/dist/src/lazy.js +0 -73
- package/dist/src/lazy.js.map +0 -1
- package/docs/en/01-routing.md +0 -152
- package/docs/en/02-project-layout.md +0 -124
- package/docs/en/03-static-bake.md +0 -249
- package/docs/en/04-ide-setup.md +0 -162
- package/docs/en/07-llm-pitfalls.md +0 -724
- package/docs/en/08-llm-zero-history-test.md +0 -56
- package/docs/en/09-shadow-vs-light-dom.md +0 -174
- package/docs/en/11-layouts.md +0 -113
- package/docs/en/12-auth-and-api.md +0 -124
- package/docs/en/16-bake-cookbook.md +0 -100
- package/docs/en/17-shadow-dom-forms.md +0 -192
- package/docs/fr/00-the-mado-way.md +0 -85
- package/docs/fr/01-routing.md +0 -120
- package/docs/fr/02-project-layout.md +0 -84
- package/docs/fr/03-static-bake.md +0 -289
- package/docs/fr/04-ide-setup.md +0 -162
- package/docs/fr/05-why-mado.md +0 -193
- package/docs/fr/06-for-backenders.md +0 -438
- package/docs/fr/07-llm-pitfalls.md +0 -625
- package/docs/fr/08-llm-zero-history-test.md +0 -38
- package/docs/fr/09-shadow-vs-light-dom.md +0 -65
- package/docs/fr/10-app-architecture.md +0 -138
- package/docs/fr/11-layouts.md +0 -47
- package/docs/fr/12-auth-and-api.md +0 -76
- package/docs/fr/13-deployment.md +0 -57
- package/docs/fr/14-testing.md +0 -41
- package/docs/fr/15-error-handling.md +0 -50
- package/docs/fr/16-bake-cookbook.md +0 -88
- package/docs/fr/17-shadow-dom-forms.md +0 -196
- package/docs/fr/18-api-freeze-map.md +0 -63
- package/docs/fr/19-reactivity-ordering.md +0 -97
- package/docs/fr/20-v1-stability.md +0 -88
- package/docs/fr/README.md +0 -27
- package/docs/ru/00-the-mado-way.md +0 -84
- package/docs/ru/01-routing.md +0 -119
- package/docs/ru/02-project-layout.md +0 -84
- package/docs/ru/03-static-bake.md +0 -250
- package/docs/ru/04-ide-setup.md +0 -144
- package/docs/ru/05-why-mado.md +0 -193
- package/docs/ru/06-for-backenders.md +0 -428
- package/docs/ru/07-llm-pitfalls.md +0 -624
- package/docs/ru/08-llm-zero-history-test.md +0 -57
- package/docs/ru/09-shadow-vs-light-dom.md +0 -63
- package/docs/ru/10-app-architecture.md +0 -152
- package/docs/ru/11-layouts.md +0 -47
- package/docs/ru/12-auth-and-api.md +0 -75
- package/docs/ru/13-deployment.md +0 -66
- package/docs/ru/14-testing.md +0 -50
- package/docs/ru/15-error-handling.md +0 -56
- package/docs/ru/16-bake-cookbook.md +0 -95
- package/docs/ru/17-shadow-dom-forms.md +0 -193
- package/docs/ru/18-api-freeze-map.md +0 -64
- package/docs/ru/19-reactivity-ordering.md +0 -95
- package/docs/ru/20-v1-stability.md +0 -82
- package/docs/ru/README.md +0 -25
- package/docs/uk/00-the-mado-way.md +0 -82
- package/docs/uk/01-routing.md +0 -76
- package/docs/uk/02-project-layout.md +0 -73
- package/docs/uk/03-static-bake.md +0 -48
- package/docs/uk/04-ide-setup.md +0 -26
- package/docs/uk/05-why-mado.md +0 -34
- package/docs/uk/06-for-backenders.md +0 -55
- package/docs/uk/07-llm-pitfalls.md +0 -145
- package/docs/uk/08-llm-zero-history-test.md +0 -34
- package/docs/uk/09-shadow-vs-light-dom.md +0 -58
- package/docs/uk/10-app-architecture.md +0 -97
- package/docs/uk/11-layouts.md +0 -47
- package/docs/uk/12-auth-and-api.md +0 -70
- package/docs/uk/13-deployment.md +0 -40
- package/docs/uk/14-testing.md +0 -34
- package/docs/uk/15-error-handling.md +0 -32
- package/docs/uk/16-bake-cookbook.md +0 -36
- package/docs/uk/17-shadow-dom-forms.md +0 -193
- package/docs/uk/18-api-freeze-map.md +0 -61
- package/docs/uk/19-reactivity-ordering.md +0 -95
- package/docs/uk/20-v1-stability.md +0 -83
- package/docs/uk/README.md +0 -27
- package/scripts/bake.mjs +0 -570
- package/scripts/package-smoke.mjs +0 -77
- package/scripts/release-notes.mjs +0 -66
- package/scripts/size-budget.mjs +0 -91
- package/starters/default/src/modules/home/not-found.page.ts +0 -11
- package/starters/default/src/shared/ui/x-button.component.ts +0 -49
- /package/docs/en/{19-reactivity-ordering.md → 31-reactivity-ordering.md} +0 -0
- /package/starters/{default → modular}/.editorconfig +0 -0
- /package/starters/{default → modular}/eslint.config.mjs +0 -0
- /package/starters/{default → modular}/public/favicon.svg +0 -0
- /package/starters/{default → modular}/src/modules/auth/_contracts/auth-api.types.ts +0 -0
- /package/starters/{default → modular}/src/modules/auth/auth.connector.ts +0 -0
- /package/starters/{default → modular}/src/modules/auth/auth.guard.ts +0 -0
- /package/starters/{default → modular}/src/modules/auth/auth.public.ts +0 -0
- /package/starters/{default → modular}/src/modules/auth/auth.routes.ts +0 -0
- /package/starters/{default → modular}/src/modules/auth/auth.service.ts +0 -0
- /package/starters/{default → modular}/src/modules/auth/auth.types.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/_contracts/stripe.types.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/api/stripe.connector.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/billing.public.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/billing.routes.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/billing.types.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/components/invoice-status-badge.component.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/data/invoices.resource.ts +0 -0
- /package/starters/{default → modular}/src/shared/http/http-client.ts +0 -0
- /package/starters/{default → modular}/src/shared/http/http-error.ts +0 -0
- /package/starters/{default → modular}/src/shared/http/interceptors.ts +0 -0
- /package/starters/{default → modular}/src/shared/lib/format-date.ts +0 -0
- /package/starters/{default → modular}/src/shared/styles/content.css +0 -0
- /package/starters/{default → modular}/src/shared/styles/shell.css +0 -0
- /package/starters/{default → modular}/src/shared/styles/tokens.css +0 -0
- /package/starters/{default → modular}/src/shared/ui/x-spinner.component.ts +0 -0
package/dist/src/page.d.ts
CHANGED
|
@@ -26,6 +26,17 @@
|
|
|
26
26
|
import type { TemplateResult } from "./html/template-types.js";
|
|
27
27
|
/** Route params (values from :placeholders). */
|
|
28
28
|
export type RouteParams = Record<string, string>;
|
|
29
|
+
/**
|
|
30
|
+
* JSON-compatible value. Static seeds and any data crossing the build-time →
|
|
31
|
+
* snapshot → client boundary must satisfy this contract: only primitives,
|
|
32
|
+
* plain arrays and plain object trees. Date, Map, Set, class instances,
|
|
33
|
+
* functions, undefined values and circular references are not allowed and
|
|
34
|
+
* fail at serialization time with a route-tagged error.
|
|
35
|
+
*/
|
|
36
|
+
export type JsonPrimitive = string | number | boolean | null;
|
|
37
|
+
export type JsonValue = JsonPrimitive | JsonValue[] | {
|
|
38
|
+
[key: string]: JsonValue;
|
|
39
|
+
};
|
|
29
40
|
/**
|
|
30
41
|
* Loaded data. If load returned a Resource — this is `Resource<T>`,
|
|
31
42
|
* otherwise — a plain value. The view decides what to do with it.
|
|
@@ -57,8 +68,8 @@ export interface PageContext<P extends RouteParams, D> {
|
|
|
57
68
|
onDispose?: (fn: () => void) => void;
|
|
58
69
|
}
|
|
59
70
|
/**
|
|
60
|
-
* Metadata for <head>.
|
|
61
|
-
*
|
|
71
|
+
* Metadata for <head>. Static snapshots write it into HTML, and the SPA
|
|
72
|
+
* runtime updates it on route changes.
|
|
62
73
|
*/
|
|
63
74
|
export interface HeadMeta {
|
|
64
75
|
/** If set — overrides page.title. */
|
|
@@ -97,27 +108,36 @@ export interface HeadMeta {
|
|
|
97
108
|
jsonLd?: unknown;
|
|
98
109
|
}
|
|
99
110
|
/**
|
|
100
|
-
*
|
|
101
|
-
* to
|
|
111
|
+
* Static snapshot configuration: gives the build script enough information
|
|
112
|
+
* to enumerate public routes and seed browser-rendered snapshots.
|
|
113
|
+
*
|
|
114
|
+
* `S` is the JSON-safe seed type (build-time → snapshot → client boundary).
|
|
115
|
+
* It is intentionally decoupled from the runtime `D` data type: a seed of
|
|
116
|
+
* type `Product` is fine even when `page.load()` returns `Resource<Product>`.
|
|
117
|
+
*
|
|
118
|
+
* `static.paths` and `static.initialData` MUST be browser-bundle-safe,
|
|
119
|
+
* build-executable, and secret-free. They are executed during build
|
|
120
|
+
* discovery, but their module also remains in the client bundle: do not
|
|
121
|
+
* import Node-only APIs, do not embed credentials, do not perform side
|
|
122
|
+
* effects at module top level.
|
|
102
123
|
*/
|
|
103
|
-
export interface
|
|
104
|
-
/**
|
|
105
|
-
* List of all params for which to bake a page.
|
|
106
|
-
* Return a ready array (may fetch from API).
|
|
107
|
-
*/
|
|
108
|
-
paths: () => Promise<P[]> | P[];
|
|
124
|
+
export interface StaticPageConfig<P extends RouteParams, S extends JsonValue> {
|
|
109
125
|
/**
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
126
|
+
* Route parameter sets to materialize.
|
|
127
|
+
*
|
|
128
|
+
* Optional for literal routes such as "/".
|
|
129
|
+
* Required for dynamic routes such as "/products/:slug".
|
|
114
130
|
*/
|
|
115
|
-
|
|
131
|
+
paths?: () => Promise<P[]> | P[];
|
|
116
132
|
/**
|
|
117
|
-
*
|
|
118
|
-
*
|
|
133
|
+
* Optional build-time seed. Passed to page.head(params, seed)
|
|
134
|
+
* and page.load(params, seed).
|
|
135
|
+
*
|
|
136
|
+
* Used both at build time (to render the snapshot) and at client boot
|
|
137
|
+
* (so the runtime does not re-fetch what the snapshot already knows).
|
|
138
|
+
* The seed is consumed exactly once per route commit.
|
|
119
139
|
*/
|
|
120
|
-
|
|
140
|
+
initialData?: (params: P) => Promise<S> | S;
|
|
121
141
|
}
|
|
122
142
|
/**
|
|
123
143
|
* Guard verdict.
|
|
@@ -149,21 +169,30 @@ export type Guard = (ctx: {
|
|
|
149
169
|
params: RouteParams;
|
|
150
170
|
path: string;
|
|
151
171
|
}) => GuardResult | Promise<GuardResult>;
|
|
152
|
-
export interface Page<P extends RouteParams = RouteParams, D = unknown> {
|
|
172
|
+
export interface Page<P extends RouteParams = RouteParams, D = unknown, S extends JsonValue = JsonValue> {
|
|
153
173
|
readonly _page: true;
|
|
154
174
|
title?: string | ((params: P) => string);
|
|
155
|
-
|
|
175
|
+
/**
|
|
176
|
+
* Runtime data loader. Receives params and the optional static seed
|
|
177
|
+
* (only present on the first client boot of a static route, never
|
|
178
|
+
* during SPA navigation).
|
|
179
|
+
*/
|
|
180
|
+
load?: (params: P, seed?: S) => D;
|
|
156
181
|
view: (ctx: PageContext<P, D>) => TemplateResult;
|
|
157
182
|
/**
|
|
158
|
-
* <head> metadata. Receives params and
|
|
159
|
-
*
|
|
183
|
+
* <head> metadata. Receives params and the optional static seed.
|
|
184
|
+
*
|
|
185
|
+
* `head` is intentionally built from `(params, seed)` and not from the
|
|
186
|
+
* runtime `load()` result: head must be deterministic and JSON-derivable
|
|
187
|
+
* so that it appears in the raw snapshot HTML for SEO and social
|
|
188
|
+
* crawlers.
|
|
160
189
|
*/
|
|
161
|
-
head?: (params: P,
|
|
190
|
+
head?: (params: P, seed?: S) => HeadMeta;
|
|
162
191
|
/**
|
|
163
|
-
* Static HTML
|
|
164
|
-
*
|
|
192
|
+
* Static HTML snapshot declaration. Used only by build-time discovery.
|
|
193
|
+
* Non-static routes remain ordinary SPA routes.
|
|
165
194
|
*/
|
|
166
|
-
|
|
195
|
+
static?: true | StaticPageConfig<P, S>;
|
|
167
196
|
/**
|
|
168
197
|
* Local error boundary. Catches errors from view() and load() of this page.
|
|
169
198
|
* If not set — the global `error` from routes() is used.
|
|
@@ -178,8 +207,13 @@ export interface Page<P extends RouteParams = RouteParams, D = unknown> {
|
|
|
178
207
|
/**
|
|
179
208
|
* Page factory. A typed wrapper over a plain object —
|
|
180
209
|
* needed only for type inference and the single "_page" stamp.
|
|
210
|
+
*
|
|
211
|
+
* The three generic parameters are inferred from `spec` in the common case:
|
|
212
|
+
* page({ static: true, view: () => html`...` })
|
|
213
|
+
* Provide them explicitly only for non-trivial shapes:
|
|
214
|
+
* page<{ slug: string }, Resource<Product>, Product>({ ... })
|
|
181
215
|
*/
|
|
182
|
-
export declare function page<P extends RouteParams = RouteParams, D = unknown>(spec: Omit<Page<P, D>, "_page">): Page<P, D>;
|
|
216
|
+
export declare function page<P extends RouteParams = RouteParams, D = unknown, S extends JsonValue = JsonValue>(spec: Omit<Page<P, D, S>, "_page">): Page<P, D, S>;
|
|
183
217
|
export declare const isPage: (v: unknown) => v is Page;
|
|
184
218
|
/**
|
|
185
219
|
* Manifest entry — what should respond to a path.
|
|
@@ -191,7 +225,7 @@ export declare const isPage: (v: unknown) => v is Page;
|
|
|
191
225
|
* can be assigned here (TS disallows Page<{slug:string}> → Page<RouteParams>
|
|
192
226
|
* due to parameter contravariance).
|
|
193
227
|
*/
|
|
194
|
-
export type AnyPage = Page<any, any>;
|
|
228
|
+
export type AnyPage = Page<any, any, any>;
|
|
195
229
|
export type RouteEntry = AnyPage | (() => Promise<{
|
|
196
230
|
default: AnyPage;
|
|
197
231
|
}>) | LayoutRoutes;
|
|
@@ -223,3 +257,4 @@ export interface LayoutRoutes {
|
|
|
223
257
|
*/
|
|
224
258
|
export declare function layout(spec: Omit<LayoutRoutes, "_layout">): LayoutRoutes;
|
|
225
259
|
export declare const isLayoutGroup: (v: unknown) => v is LayoutRoutes;
|
|
260
|
+
//# sourceMappingURL=page.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../src/page.ts"],"names":[],"mappings":"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"}
|
package/dist/src/page.js
CHANGED
|
@@ -26,6 +26,11 @@
|
|
|
26
26
|
/**
|
|
27
27
|
* Page factory. A typed wrapper over a plain object —
|
|
28
28
|
* needed only for type inference and the single "_page" stamp.
|
|
29
|
+
*
|
|
30
|
+
* The three generic parameters are inferred from `spec` in the common case:
|
|
31
|
+
* page({ static: true, view: () => html`...` })
|
|
32
|
+
* Provide them explicitly only for non-trivial shapes:
|
|
33
|
+
* page<{ slug: string }, Resource<Product>, Product>({ ... })
|
|
29
34
|
*/
|
|
30
35
|
export function page(spec) {
|
|
31
36
|
return { _page: true, ...spec };
|
package/dist/src/page.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page.js","sourceRoot":"","sources":["../../src/page.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AA4JH;;;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;AAmCpE;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,MAAM,CAAC,IAAmC;IACxD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,CAAC;AACpC,CAAC;AAED,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAU,EAAqB,EAAE,CAC7D,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAK,CAAkB,CAAC,OAAO,KAAK,IAAI,CAAC"}
|
|
1
|
+
{"version":3,"file":"page.js","sourceRoot":"","sources":["../../src/page.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAkMH;;;;;;;;GAQG;AACH,MAAM,UAAU,IAAI,CAIlB,IAAkC;IAClC,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;AAmCpE;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,MAAM,CAAC,IAAmC;IACxD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,CAAC;AACpC,CAAC;AAED,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAU,EAAqB,EAAE,CAC7D,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAK,CAAkB,CAAC,OAAO,KAAK,IAAI,CAAC","sourcesContent":["/**\n * Page contract — a uniform format for describing a page.\n *\n * // pages/user-profile.ts\n * import { page, html, resource, jsonFetcher } from '@madojs/mado';\n *\n * export default page({\n * title: ({ id }) => `User #${id}`,\n * load: ({ id }) => resource(() => `/api/users/${id}`, jsonFetcher()),\n * view: ({ params, data }) => html`\n * <h1>${() => data?.data()?.name ?? '\\u2026'}</h1>\n * `,\n * });\n *\n * // app.routes.ts\n * import { routes } from '@madojs/mado';\n *\n * export default routes({\n * '/users/:id': () => import('./modules/users/pages/user-profile.page.js'),\n * });\n *\n * The contract is strict: exactly three slots — title / load / view.\n * If a page exports something other than Page — tsc will stop it before build.\n * \"One right way\" — as in Rust: fewer branches, fewer bugs.\n */\n\nimport type { TemplateResult } from \"./html/template-types.js\";\n\n/** Route params (values from :placeholders). */\nexport type RouteParams = Record<string, string>;\n\n/**\n * JSON-compatible value. Static seeds and any data crossing the build-time →\n * snapshot → client boundary must satisfy this contract: only primitives,\n * plain arrays and plain object trees. Date, Map, Set, class instances,\n * functions, undefined values and circular references are not allowed and\n * fail at serialization time with a route-tagged error.\n */\nexport type JsonPrimitive = string | number | boolean | null;\nexport type JsonValue =\n | JsonPrimitive\n | JsonValue[]\n | { [key: string]: JsonValue };\n\n/**\n * Loaded data. If load returned a Resource — this is `Resource<T>`,\n * otherwise — a plain value. The view decides what to do with it.\n */\nexport type PageData<D> = D;\n\nexport interface PageContext<P extends RouteParams, D> {\n /** URL parameters. */\n params: P;\n /** Result of load() — usually Resource<T> with data/error/loading. */\n data: D;\n /** Current path (as a signal function). */\n path: () => string;\n /**\n * Child view (for layout components).\n * For regular pages always null.\n */\n child: TemplateResult | null;\n /**\n * Register cleanup that runs when the page navigates away.\n * Use for timers, manual subscriptions, or anything not automatically\n * managed by resource()/effect().\n *\n * view: ({ onDispose }) => {\n * const id = setInterval(tick, 3000);\n * onDispose(() => clearInterval(id));\n * return html`...`;\n * }\n */\n onDispose?: (fn: () => void) => void;\n}\n\n/**\n * Metadata for <head>. Static snapshots write it into HTML, and the SPA\n * runtime updates it on route changes.\n */\nexport interface HeadMeta {\n /** If set — overrides page.title. */\n title?: string;\n description?: string;\n /** Canonical URL — important for SEO with duplicate content. */\n canonical?: string;\n /** OpenGraph for social networks. */\n og?: {\n title?: string;\n description?: string;\n image?: string;\n type?: string;\n url?: string;\n };\n /** Twitter Cards (inherits og.* if not set). */\n twitter?: {\n card?: \"summary\" | \"summary_large_image\";\n title?: string;\n description?: string;\n image?: string;\n };\n /** Arbitrary meta tags: { name: 'robots', content: 'index,follow' } */\n meta?: Array<{ name?: string; property?: string; content: string }>;\n /** Arbitrary link tags: { rel: 'alternate', href: '/en/...' } */\n link?: Array<{ rel: string; href: string; hreflang?: string }>;\n /** JSON-LD structure (Schema.org). Will be inserted as <script type=\"application/ld+json\">. */\n jsonLd?: unknown;\n}\n\n/**\n * Static snapshot configuration: gives the build script enough information\n * to enumerate public routes and seed browser-rendered snapshots.\n *\n * `S` is the JSON-safe seed type (build-time → snapshot → client boundary).\n * It is intentionally decoupled from the runtime `D` data type: a seed of\n * type `Product` is fine even when `page.load()` returns `Resource<Product>`.\n *\n * `static.paths` and `static.initialData` MUST be browser-bundle-safe,\n * build-executable, and secret-free. They are executed during build\n * discovery, but their module also remains in the client bundle: do not\n * import Node-only APIs, do not embed credentials, do not perform side\n * effects at module top level.\n */\nexport interface StaticPageConfig<\n P extends RouteParams,\n S extends JsonValue,\n> {\n /**\n * Route parameter sets to materialize.\n *\n * Optional for literal routes such as \"/\".\n * Required for dynamic routes such as \"/products/:slug\".\n */\n paths?: () => Promise<P[]> | P[];\n /**\n * Optional build-time seed. Passed to page.head(params, seed)\n * and page.load(params, seed).\n *\n * Used both at build time (to render the snapshot) and at client boot\n * (so the runtime does not re-fetch what the snapshot already knows).\n * The seed is consumed exactly once per route commit.\n */\n initialData?: (params: P) => Promise<S> | S;\n}\n\n/**\n * Guard verdict.\n *\n * undefined / void / true → pass: continue to the next guard or render the page.\n * false / { halt: true } → stop: render nothing.\n * string → redirect to that path.\n * { redirect: url } → navigate to `url`.\n */\nexport type GuardResult =\n | void\n | undefined\n | boolean\n | string\n | { halt: true }\n | { redirect: string; replace?: boolean };\n\n/**\n * Guard function. Runs before a page (or any page in a layout group) is rendered.\n *\n * const requireAuth: Guard = ({ path }) => {\n * if (isLoggedIn()) return;\n * return { redirect: `/login?return=${encodeURIComponent(path)}` };\n * };\n *\n * Guards can be async. They are evaluated in order: each parent group's guards\n * first (outer → inner), then the page's own guards. The first non-pass verdict\n * wins.\n */\nexport type Guard = (ctx: {\n params: RouteParams;\n path: string;\n}) => GuardResult | Promise<GuardResult>;\n\nexport interface Page<\n P extends RouteParams = RouteParams,\n D = unknown,\n S extends JsonValue = JsonValue,\n> {\n readonly _page: true;\n title?: string | ((params: P) => string);\n /**\n * Runtime data loader. Receives params and the optional static seed\n * (only present on the first client boot of a static route, never\n * during SPA navigation).\n */\n load?: (params: P, seed?: S) => D;\n view: (ctx: PageContext<P, D>) => TemplateResult;\n /**\n * <head> metadata. Receives params and the optional static seed.\n *\n * `head` is intentionally built from `(params, seed)` and not from the\n * runtime `load()` result: head must be deterministic and JSON-derivable\n * so that it appears in the raw snapshot HTML for SEO and social\n * crawlers.\n */\n head?: (params: P, seed?: S) => HeadMeta;\n /**\n * Static HTML snapshot declaration. Used only by build-time discovery.\n * Non-static routes remain ordinary SPA routes.\n */\n static?: true | StaticPageConfig<P, S>;\n /**\n * Local error boundary. Catches errors from view() and load() of this page.\n * If not set — the global `error` from routes() is used.\n */\n errorView?: (err: Error, params: P) => TemplateResult;\n /**\n * Route guards for this page. Evaluated after any guards from the\n * enclosing layout groups. See `Guard` for the verdict contract.\n */\n guard?: Guard | Guard[];\n}\n\n/**\n * Page factory. A typed wrapper over a plain object —\n * needed only for type inference and the single \"_page\" stamp.\n *\n * The three generic parameters are inferred from `spec` in the common case:\n * page({ static: true, view: () => html`...` })\n * Provide them explicitly only for non-trivial shapes:\n * page<{ slug: string }, Resource<Product>, Product>({ ... })\n */\nexport function page<\n P extends RouteParams = RouteParams,\n D = unknown,\n S extends JsonValue = JsonValue,\n>(spec: Omit<Page<P, D, S>, \"_page\">): Page<P, D, S> {\n return { _page: true, ...spec };\n}\n\nexport const isPage = (v: unknown): v is Page =>\n typeof v === \"object\" && v !== null && (v as Page)._page === true;\n\n// ---------- Routes manifest ----------\n\n/**\n * Manifest entry — what should respond to a path.\n * - Page → serve immediately, without import (for eager pages)\n * - () => Promise<{default:Page}> → lazy via dynamic import\n * - LayoutRoutes → a route group with a shared layout\n *\n * Using Page<any, any> so that user-defined page<{slug:string}>\n * can be assigned here (TS disallows Page<{slug:string}> → Page<RouteParams>\n * due to parameter contravariance).\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyPage = Page<any, any, any>;\n\nexport type RouteEntry =\n | AnyPage\n | (() => Promise<{ default: AnyPage }>)\n | LayoutRoutes;\n\nexport interface LayoutRoutes {\n readonly _layout: true;\n /** Layout page wrapping children. Will receive child=TemplateResult. */\n layout?: AnyPage | (() => Promise<{ default: AnyPage }>);\n /** Sub-routes. Keys are relative (\"\" , \"users\", \"users/:id\"). */\n routes: Record<string, RouteEntry>;\n /**\n * Guards applied to every page below this group, before the page's own\n * guards. Single guard or ordered array. Async OK.\n */\n guard?: Guard | Guard[];\n}\n\n/**\n * `layout()` creates a route group with a shared shell:\n *\n * \"/admin\": layout({\n * layout: () => import(\"./layouts/admin.js\"),\n * guard: () => import(\"./lib/auth.js\").then(m => m.requireAuth),\n * routes: {\n * \"/\": () => import(\"./pages/admin/dashboard.js\"),\n * \"/orders\": () => import(\"./pages/admin/orders.js\"),\n * },\n * })\n */\nexport function layout(spec: Omit<LayoutRoutes, \"_layout\">): LayoutRoutes {\n return { _layout: true, ...spec };\n}\n\nexport const isLayoutGroup = (v: unknown): v is LayoutRoutes =>\n typeof v === \"object\" && v !== null && (v as LayoutRoutes)._layout === true;\n"]}
|
package/dist/src/persisted.d.ts
CHANGED
|
@@ -41,7 +41,12 @@ export interface PersistedOptions<T> {
|
|
|
41
41
|
syncTabs?: boolean;
|
|
42
42
|
}
|
|
43
43
|
export interface PersistedSignal<T> extends Signal<T> {
|
|
44
|
-
/**
|
|
44
|
+
/** Stop storage/channel subscriptions while preserving the stored value. */
|
|
45
|
+
dispose(): void;
|
|
46
|
+
/** Remove the stored value without disposing the signal. */
|
|
47
|
+
clear(): void;
|
|
48
|
+
/** Remove the stored value and dispose all persistence subscriptions. */
|
|
45
49
|
destroy(): void;
|
|
46
50
|
}
|
|
47
51
|
export declare function persisted<T>(key: string, base: Signal<T>, options?: PersistedOptions<T>): PersistedSignal<T>;
|
|
52
|
+
//# sourceMappingURL=persisted.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persisted.d.ts","sourceRoot":"","sources":["../../src/persisted.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAyB,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAIjE,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,sCAAsC;IACtC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,CAAC;IACjC,+CAA+C;IAC/C,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,CAAC,CAAC;IACjC;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,eAAe,CAAC,CAAC,CAAE,SAAQ,MAAM,CAAC,CAAC,CAAC;IACnD,4EAA4E;IAC5E,OAAO,IAAI,IAAI,CAAC;IAChB,4DAA4D;IAC5D,KAAK,IAAI,IAAI,CAAC;IACd,yEAAyE;IACzE,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,wBAAgB,SAAS,CAAC,CAAC,EACzB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,EACf,OAAO,GAAE,gBAAgB,CAAC,CAAC,CAAM,GAChC,eAAe,CAAC,CAAC,CAAC,CA4HpB"}
|
package/dist/src/persisted.js
CHANGED
|
@@ -46,7 +46,7 @@ export function persisted(key, base, options = {}) {
|
|
|
46
46
|
// never suppresses the echo — without this guard two tabs ping-pong forever.
|
|
47
47
|
// Seeded with the current value so the creation-time publish is a no-op.
|
|
48
48
|
let lastSync = serialize(base.peek());
|
|
49
|
-
let
|
|
49
|
+
let disposed = false;
|
|
50
50
|
// Collected so destroy() actually tears everything down. The original code
|
|
51
51
|
// dropped these disposers, so destroy() left the write effect alive and the
|
|
52
52
|
// next set() re-created the storage key.
|
|
@@ -54,7 +54,7 @@ export function persisted(key, base, options = {}) {
|
|
|
54
54
|
// 2) Write on each change (optionally debounced)
|
|
55
55
|
let writeTimer = null;
|
|
56
56
|
const flushWrite = (v) => {
|
|
57
|
-
if (!storage ||
|
|
57
|
+
if (!storage || disposed)
|
|
58
58
|
return;
|
|
59
59
|
try {
|
|
60
60
|
storage.setItem(fullKey, serialize(v));
|
|
@@ -79,7 +79,7 @@ export function persisted(key, base, options = {}) {
|
|
|
79
79
|
let bc = null;
|
|
80
80
|
if (wantSync && typeof BroadcastChannel !== "undefined") {
|
|
81
81
|
try {
|
|
82
|
-
bc = new BroadcastChannel(`mado:persisted:${
|
|
82
|
+
bc = new BroadcastChannel(`mado:persisted:${options.storage ?? "local"}:${fullKey}`);
|
|
83
83
|
const onMessage = (e) => {
|
|
84
84
|
try {
|
|
85
85
|
// Record what arrived BEFORE applying it, so the publisher effect
|
|
@@ -108,10 +108,10 @@ export function persisted(key, base, options = {}) {
|
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
const out = base;
|
|
111
|
-
out.
|
|
112
|
-
if (
|
|
111
|
+
out.dispose = () => {
|
|
112
|
+
if (disposed)
|
|
113
113
|
return;
|
|
114
|
-
|
|
114
|
+
disposed = true;
|
|
115
115
|
if (writeTimer) {
|
|
116
116
|
clearTimeout(writeTimer);
|
|
117
117
|
writeTimer = null;
|
|
@@ -119,6 +119,10 @@ export function persisted(key, base, options = {}) {
|
|
|
119
119
|
// Dispose the write + publish effects so later set() calls are inert.
|
|
120
120
|
for (const d of disposers.splice(0))
|
|
121
121
|
d();
|
|
122
|
+
bc?.close();
|
|
123
|
+
bc = null;
|
|
124
|
+
};
|
|
125
|
+
out.clear = () => {
|
|
122
126
|
if (storage) {
|
|
123
127
|
try {
|
|
124
128
|
storage.removeItem(fullKey);
|
|
@@ -127,12 +131,14 @@ export function persisted(key, base, options = {}) {
|
|
|
127
131
|
/* noop */
|
|
128
132
|
}
|
|
129
133
|
}
|
|
130
|
-
bc?.close();
|
|
131
|
-
bc = null;
|
|
132
134
|
};
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
135
|
+
out.destroy = () => {
|
|
136
|
+
out.clear();
|
|
137
|
+
out.dispose();
|
|
138
|
+
};
|
|
139
|
+
// Lifecycle cleanup stops subscriptions but deliberately preserves the
|
|
140
|
+
// persisted value. Removing storage is an explicit clear()/destroy() action.
|
|
141
|
+
getCurrentLifecycle()?.onDispose(() => out.dispose());
|
|
136
142
|
return out;
|
|
137
143
|
}
|
|
138
144
|
function safeStorage(name) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"persisted.js","sourceRoot":"","sources":["../../src/persisted.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"persisted.js","sourceRoot":"","sources":["../../src/persisted.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * persisted() wraps a signal in localStorage / sessionStorage.\n *\n * Useful for:\n * - theme (light/dark)\n * - selected language\n * - last viewed product\n * - form drafts\n *\n * const theme = persisted('theme', signal<'light'|'dark'>('light'));\n * const draft = persisted('newPost.draft', signal(''),\n * { storage: 'session', debounce: 300 });\n *\n * Returns the same Signal API. Reads from storage on startup; writes on every\n * change (optionally debounced). Synchronizes across tabs via BroadcastChannel.\n *\n * Notes:\n * - JSON.parse/stringify; Date/Map/Set need a custom serializer.\n * - On QuotaExceeded or private-mode failures it silently falls back to memory.\n * - destroy() optionally closes the BroadcastChannel subscription.\n */\n\nimport { effect, type Disposer, type Signal } from \"./signal.js\";\nimport { getCurrentLifecycle } from \"./lifecycle.js\";\n\n\nexport interface PersistedOptions<T> {\n /** \"local\" (default) or \"session\". */\n storage?: \"local\" | \"session\";\n /** Write delay in ms. Default 0 (synchronous). */\n debounce?: number;\n /**\n * Key prefix. Default \"mado:\". Helps avoid collisions\n * with other scripts on the page.\n */\n keyPrefix?: string;\n /** Custom serialiser. Default JSON.stringify. */\n serialize?: (value: T) => string;\n /** Custom deserialiser. Default JSON.parse. */\n deserialize?: (raw: string) => T;\n /**\n * Cross-tab synchronisation via BroadcastChannel.\n * Default true for \"local\", false for \"session\".\n */\n syncTabs?: boolean;\n}\n\nexport interface PersistedSignal<T> extends Signal<T> {\n /** Stop storage/channel subscriptions while preserving the stored value. */\n dispose(): void;\n /** Remove the stored value without disposing the signal. */\n clear(): void;\n /** Remove the stored value and dispose all persistence subscriptions. */\n destroy(): void;\n}\n\nexport function persisted<T>(\n key: string,\n base: Signal<T>,\n options: PersistedOptions<T> = {},\n): PersistedSignal<T> {\n const prefix = options.keyPrefix ?? \"mado:\";\n const fullKey = prefix + key;\n const storage =\n options.storage === \"session\"\n ? safeStorage(\"sessionStorage\")\n : safeStorage(\"localStorage\");\n const serialize = options.serialize ?? JSON.stringify;\n const deserialize =\n options.deserialize ?? ((s: string) => JSON.parse(s) as T);\n\n // 1) Read initial value\n if (storage) {\n try {\n const raw = storage.getItem(fullKey);\n if (raw != null) base.set(deserialize(raw));\n } catch {\n /* corrupt JSON — ignore */\n }\n }\n\n // Echo guard: the serialized form of the last value we either received from\n // the channel or published to it. For object/array values, structured clone\n // gives a new identity on every cross-tab hop, so Object.is inside signal.set\n // never suppresses the echo — without this guard two tabs ping-pong forever.\n // Seeded with the current value so the creation-time publish is a no-op.\n let lastSync = serialize(base.peek());\n let disposed = false;\n\n // Collected so destroy() actually tears everything down. The original code\n // dropped these disposers, so destroy() left the write effect alive and the\n // next set() re-created the storage key.\n const disposers: Disposer[] = [];\n\n // 2) Write on each change (optionally debounced)\n let writeTimer: ReturnType<typeof setTimeout> | null = null;\n const flushWrite = (v: T) => {\n if (!storage || disposed) return;\n try {\n storage.setItem(fullKey, serialize(v));\n } catch {\n /* QuotaExceeded — skip */\n }\n };\n\n disposers.push(\n effect(() => {\n const v = base();\n if (options.debounce && options.debounce > 0) {\n if (writeTimer) clearTimeout(writeTimer);\n writeTimer = setTimeout(() => flushWrite(v), options.debounce);\n } else {\n flushWrite(v);\n }\n }),\n );\n\n // 3) Cross-tab synchronisation via BroadcastChannel\n const wantSync = options.syncTabs ?? options.storage !== \"session\";\n let bc: BroadcastChannel | null = null;\n if (wantSync && typeof BroadcastChannel !== \"undefined\") {\n try {\n bc = new BroadcastChannel(\n `mado:persisted:${options.storage ?? \"local\"}:${fullKey}`,\n );\n const onMessage = (e: MessageEvent) => {\n try {\n // Record what arrived BEFORE applying it, so the publisher effect\n // below recognises this value as remote-origin and does not echo it.\n lastSync = serialize(e.data as T);\n base.set(e.data as T);\n } catch {\n /* noop */\n }\n };\n bc.addEventListener(\"message\", onMessage as EventListener);\n // publish changes — but never re-publish a value that just arrived from\n // another tab (which is what created the infinite loop).\n disposers.push(\n effect(() => {\n const v = base();\n const s = serialize(v);\n if (s === lastSync) return; // unchanged or remote-origin → no echo\n lastSync = s;\n bc?.postMessage(v);\n }),\n );\n } catch {\n bc = null;\n }\n }\n\n const out = base as PersistedSignal<T>;\n out.dispose = () => {\n if (disposed) return;\n disposed = true;\n if (writeTimer) {\n clearTimeout(writeTimer);\n writeTimer = null;\n }\n // Dispose the write + publish effects so later set() calls are inert.\n for (const d of disposers.splice(0)) d();\n bc?.close();\n bc = null;\n };\n out.clear = () => {\n if (storage) {\n try {\n storage.removeItem(fullKey);\n } catch {\n /* noop */\n }\n }\n };\n out.destroy = () => {\n out.clear();\n out.dispose();\n };\n\n // Lifecycle cleanup stops subscriptions but deliberately preserves the\n // persisted value. Removing storage is an explicit clear()/destroy() action.\n getCurrentLifecycle()?.onDispose(() => out.dispose());\n\n return out;\n}\n\n\nfunction safeStorage(name: \"localStorage\" | \"sessionStorage\"): Storage | null {\n try {\n const s = (globalThis as unknown as Record<string, Storage | undefined>)[\n name\n ];\n if (!s) return null;\n // Some browsers throw on storage access in private mode.\n const probe = \"__madoprobe__\";\n s.setItem(probe, \"1\");\n s.removeItem(probe);\n return s;\n } catch {\n return null;\n }\n}\n"]}
|
package/dist/src/resource.d.ts
CHANGED
|
@@ -47,7 +47,7 @@ export interface Resource<T> {
|
|
|
47
47
|
/** Signal: current key (useful for debugging and DI) */
|
|
48
48
|
key: () => string;
|
|
49
49
|
/** Force re-run the request. */
|
|
50
|
-
refresh():
|
|
50
|
+
refresh(): Promise<T>;
|
|
51
51
|
/**
|
|
52
52
|
* Locally replace the data (optimistic update).
|
|
53
53
|
* The cache is also updated.
|
|
@@ -125,3 +125,4 @@ export declare class HttpError extends Error {
|
|
|
125
125
|
*/
|
|
126
126
|
export declare function jsonFetcher<T>(init?: RequestInit): (url: string, signal: AbortSignal) => Promise<T>;
|
|
127
127
|
export {};
|
|
128
|
+
//# sourceMappingURL=resource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../../src/resource.ts"],"names":[],"mappings":"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"}
|