@alwatr/flux 9.18.0 → 9.19.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/README.md CHANGED
@@ -268,6 +268,58 @@ console.log(userPrefs.get()); // {theme: 'dark', lang: 'fa'}
268
268
  - **Type-safe** — full TypeScript support
269
269
  - **Migration-friendly** — bump `schemaVersion` to reset storage
270
270
 
271
+ ### 🌐 **SSR State Hydration**
272
+
273
+ `@alwatr/embedded-data` bridges the gap between server-rendered HTML and client-side reactive state. The server embeds initial data as JSON inside `<script type="application/json">` tags; the client extracts, validates, and feeds it into signals — **zero extra HTTP round-trips, zero flash of empty content**.
274
+
275
+ ```html
276
+ <!-- Server renders this into the HTML -->
277
+ <script
278
+ type="application/json"
279
+ data-user-profile
280
+ >
281
+ {"userId": 42, "name": "Ali", "role": "admin"}
282
+ </script>
283
+ ```
284
+
285
+ ```typescript
286
+ import {EmbeddedDataCollector} from '@alwatr/flux';
287
+ import {lazy} from '@alwatr/lazy';
288
+
289
+ interface UserProfile {
290
+ userId: number;
291
+ name: string;
292
+ role: 'admin' | 'user';
293
+ }
294
+
295
+ function isUserProfile(data: unknown): data is UserProfile {
296
+ return typeof data === 'object' && data !== null && 'userId' in data;
297
+ }
298
+
299
+ // Lazy: extraction runs only when .value is first accessed — not at module load.
300
+ export const userProfile = lazy(() =>
301
+ new EmbeddedDataCollector<UserProfile>('data-user-profile', isUserProfile).collect(),
302
+ );
303
+ ```
304
+
305
+ ```typescript
306
+ // Feed into a signal — the rest of the app reacts normally
307
+ import {createStateSignal} from '@alwatr/flux';
308
+
309
+ const userSignal = createStateSignal<UserProfile | null>({
310
+ name: 'user',
311
+ initialValue: userProfile.value, // hydrated from DOM, no API call needed
312
+ });
313
+ ```
314
+
315
+ **Why this matters:**
316
+
317
+ - **No flash of empty content** — state is available synchronously on first render
318
+ - **No extra API call** — server already sent the data in the HTML payload
319
+ - **SSR-safe** — `EmbeddedDataCollector` guards against missing `document` in Node.js/Bun
320
+ - **Memory-efficient** — script tag content is cleared after extraction (GC hint)
321
+ - **Type-safe** — optional type-guard validator ensures runtime safety
322
+
271
323
  ### 📄 **Page-Ready Signal for MPA**
272
324
 
273
325
  Lightweight page identity system for Multi-Page Applications:
@@ -915,6 +967,81 @@ Same as `createLocalStorageProvider` but uses `sessionStorage`.
915
967
 
916
968
  ---
917
969
 
970
+ ### Embedded Data
971
+
972
+ #### `EmbeddedDataCollector<T>`
973
+
974
+ Extracts, parses, and validates JSON embedded in `<script type="application/json">` DOM nodes. Designed for SSR state hydration — the server renders initial state into the HTML, the client reads it on boot without an extra HTTP round-trip.
975
+
976
+ ```typescript
977
+ import {EmbeddedDataCollector} from '@alwatr/flux';
978
+
979
+ // HTML: <script type="application/json" data-config>{"apiUrl":"https://api.example.com"}</script>
980
+
981
+ const collector = new EmbeddedDataCollector<AppConfig>('data-config');
982
+ const config = collector.collect(); // AppConfig | null
983
+ ```
984
+
985
+ **Constructor:**
986
+
987
+ ```typescript
988
+ new EmbeddedDataCollector<T>(
989
+ attributeName: string, // HTML attribute to query (e.g. 'data-config')
990
+ validator?: (data: unknown) => data is T // optional type-guard
991
+ )
992
+ ```
993
+
994
+ **`collect(): T | null`**
995
+
996
+ Runs the full extraction pipeline:
997
+
998
+ 1. `querySelector('script[attributeName]')` — SSR-safe, returns `null` if `document` is undefined
999
+ 2. Read `textContent`, then set it to `''` (GC hint)
1000
+ 3. `JSON.parse()`
1001
+ 4. Run `validator` if provided
1002
+ 5. Return typed data or `null` on any failure
1003
+
1004
+ **With type-guard validation:**
1005
+
1006
+ ```typescript
1007
+ import {EmbeddedDataCollector} from '@alwatr/flux';
1008
+ import {createStateSignal} from '@alwatr/flux';
1009
+
1010
+ interface CartState {
1011
+ items: {id: number; qty: number}[];
1012
+ total: number;
1013
+ }
1014
+
1015
+ function isCartState(data: unknown): data is CartState {
1016
+ return typeof data === 'object' && data !== null && Array.isArray((data as CartState).items);
1017
+ }
1018
+
1019
+ // Extract once at boot, feed into signal
1020
+ const collector = new EmbeddedDataCollector<CartState>('data-cart', isCartState);
1021
+
1022
+ const cartSignal = createStateSignal<CartState>({
1023
+ name: 'cart',
1024
+ initialValue: collector.collect() ?? {items: [], total: 0},
1025
+ });
1026
+ ```
1027
+
1028
+ **Combine with `@alwatr/lazy` for deferred extraction:**
1029
+
1030
+ ```typescript
1031
+ import {lazy} from '@alwatr/lazy';
1032
+ import {EmbeddedDataCollector} from '@alwatr/flux';
1033
+
1034
+ // Extraction is deferred until .value is first accessed
1035
+ export const serverConfig = lazy(() =>
1036
+ new EmbeddedDataCollector<ServerConfig>('data-server-config', isServerConfig).collect(),
1037
+ );
1038
+
1039
+ // Somewhere in your bootstrap code:
1040
+ const config = serverConfig.value; // extracted here, cached forever
1041
+ ```
1042
+
1043
+ ---
1044
+
918
1045
  ### Render State
919
1046
 
920
1047
  #### `renderState<R, T>(state, renderRecord, thisArg?)`
@@ -1172,6 +1299,7 @@ todosSignal.subscribe((todos) => {
1172
1299
  - **[@alwatr/signal](https://github.com/Alwatr/alwatr/tree/next/pkg/nanolib/signal)** — Fine-grained reactive signals (part of Flux)
1173
1300
  - **[@alwatr/action](https://github.com/Alwatr/alwatr/tree/next/pkg/nanolib/action)** — Global event delegation action bus (part of Flux)
1174
1301
  - **[@alwatr/directive](https://github.com/Alwatr/alwatr/tree/next/pkg/nanolib/directive)** — Attribute-based DOM directives (part of Flux)
1302
+ - **[@alwatr/embedded-data](https://github.com/Alwatr/alwatr/tree/next/pkg/nanolib/embedded-data)** — Extract and validate embedded JSON from DOM script tags for SSR hydration (part of Flux)
1175
1303
  - **[@alwatr/fsm](https://github.com/Alwatr/alwatr/tree/next/pkg/fsm)** — Type-safe Finite State Machine
1176
1304
  - **[@alwatr/nanotron](https://github.com/Alwatr/alwatr/tree/next/pkg/nanotron)** — Lightweight API server framework
1177
1305
  - **[@alwatr/nitrobase](https://github.com/Alwatr/alwatr/tree/next/pkg/nitrobase)** — In-memory JSON database
package/dist/main.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from '@alwatr/signal';
2
2
  export * from '@alwatr/action';
3
3
  export * from '@alwatr/directive';
4
+ export * from '@alwatr/embedded-data';
4
5
  export * from '@alwatr/render-state';
5
6
  export * from '@alwatr/local-storage';
6
7
  export * from '@alwatr/session-storage';
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAGA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,mBAAmB,qBAAqB,CAAC"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAGA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,mBAAmB,qBAAqB,CAAC"}
package/dist/main.js CHANGED
@@ -1,5 +1,5 @@
1
- /* 📦 @alwatr/flux v9.18.0 */
2
- export*from"@alwatr/signal";export*from"@alwatr/action";export*from"@alwatr/directive";export*from"@alwatr/render-state";export*from"@alwatr/local-storage";export*from"@alwatr/session-storage";export*from"@alwatr/page-ready";import{html as o,render as a,noChange as t,nothing as p}from"lit-html";import{ifDefined as f}from"lit-html/directives/if-defined.js";import{cache as g}from"lit-html/directives/cache.js";import{classMap as i}from"lit-html/directives/class-map.js";import{when as s}from"lit-html/directives/when.js";export{s as when,a as render,p as nothing,t as noChange,f as ifDefined,o as html,i as classMap,g as cache};
1
+ /* 📦 @alwatr/flux v9.19.0 */
2
+ export*from"@alwatr/signal";export*from"@alwatr/action";export*from"@alwatr/directive";export*from"@alwatr/embedded-data";export*from"@alwatr/render-state";export*from"@alwatr/local-storage";export*from"@alwatr/session-storage";export*from"@alwatr/page-ready";import{html as e,render as t,noChange as a,nothing as p}from"lit-html";import{ifDefined as n}from"lit-html/directives/if-defined.js";import{cache as g}from"lit-html/directives/cache.js";import{classMap as i}from"lit-html/directives/class-map.js";import{when as s}from"lit-html/directives/when.js";export{s as when,t as render,p as nothing,a as noChange,n as ifDefined,e as html,i as classMap,g as cache};
3
3
 
4
- //# debugId=432FAE968752561764756E2164756E21
4
+ //# debugId=3C8541CE38BF180764756E2164756E21
5
5
  //# sourceMappingURL=main.js.map
package/dist/main.js.map CHANGED
@@ -2,10 +2,10 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/main.ts", "../src/lit-html.ts"],
4
4
  "sourcesContent": [
5
- "// UI and reactive bundle — signals, actions, directives, and client-side storage.\n// This package aggregates all UI-layer nanolibs for convenient single-import usage.\n\nexport * from '@alwatr/signal';\nexport * from '@alwatr/action';\nexport * from '@alwatr/directive';\nexport * from '@alwatr/render-state';\nexport * from '@alwatr/local-storage';\nexport * from '@alwatr/session-storage';\nexport * from '@alwatr/page-ready';\nexport * from './lit-html.js';\nexport type * from '@alwatr/type-helper';\n",
5
+ "// UI and reactive bundle — signals, actions, directives, and client-side storage.\n// This package aggregates all UI-layer nanolibs for convenient single-import usage.\n\nexport * from '@alwatr/signal';\nexport * from '@alwatr/action';\nexport * from '@alwatr/directive';\nexport * from '@alwatr/embedded-data';\nexport * from '@alwatr/render-state';\nexport * from '@alwatr/local-storage';\nexport * from '@alwatr/session-storage';\nexport * from '@alwatr/page-ready';\nexport * from './lit-html.js';\nexport type * from '@alwatr/type-helper';\n",
6
6
  "/**\n * Curated re-exports from `lit-html` for use within `@alwatr/flux`.\n *\n * Only the subset of `lit-html` APIs that are commonly needed in a Flux-based\n * application is exported here. This keeps the public surface minimal and\n * avoids pulling in advanced directive utilities that most consumers never use.\n *\n * **Exported APIs:**\n * - `html` — tagged template literal that produces a `TemplateResult`\n * - `render` — renders a `TemplateResult` into a DOM container\n * - `noChange` — sentinel that tells lit-html to leave the current part value unchanged\n * - `nothing` — sentinel that renders nothing (removes the node/attribute)\n * - `ifDefined` — renders a value only when it is not `undefined`\n * - `cache` — caches rendered templates to avoid re-parsing on state changes\n * - `classMap` — efficiently sets/removes CSS classes from an object map\n * - `when` — conditional rendering helper (`when(condition, trueCase, falseCase)`)\n *\n * @example\n * ```typescript\n * import {html, render, classMap, when} from '@alwatr/flux';\n *\n * const template = (isActive: boolean) => html`\n * <div class=${classMap({active: isActive, hidden: !isActive})}>\n * ${when(isActive, () => html`<span>Active</span>`, () => html`<span>Inactive</span>`)}\n * </div>\n * `;\n *\n * render(template(true), document.getElementById('app')!);\n * ```\n */\nexport {html, render, noChange, nothing} from 'lit-html';\n// export {Directive, PartType, directive} from 'lit-html/directive.js';\n// export {AsyncDirective} from 'lit-html/async-directive.js';\n// export {unsafeSVG} from 'lit-html/directives/unsafe-svg.js';\nexport {ifDefined} from 'lit-html/directives/if-defined.js';\nexport {cache} from 'lit-html/directives/cache.js';\nexport {classMap} from 'lit-html/directives/class-map.js';\nexport {when} from 'lit-html/directives/when.js';\n\n// export type {Part, PartInfo} from 'lit-html/directive.js';\n// export type {LitUnstable} from 'lit-html';\n"
7
7
  ],
8
- "mappings": ";AAGA,4BACA,4BACA,+BACA,kCACA,mCACA,qCACA,gCCqBA,eAAQ,YAAM,cAAQ,aAAU,iBAIhC,oBAAQ,0CACR,gBAAQ,qCACR,mBAAQ,yCACR,eAAQ",
9
- "debugId": "432FAE968752561764756E2164756E21",
8
+ "mappings": ";AAGA,4BACA,4BACA,+BACA,mCACA,kCACA,mCACA,qCACA,gCCoBA,eAAQ,YAAM,cAAQ,aAAU,iBAIhC,oBAAQ,0CACR,gBAAQ,qCACR,mBAAQ,yCACR,eAAQ",
9
+ "debugId": "3C8541CE38BF180764756E2164756E21",
10
10
  "names": []
11
11
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alwatr/flux",
3
- "version": "9.18.0",
3
+ "version": "9.19.0",
4
4
  "description": "UI and reactive library bundle for ECMAScript (JavaScript/TypeScript) projects — signals, actions, directives, and storage.",
5
5
  "license": "MPL-2.0",
6
6
  "author": "S. Ali Mihandoost <ali.mihandoost@gmail.com> (https://ali.mihandoost.com)",
@@ -21,8 +21,9 @@
21
21
  },
22
22
  "sideEffects": false,
23
23
  "dependencies": {
24
- "@alwatr/action": "9.17.0",
24
+ "@alwatr/action": "9.19.0",
25
25
  "@alwatr/directive": "9.18.0",
26
+ "@alwatr/embedded-data": "9.19.0",
26
27
  "@alwatr/local-storage": "9.16.0",
27
28
  "@alwatr/page-ready": "9.16.0",
28
29
  "@alwatr/render-state": "9.16.0",
@@ -82,5 +83,5 @@
82
83
  "ui",
83
84
  "unidirectional-data-flow"
84
85
  ],
85
- "gitHead": "ae7aed95106fd2e6d3c14f0628fc12ae8a29ea15"
86
+ "gitHead": "20106a8e95925e2b6f895325afe1c7b7c263e2cc"
86
87
  }
package/src/main.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  export * from '@alwatr/signal';
5
5
  export * from '@alwatr/action';
6
6
  export * from '@alwatr/directive';
7
+ export * from '@alwatr/embedded-data';
7
8
  export * from '@alwatr/render-state';
8
9
  export * from '@alwatr/local-storage';
9
10
  export * from '@alwatr/session-storage';