@actdim/dynstruct 1.2.7 → 1.2.9

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
@@ -2,13 +2,37 @@
2
2
 
3
3
  Build scalable applications with dynamic structured components, explicit wiring, and decoupled message flow. Keep architecture clean and modular.
4
4
 
5
- [![npm version](https://img.shields.io/npm/v/@actdim/dynstruct.svg)](https://www.npmjs.com/package/@actdim/dynstruct)
6
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.9+-blue.svg)](https://www.typescriptlang.org/)
7
- [![License: Proprietary](https://img.shields.io/badge/License-Proprietary-red.svg)](LICENSE)
8
-
9
- ## Overview
10
-
11
- **@actdim/dynstruct** is a sophisticated TypeScript-based component system and architectural framework for building large-scale, modular applications. It provides a structure-first, declarative approach to component design with:
5
+ [![npm version](https://img.shields.io/npm/v/@actdim/dynstruct.svg)](https://www.npmjs.com/package/@actdim/dynstruct)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.9+-blue.svg)](https://www.typescriptlang.org/)
7
+ [![License: Proprietary](https://img.shields.io/badge/License-Proprietary-red.svg)](LICENSE)
8
+
9
+ ## Table of Contents
10
+
11
+ - [Overview](#overview)
12
+ - [Framework Support](#framework-support)
13
+ - [Features](#features)
14
+ - [Quick Start](#quick-start)
15
+ - [How It Works](#how-it-works)
16
+ - [Installation](#installation)
17
+ - [Getting Started (React)](#getting-started-react)
18
+ - [Key Advantages (React Examples)](#key-advantages-react-examples)
19
+ - [Core Concepts](#core-concepts)
20
+ - [More Examples (React)](#more-examples-react)
21
+ - [Architecture](#architecture)
22
+ - [API Reference](#api-reference)
23
+ - [Storybook Examples](#storybook-examples)
24
+ - [Development](#development)
25
+ - [Package Management](#package-management)
26
+ - [Contributing](#contributing)
27
+ - [License](#license)
28
+ - [Author](#author)
29
+ - [Repository](#repository)
30
+ - [Issues](#issues)
31
+ - [Keywords](#keywords)
32
+
33
+ ## Overview
34
+
35
+ **@actdim/dynstruct** is a TypeScript-based component system and architectural framework for building large-scale, modular applications. It provides a structure-first, declarative approach to component design with:
12
36
 
13
37
  - **Type-safe component model** with explicit dependency wiring
14
38
  - **Decoupled messaging architecture** using a message bus for inter-component communication
@@ -49,7 +73,7 @@ The architectural core is framework-agnostic, allowing the same component struct
49
73
 
50
74
  🎯 **Navigation & Routing** - Built-in navigation contracts with React Router integration
51
75
 
52
- 🔐 **Security Provider** - Authentication, authorization, and ACL support
76
+ 🔐 **Security Provider** - Authentication and authorization support
53
77
 
54
78
  ## Quick Start
55
79
 
@@ -73,13 +97,14 @@ The core pattern in dynstruct is **structure-first composition** where parent co
73
97
  npm install @actdim/dynstruct
74
98
  ```
75
99
 
76
- ### Peer Dependencies
77
-
78
- This package requires the following peer dependencies:
79
-
80
- ```bash
81
- npm install react react-dom mobx mobx-react-lite mobx-utils \
82
- @actdim/msgmesh @actdim/utico react-router react-router-dom \
100
+ ### Peer Dependencies
101
+
102
+ This package requires the following peer dependencies:
103
+ For message bus functionality, install [@actdim/msgmesh](https://www.npmjs.com/package/@actdim/msgmesh).
104
+
105
+ ```bash
106
+ npm install react react-dom mobx mobx-react-lite mobx-utils \
107
+ @actdim/msgmesh @actdim/utico react-router react-router-dom \
83
108
  rxjs uuid path-to-regexp jwt-decode http-status
84
109
  ```
85
110
 
@@ -92,7 +117,7 @@ pnpm add @actdim/dynstruct @actdim/msgmesh @actdim/utico \
92
117
  jwt-decode http-status
93
118
  ```
94
119
 
95
- ## Quick Start (React)
120
+ ## Getting Started (React)
96
121
 
97
122
  > **Note:** All examples below are for the **React** implementation. SolidJS and Vue.js versions will have similar structure with framework-specific adapters.
98
123
 
@@ -389,7 +414,7 @@ function TodoList({ todos }) {
389
414
 
390
415
  ### MobX Reactivity Pitfalls
391
416
 
392
- While MobX is powerful, it has subtle issues that cause unexpected re-renders and are hard to debug:
417
+ While MobX is capable, it has subtle issues that cause unexpected re-renders and are hard to debug:
393
418
 
394
419
  #### Problem 1: Computed Returns New Object
395
420
 
@@ -731,7 +756,7 @@ Traditional React development offers **too many choices** for managing state and
731
756
  - **Personal taste** - "I prefer this pattern because it looks cleaner to me"
732
757
  - **Laziness** - "This is faster to write, even if it's not optimal"
733
758
 
734
- When your component architecture is built on **many different principles** and becomes **sophisticated**, understanding where a problem is hiding becomes extremely difficult. Different components use different approaches, making the codebase inconsistent and hard to reason about.
759
+ When your component architecture is built on **many different principles** and becomes **complex**, understanding where a problem is hiding becomes extremely difficult. Different components use different approaches, making the codebase inconsistent and hard to reason about.
735
760
 
736
761
  **When Problems Surface:**
737
762
  - ❌ **Hard to detect** - Inconsistent patterns mask the root cause
@@ -824,7 +849,7 @@ type Struct = ComponentStruct<
824
849
 
825
850
  // List of effect names that will be available in this component.
826
851
  // Effect implementations are defined in ComponentDef (see below).
827
- effects: ['loadData', 'syncState'];
852
+ effects: ['computeSummary', 'trackCounter'];
828
853
  }
829
854
  >;
830
855
  ```
@@ -870,23 +895,24 @@ const useMyComponent = (params: ComponentParams<Struct>) => {
870
895
  },
871
896
 
872
897
  effects: {
873
- // Effect implementations. Effects are methods similar to actions
874
- // (or they simply call actions), but they run automatically as
875
- // soon as any property accessed within the effect implementation
876
- // changes.
898
+ // Effect implementations. Effects are auto-tracking reactive
899
+ // functions that re-run automatically whenever any reactive
900
+ // property accessed inside them changes.
877
901
  //
878
902
  // Effects are accessed on the component instance by name via
879
- // the `effects` property (e.g. c.effects.loadData).
903
+ // the `effects` property (e.g. c.effects.computeSummary).
880
904
  //
881
905
  // An effect runs immediately when the component is created and
882
906
  // can later be manually paused, resumed, or stopped entirely.
883
- loadData: (component) => {
884
- console.log('Items count:', m.items.length);
907
+ computeSummary: (component) => {
908
+ // Re-runs whenever m.items changes
909
+ m.message = `Total items: ${m.items.length}`;
885
910
  // Return an optional cleanup function
886
911
  return () => { /* cleanup */ };
887
912
  },
888
- syncState: (component) => {
889
- console.log('Counter is', m.counter);
913
+ trackCounter: (component) => {
914
+ // Re-runs whenever m.counter changes
915
+ if (m.counter > 100) m.message = 'Counter is high!';
890
916
  },
891
917
  },
892
918
 
@@ -1033,7 +1059,7 @@ children: {
1033
1059
 
1034
1060
  ### Message Bus Communication
1035
1061
 
1036
- dynstruct integrates with **[@actdim/msgmesh](https://www.npmjs.com/package/@actdim/msgmesh)**, a powerful type-safe message bus library that enables decoupled component communication.
1062
+ dynstruct integrates with **[@actdim/msgmesh](https://www.npmjs.com/package/@actdim/msgmesh)**, a type-safe message bus library that enables decoupled component communication.
1037
1063
 
1038
1064
  #### Key Benefits
1039
1065
 
@@ -1435,6 +1461,130 @@ const ancestors = component.getChainUp();
1435
1461
  const descendants = component.getChainDown();
1436
1462
  ```
1437
1463
 
1464
+ ### Dynamic Content
1465
+
1466
+ Not all children need to be full dynstruct components. The `children` field supports three patterns for embedding dynamic content, ranging from lightweight React wrappers to parameterized component factories.
1467
+
1468
+ #### 1. React.FC Wrapper
1469
+
1470
+ The simplest approach: declare a child as `React.FC` in the structure and provide a plain function returning JSX in the definition. This is useful for small inline fragments that need access to the parent's reactive model but don't require their own component structure.
1471
+
1472
+ In the structure, the child type is `React.FC`. In the view, it is accessed with a **capitalized** name (because it's a function type): `<c.children.Summary />`.
1473
+
1474
+ ```typescript
1475
+ type Struct = ComponentStruct<AppMsgStruct, {
1476
+ props: {
1477
+ counter: number;
1478
+ };
1479
+ children: {
1480
+ summary: React.FC; // standard React functional component
1481
+ };
1482
+ }>;
1483
+
1484
+ const def: ComponentDef<Struct> = {
1485
+ props: { counter: 0 },
1486
+ children: {
1487
+ // Plain function returning JSX — has access to the parent model
1488
+ summary: () => {
1489
+ return <div>Counter: {m.counter}</div>;
1490
+ },
1491
+ },
1492
+ view: (_, c) => (
1493
+ <div>
1494
+ {/* Capitalized because it's a function type */}
1495
+ <c.children.Summary />
1496
+ </div>
1497
+ ),
1498
+ };
1499
+ ```
1500
+
1501
+ #### 2. DynamicContent Component
1502
+
1503
+ When you need typed data and a render function inside a proper dynstruct component, use `DynamicContentStruct` / `useDynamicContent`. This gives you a component with a reactive `data` prop and a `render` callback, so the content re-renders when the data changes.
1504
+
1505
+ ```typescript
1506
+ import { DynamicContentStruct, useDynamicContent } from '@actdim/dynstruct/componentModel/DynamicContent';
1507
+
1508
+ type Struct = ComponentStruct<AppMsgStruct, {
1509
+ props: {
1510
+ text: string;
1511
+ };
1512
+ children: {
1513
+ content: DynamicContentStruct<string, AppMsgStruct>;
1514
+ };
1515
+ }>;
1516
+
1517
+ const def: ComponentDef<Struct> = {
1518
+ props: { text: 'hello' },
1519
+ children: {
1520
+ content: useDynamicContent<string>({
1521
+ // Bind data to a parent property
1522
+ data: bindProp(() => m, 'text'),
1523
+ // Render function — can access the component's own model
1524
+ render: () => {
1525
+ return <>{c.children.content.model.data}</>;
1526
+ },
1527
+ }),
1528
+ },
1529
+ view: (_, c) => (
1530
+ <div>
1531
+ <c.children.content.View />
1532
+ </div>
1533
+ ),
1534
+ };
1535
+ ```
1536
+
1537
+ `DynamicContentStruct` is generic: `DynamicContentStruct<TData, TMsgStruct>`. The `data` prop holds typed data (bound to a parent property or passed directly), and `render` produces the JSX.
1538
+
1539
+ #### 3. Factory Function (Parameterized Children)
1540
+
1541
+ When you need to create multiple instances of a child component dynamically (e.g. in a loop), declare the child as a factory function in the structure. The function accepts parameters and returns a component structure type.
1542
+
1543
+ In the view, factory children are also accessed with a **capitalized** name and can receive props (including a `key`):
1544
+
1545
+ ```typescript
1546
+ type Struct = ComponentStruct<AppMsgStruct, {
1547
+ props: {
1548
+ counter: number;
1549
+ text: string;
1550
+ };
1551
+ children: {
1552
+ dynEdit: (props: { value?: string }) => SimpleEditStruct;
1553
+ };
1554
+ }>;
1555
+
1556
+ const def: ComponentDef<Struct> = {
1557
+ props: { counter: 0, text: 'bar' },
1558
+ children: {
1559
+ // Factory: called each time <c.children.DynEdit /> is rendered
1560
+ dynEdit: (params) => {
1561
+ return useSimpleEdit({
1562
+ value: bindProp(() => m, 'text'),
1563
+ });
1564
+ },
1565
+ },
1566
+ view: (_, c) => (
1567
+ <ul>
1568
+ {Array.from({ length: m.counter }).map((_, i) => (
1569
+ <li key={i}>
1570
+ <c.children.DynEdit key={i} />
1571
+ </li>
1572
+ ))}
1573
+ </ul>
1574
+ ),
1575
+ };
1576
+ ```
1577
+
1578
+ #### Summary
1579
+
1580
+ | Pattern | Structure type | Access in view | Use case |
1581
+ |---|---|---|---|
1582
+ | React.FC wrapper | `React.FC` | `<c.children.Name />` | Small inline fragments with access to parent model |
1583
+ | DynamicContent | `DynamicContentStruct<TData>` | `<c.children.name.View />` | Typed reactive data with custom render function |
1584
+ | Factory function | `(params) => ChildStruct` | `<c.children.Name key={...} />` | Multiple dynamic instances, parameterized creation |
1585
+
1586
+ > **Naming convention:** Children declared as function types (`React.FC`, factory functions) are accessed with a **capitalized** name in the view (`c.children.Summary`, `c.children.DynEdit`). Children declared as component structures use their original name (`c.children.content`).
1587
+
1438
1588
  ### Component Events
1439
1589
 
1440
1590
  The component model provides **automatic type-safe event handlers** for the component lifecycle and property changes. IntelliSense automatically suggests all available events based on the component structure.
@@ -1595,18 +1745,18 @@ const useForm = (params: ComponentParams<FormStruct>) => {
1595
1745
  isValid: false
1596
1746
  },
1597
1747
  events: {
1598
- // Validate email when it changes
1599
- onChangeEmail: (oldValue, newValue) => {
1748
+ // Validate email after it changes — onChange receives only the new value
1749
+ onChangeEmail: (value) => {
1600
1750
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1601
- m.isValid = emailRegex.test(newValue) && m.password.length >= 6;
1751
+ m.isValid = emailRegex.test(value) && m.password.length >= 6;
1602
1752
  },
1603
1753
 
1604
- // Validate password when it changes
1605
- onChangePassword: (oldValue, newValue) => {
1606
- m.isValid = m.email.includes('@') && newValue.length >= 6;
1754
+ // Validate password after it changes
1755
+ onChangePassword: (value) => {
1756
+ m.isValid = m.email.includes('@') && value.length >= 6;
1607
1757
  },
1608
1758
 
1609
- // Sanitize input before setting
1759
+ // Sanitize input before setting — onChanging receives (oldValue, newValue)
1610
1760
  onChangingEmail: (oldValue, newValue) => {
1611
1761
  return newValue.toLowerCase().trim();
1612
1762
  }
@@ -1730,302 +1880,128 @@ const useEffectDemo = (params: ComponentParams<Struct>) => {
1730
1880
 
1731
1881
  In this example the `trackNameChanges` effect accesses `m.firstName` and `m.lastName`, so it re-runs whenever either changes. Clicking **Pause** calls `c.effects.trackNameChanges.pause()`, which suspends the auto-tracking — edits to the name fields no longer update `fullName` until **Resume** is clicked.
1732
1882
 
1733
- ## Examples (React)
1883
+ ## More Examples (React)
1734
1884
 
1735
- > **Note:** All examples below are for the **React** implementation.
1885
+ > **Note:** For basic examples (simple component, parent-child, bindings, events, effects) see the [Getting Started](#getting-started-react) and [Core Concepts](#core-concepts) sections above.
1736
1886
 
1737
- ### Example 1: Simple Counter Component
1887
+ ### Service Integration (API Calls)
1738
1888
 
1739
- ```typescript
1740
- // React implementation
1741
- import { ComponentStruct, ComponentDef, ComponentParams } from '@actdim/dynstruct/componentModel/contracts';
1742
- import { useComponent, toReact } from '@actdim/dynstruct/componentModel/react';
1743
- import { AppMsgStruct } from '@actdim/dynstruct/appDomain/appContracts';
1889
+ dynstruct integrates with the **service adapter** system from [@actdim/msgmesh](https://github.com/actdim/msgmesh/?tab=readme-ov-file#service-adapters). Adapters automatically register any service class (e.g. an API client) as a message bus provider — channel names, payload types, and return types are all derived from the service class at compile time.
1744
1890
 
1745
- type CounterStruct = ComponentStruct<AppMsgStruct, {
1746
- props: { count: number };
1747
- actions: { increment: () => void; decrement: () => void };
1748
- }>;
1891
+ #### 1. Define an API client
1749
1892
 
1750
- const useCounter = (params: ComponentParams<CounterStruct>) => {
1751
- const def: ComponentDef<CounterStruct> = {
1752
- props: { count: params.count ?? 0 },
1753
- actions: {
1754
- increment: () => { c.model.count++; },
1755
- decrement: () => { c.model.count--; }
1756
- },
1757
- view: (_, c) => (
1758
- <div>
1759
- <h2>Counter: {c.model.count}</h2>
1760
- <button onClick={c.actions.increment}>+</button>
1761
- <button onClick={c.actions.decrement}>-</button>
1762
- </div>
1763
- )
1764
- };
1893
+ ```typescript
1894
+ export type DataItem = { id: number; name: string };
1765
1895
 
1766
- const c = useComponent(def, params);
1767
- return c;
1768
- };
1896
+ export class TestApiClient {
1897
+ static readonly name = 'TestApiClient' as const;
1898
+ readonly name = 'TestApiClient' as const;
1769
1899
 
1770
- export const Counter = toReact(useCounter);
1900
+ getDataItems(param1: number[], param2: string[]): Promise<DataItem[]> {
1901
+ return fetch('/api/data').then(r => r.json());
1902
+ }
1903
+ }
1771
1904
  ```
1772
1905
 
1773
- ### Example 2: Component with Children
1906
+ #### 2. Set up adapters and service provider
1907
+
1908
+ Type utilities from [`@actdim/msgmesh/adapters`](https://www.npmjs.com/package/@actdim/msgmesh) transform the service class into a typed bus structure. Each public method becomes a channel (e.g. `getDataItems` → `API.TEST.GETDATAITEMS`). See [@actdim/msgmesh — Service Adapters](https://github.com/actdim/msgmesh/?tab=readme-ov-file#service-adapters) for details on how the type transformation works.
1774
1909
 
1775
1910
  ```typescript
1776
- // React implementation
1777
- import { ComponentStruct, ComponentDef, ComponentParams } from '@actdim/dynstruct/componentModel/contracts';
1778
- import { useComponent, toReact } from '@actdim/dynstruct/componentModel/react';
1779
- import { AppMsgStruct } from '@actdim/dynstruct/appDomain/appContracts';
1911
+ import {
1912
+ BaseServiceSuffix, getMsgChannelSelector, MsgProviderAdapter,
1913
+ ToMsgChannelPrefix, ToMsgStruct,
1914
+ } from '@actdim/msgmesh/adapters';
1915
+ import { ServiceProvider } from '@actdim/dynstruct/services/react/ServiceProvider';
1916
+
1917
+ // "TestApiClient" → remove suffix "Client" → uppercase → "API.TEST."
1918
+ type ApiPrefix = 'API';
1919
+ type TestApiChannelPrefix = ToMsgChannelPrefix<
1920
+ typeof TestApiClient.name, ApiPrefix, BaseServiceSuffix
1921
+ >;
1780
1922
 
1781
- // Child component
1782
- type ButtonStruct = ComponentStruct<AppMsgStruct, {
1783
- props: { label: string; onClick: () => void };
1784
- }>;
1923
+ // Transform service methods into a bus struct
1924
+ type ApiMsgStruct = ToMsgStruct<TestApiClient, TestApiChannelPrefix>;
1785
1925
 
1786
- const useButton = (params: ComponentParams<ButtonStruct>) => {
1787
- const def: ComponentDef<ButtonStruct> = {
1788
- props: {
1789
- label: params.label ?? 'Click',
1790
- onClick: params.onClick ?? (() => {})
1791
- },
1792
- view: (_, c) => (
1793
- <button onClick={c.model.onClick}>{c.model.label}</button>
1794
- )
1795
- };
1796
- return useComponent(def, params);
1926
+ // Create adapter instances
1927
+ const services: Record<TestApiChannelPrefix, any> = {
1928
+ 'API.TEST.': new TestApiClient(),
1797
1929
  };
1798
1930
 
1799
- // Parent component with children
1800
- type PanelStruct = ComponentStruct<AppMsgStruct, {
1801
- props: { title: string; clickCount: number };
1802
- children: {
1803
- okButton: ButtonStruct;
1804
- cancelButton: ButtonStruct;
1805
- };
1806
- }>;
1807
-
1808
- const usePanel = (params: ComponentParams<PanelStruct>) => {
1809
- let c: Component<PanelStruct>;
1810
- let m: ComponentModel<PanelStruct>;
1811
-
1812
- const def: ComponentDef<PanelStruct> = {
1813
- props: {
1814
- title: params.title ?? 'Panel',
1815
- clickCount: 0
1816
- },
1817
- children: {
1818
- okButton: useButton({
1819
- label: 'OK',
1820
- onClick: () => {
1821
- m.clickCount++; // Reactive property update
1822
- console.log('OK clicked');
1823
- }
1824
- }),
1825
- cancelButton: useButton({
1826
- label: 'Cancel',
1827
- onClick: () => console.log('Cancel clicked')
1828
- })
1829
- },
1830
- view: (_, c) => (
1831
- <div className="panel">
1832
- <h3>{m.title}</h3>
1833
- <p>Clicks: {m.clickCount}</p>
1834
- <div className="buttons">
1835
- <c.children.okButton.View />
1836
- <c.children.cancelButton.View />
1837
- </div>
1838
- </div>
1839
- )
1840
- };
1841
-
1842
- c = useComponent(def, params);
1843
- m = c.model;
1844
- return c;
1845
- };
1931
+ const msgProviderAdapters = Object.entries(services).map(
1932
+ ([_, service]) => ({
1933
+ service,
1934
+ channelSelector: getMsgChannelSelector(services),
1935
+ }) as MsgProviderAdapter,
1936
+ );
1846
1937
 
1847
- export const Panel = toReact(usePanel);
1938
+ // React provider component — wraps children with registered adapters
1939
+ export const ApiServiceProvider = () => ServiceProvider({ adapters: msgProviderAdapters });
1848
1940
  ```
1849
1941
 
1850
- ### Example 3: Message Bus Producer/Consumer
1942
+ #### 3. Use in a component
1851
1943
 
1852
- ```typescript
1853
- // React implementation
1854
- import { ComponentStruct, ComponentDef, ComponentParams, Component, ComponentModel } from '@actdim/dynstruct/componentModel/contracts';
1855
- import { useComponent, toReact } from '@actdim/dynstruct/componentModel/react';
1856
- import { AppMsgStruct } from '@actdim/dynstruct/appDomain/appContracts';
1857
-
1858
- // Producer Component
1859
- type ProducerStruct = ComponentStruct<AppMsgStruct, {
1860
- msgScope: {
1861
- provide: {
1862
- 'EVENT-FIRED': { timestamp: number; data: string };
1863
- };
1864
- };
1865
- }>;
1944
+ Data loading is a plain async function — it has nothing to do with effects. Call it from an event handler (`onReady`) or directly from a button click.
1866
1945
 
1867
- const useProducer = (params: ComponentParams<ProducerStruct>) => {
1868
- const def: ComponentDef<ProducerStruct> = {
1869
- // msgBroker is part of ComponentDef
1870
- msgBroker: {
1871
- provide: {
1872
- 'EVENT-FIRED': {
1873
- callback: () => ({
1874
- timestamp: Date.now(),
1875
- data: 'Event fired from producer'
1876
- })
1877
- }
1878
- }
1879
- },
1880
- view: (_, c) => (
1881
- <button onClick={() => {
1882
- // Use component's msgBus to send
1883
- c.msgBus.send({
1884
- channel: 'EVENT-FIRED',
1885
- payload: {}
1886
- });
1887
- }}>
1888
- Fire Event
1889
- </button>
1890
- )
1946
+ ```typescript
1947
+ type Struct = ComponentStruct<ApiMsgStruct, {
1948
+ props: {
1949
+ dataItems: DataItem[];
1891
1950
  };
1892
- return useComponent(def, params);
1893
- };
1894
-
1895
- export const Producer = toReact(useProducer);
1896
-
1897
- // Consumer Component
1898
- type ConsumerStruct = ComponentStruct<AppMsgStruct, {
1899
- props: { lastEvent: string };
1900
1951
  msgScope: {
1901
- subscribe: {
1902
- 'EVENT-FIRED': { timestamp: number; data: string };
1903
- };
1952
+ subscribe: ComponentMsgChannels<'API.TEST.GETDATAITEMS'>;
1953
+ publish: ComponentMsgChannels<'API.TEST.GETDATAITEMS'>;
1904
1954
  };
1905
1955
  }>;
1906
1956
 
1907
- const useConsumer = (params: ComponentParams<ConsumerStruct>) => {
1908
- let c: Component<ConsumerStruct>;
1909
- let m: ComponentModel<ConsumerStruct>;
1910
-
1911
- const def: ComponentDef<ConsumerStruct> = {
1912
- props: { lastEvent: 'No events yet' },
1913
- // msgBroker subscribes to messages
1914
- msgBroker: {
1915
- subscribe: {
1916
- 'EVENT-FIRED': {
1917
- callback: (msg) => {
1918
- // Update reactive property
1919
- m.lastEvent = `${msg.payload.data} at ${new Date(msg.payload.timestamp).toLocaleTimeString()}`;
1920
- }
1921
- }
1922
- }
1923
- },
1924
- view: (_, c) => (
1925
- <div>
1926
- <p>Last Event: {m.lastEvent}</p>
1927
- </div>
1928
- )
1929
- };
1930
-
1931
- c = useComponent(def, params);
1932
- m = c.model; // Properties are now reactive
1933
- return c;
1934
- };
1935
-
1936
- export const Consumer = toReact(useConsumer);
1937
- ```
1938
-
1939
- ### Example 4: Service Integration (API Calls)
1940
-
1941
- ```typescript
1942
- // React implementation
1943
- import { ComponentStruct, ComponentDef, ComponentParams, Component, ComponentModel } from '@actdim/dynstruct/componentModel/contracts';
1944
- import { useComponent, toReact } from '@actdim/dynstruct/componentModel/react';
1945
- import { AppMsgStruct } from '@actdim/dynstruct/appDomain/appContracts';
1946
- import { ClientBase } from '@actdim/dynstruct/net/client';
1947
- import { MsgProviderAdapter } from '@actdim/dynstruct/componentModel/adapters';
1948
- import { ServiceProvider } from '@actdim/dynstruct/services/ServiceProvider';
1949
-
1950
- // Define API client
1951
- class UserApiClient extends ClientBase {
1952
- constructor() {
1953
- super({ baseUrl: 'https://api.example.com' });
1954
- }
1955
-
1956
- async getUsers() {
1957
- return this.get<User[]>('/users');
1958
- }
1957
+ const useApiCallExample = (params: ComponentParams<Struct>) => {
1958
+ let c: Component<Struct>;
1959
+ let m: ComponentModel<Struct>;
1959
1960
 
1960
- async createUser(data: CreateUserDto) {
1961
- return this.post<User>('/users', data);
1961
+ // Plain async function not an effect, not an action
1962
+ async function loadData() {
1963
+ const msg = await c.msgBus.request({
1964
+ channel: 'API.TEST.GETDATAITEMS',
1965
+ payloadFn: (fn) => fn([1, 2], ['first', 'second']),
1966
+ });
1967
+ m.dataItems = msg.payload;
1962
1968
  }
1963
1969
 
1964
- async deleteUser(id: string) {
1965
- return this.delete(`/users/${id}`);
1970
+ async function clear() {
1971
+ m.dataItems.length = 0;
1966
1972
  }
1967
- }
1968
-
1969
- // Create adapter
1970
- const userApiAdapter: MsgProviderAdapter<UserApiClient> = {
1971
- service: new UserApiClient(),
1972
- channelSelector: (service, method) => `API.USERS.${method.toUpperCase()}`
1973
- };
1974
-
1975
- // Register in app provider
1976
- export const ApiServiceProvider = () =>
1977
- ServiceProvider({ adapters: [userApiAdapter] });
1978
1973
 
1979
- // Use in component
1980
- type AppStruct = ComponentStruct<AppMsgStruct, {
1981
- props: { users: User[]; loading: boolean };
1982
- }>;
1983
-
1984
- const useApp = (params: ComponentParams<AppStruct>) => {
1985
- let c: Component<AppStruct>;
1986
- let m: ComponentModel<AppStruct>;
1987
-
1988
- const def: ComponentDef<AppStruct> = {
1974
+ const def: ComponentDef<Struct> = {
1975
+ regType: 'ApiCallExample',
1989
1976
  props: {
1990
- users: [],
1991
- loading: false
1977
+ dataItems: [],
1992
1978
  },
1993
- effects: {
1994
- 'loadUsers': async (component) => {
1995
- m.loading = true; // Reactive update
1996
- const response = await component.msgBus.request({
1997
- channel: 'API.USERS.GETUSERS',
1998
- payload: {}
1999
- });
2000
- m.users = response.payload; // Reactive update
2001
- m.loading = false;
2002
- }
1979
+ events: {
1980
+ // Load data when the component is ready
1981
+ onReady: () => { loadData(); },
2003
1982
  },
2004
1983
  view: (_, c) => (
2005
- <div>
2006
- <h2>Users</h2>
2007
- {m.loading ? (
2008
- <p>Loading...</p>
2009
- ) : (
2010
- <ul>
2011
- {m.users.map(user => (
2012
- <li key={user.id}>{user.name}</li>
2013
- ))}
2014
- </ul>
2015
- )}
1984
+ <div id={c.id}>
1985
+ <button onClick={loadData}>Load data</button>
1986
+ <button onClick={clear}>Clear</button>
1987
+ <ul>
1988
+ {m.dataItems.map((item) => (
1989
+ <li key={item.id}>{item.id}: {item.name}</li>
1990
+ ))}
1991
+ </ul>
2016
1992
  </div>
2017
- )
1993
+ ),
2018
1994
  };
2019
1995
 
2020
1996
  c = useComponent(def, params);
2021
- m = c.model; // Properties are reactive after useComponent
1997
+ m = c.model;
2022
1998
  return c;
2023
1999
  };
2024
2000
 
2025
- export const App = toReact(useApp);
2001
+ export const ApiCallExample = toReact(useApiCallExample);
2026
2002
  ```
2027
2003
 
2028
- ### Example 5: Navigation
2004
+ ### Navigation
2029
2005
 
2030
2006
  ```typescript
2031
2007
  // React implementation
@@ -2072,7 +2048,7 @@ const usePage = (params: ComponentParams<PageStruct>) => {
2072
2048
  export const Page = toReact(usePage);
2073
2049
  ```
2074
2050
 
2075
- ### Example 6: Authentication & Security
2051
+ ### Authentication & Security
2076
2052
 
2077
2053
  ```typescript
2078
2054
  // React implementation
@@ -2174,9 +2150,6 @@ The framework provides standard message channels for common operations:
2174
2150
  - `$AUTH_REFRESH` - Refresh authentication token
2175
2151
  - `$AUTH_ENSURE` - Ensure user is authenticated
2176
2152
 
2177
- #### Access Control
2178
- - `$ACL_GET` - Get access control list
2179
-
2180
2153
  ### Component Lifecycle
2181
2154
 
2182
2155
  Components go through the following lifecycle stages:
@@ -2200,9 +2173,11 @@ Messages can be filtered by source using `ComponentMsgFilter`:
2200
2173
  msgBroker: {
2201
2174
  subscribe: {
2202
2175
  'MY-EVENT': {
2203
- filter: { FromAncestors: true },
2204
- callback: (msg) => {
2205
- // Only triggered by parent components
2176
+ in: {
2177
+ callback: (msg, component) => {
2178
+ // Only triggered by parent/ancestor components
2179
+ },
2180
+ componentFilter: ComponentMsgFilter.FromAncestors
2206
2181
  }
2207
2182
  }
2208
2183
  }
@@ -2259,12 +2234,13 @@ npm run storybook
2259
2234
  ```
2260
2235
 
2261
2236
  Available stories:
2262
- - **SimpleComponent** - Basic reactive component with props and children
2263
- - **ConnectionExample** - Message bus producer/consumer pattern
2264
- - **ParentChildConnectionExample** - Parent-child component messaging
2265
- - **ApiCallExample** - HTTP request integration with service adapters
2266
- - **LocalMsgStructExample** - Local message structure with todo list
2267
- - **StorageServiceExample** - Storage service provider usage
2237
+ - **dynstruct / Basics / SimpleComponent** - Basic reactive component with props and children
2238
+ - **dynstruct / Basics / Api Call Example** - HTTP request integration with service adapters
2239
+ - **dynstruct / Basics / Effect Demo** - Auto-tracking reactive effects with pause/resume
2240
+ - **dynstruct / Basics / Local Msg Struct** - Local message structure with todo list
2241
+ - **dynstruct / Basics / Storage Service** - Storage service provider usage
2242
+ - **dynstruct / Connection / Basics** - Message bus producer/consumer pattern
2243
+ - **dynstruct / Connection / Parent/Child** - Parent-child component messaging
2268
2244
 
2269
2245
  ## Development
2270
2246