plug-code 1.1.13 → 1.1.15

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
@@ -1,116 +1,152 @@
1
- # Plug&Code
1
+ # 🔌 Plug&Code
2
2
 
3
- Plug&Code is a multipurpose framework for React designed for **scalability, reusability, and modular organization**. It allows you to build complex applications by "plugging in" features without tightly coupling your codebase.
3
+ **Plug&Code** is a multipurpose React framework designed for **scalability, reusability, and modular organization**.
4
+ It empowers developers to build complex applications by **plugging in independent feature modules** without tightly coupling the codebase.
4
5
 
5
- ## Usage
6
-
7
- You are welcome to use Plug&Code in your projects, **personal or commercial**, as long as you **do not modify or redistribute the framework** without explicit permission from the author.
6
+ > **License**
7
+ > You may use Plug&Code in personal or commercial projects.
8
+ > **Modification or redistribution of the framework source code is prohibited** without explicit permission.
8
9
 
9
10
  ---
10
11
 
11
- ### Installation
12
-
13
- Install the framework via npm or yarn:
12
+ ## 📦 Installation
14
13
 
15
14
  ```bash
16
15
  npm install plug-code
17
16
  # or
18
17
  yarn add plug-code
19
-
20
18
  ```
21
19
 
22
- ### Core Concepts
20
+ ---
21
+
22
+ ## 🧠 Core Concepts
23
23
 
24
- Plug&Code is built around the **PLC (Pipeline-Logic-Command)** pattern:
24
+ Plug&Code is built around the **PLC (PipelineLogicCommand)** pattern combined with a specialized **Reactive State Machine**.
25
25
 
26
- * **Slots (UI Pipeline):** Inject components into pre-defined locations from any feature.
27
- * **Commands (Logic):** Execute and wrap business actions (e.g., `checkout`, `print`, `save`).
28
- * **Transforms (Data):** Pass data through named channels to be modified by different features.
26
+ | Concept | Description |
27
+ | ----------------------- | -------------------------------------------------------- |
28
+ | **Features** | Independent modules that encapsulate logic, UI, and data |
29
+ | **Stores (State)** | Isolated substores with reactive linking |
30
+ | **Slots (UI Pipeline)** | Injection points for UI from any feature |
31
+ | **Commands (Logic)** | Executable business actions with middleware |
32
+ | **Transforms (Data)** | Data pipelines modified by multiple features |
29
33
 
30
34
  ---
31
35
 
32
- ### Quick Start Guide
36
+ ## 🚀 Quick Start
33
37
 
34
- #### 1. Initialize your System
38
+ ### 1️⃣ Create a Feature Module
35
39
 
36
- Define your system and register features using the fluent API.
40
+ ```ts
41
+ // features/PaginationFeature.ts
42
+ import type { PlcAPI } from 'plug-code';
37
43
 
38
- ```tsx
44
+ export const PaginationFeature = (api: PlcAPI<any>) => {
45
+ api.createData("pagination", { currentPage: 1, pageSize: 10, total: 0 });
46
+
47
+ api.derive("activePage", ["pagination"], () => api.getData("pagination"));
48
+
49
+ api.register("table-footer", (pageData) => {
50
+ const { currentPage } = pageData;
51
+
52
+ const goNext = () => api.update("pagination", d => { d.currentPage++ });
53
+
54
+ return <button onClick={goNext}>Page {currentPage}</button>;
55
+ }, "pagination");
56
+ };
57
+ ```
58
+
59
+ ---
60
+
61
+ ### 2️⃣ Initialize the System
62
+
63
+ ```ts
64
+ // system.ts
39
65
  import { createPlugAndCode } from 'plug-code';
66
+ import { PaginationFeature } from './features/PaginationFeature';
67
+ import { SalesFeature } from './features/SalesFeature';
40
68
 
41
69
  export const { useSystemPlc, SystemPlcRoot } = createPlugAndCode((api) => {
42
-
43
- // Define a Sales Feature
44
- api.createFuture("sales", (api) => {
45
- // Register a UI component into a slot
46
- api.register("header.cart", () => <CartIcon />);
47
-
48
- // Register a business command
49
- api.registerCommand("sales.checkout", async (items) => {
50
- console.log("Saving items to database...", items);
51
- return { success: true };
52
- });
53
- });
54
-
55
- // Define a Logger Feature that wraps existing logic
56
- api.createFuture("logger", (api) => {
57
- api.wrapCommand("sales.checkout", (next) => async (items) => {
58
- console.log("Checkout started...");
59
- const result = await next(items);
60
- console.log("Checkout finished:", result);
61
- return result;
62
- });
63
- });
64
- });
70
+ api.createData("root", { appName: "My Dashboard", theme: "dark" });
65
71
 
72
+ PaginationFeature(api);
73
+ SalesFeature(api);
74
+ });
66
75
  ```
67
76
 
68
- #### 2. Wrap your Application
77
+ ---
69
78
 
70
- Use the `useSystemPlc` hook to manage the state and `SystemPlcRoot` to provide the context.
79
+ ### 3️⃣ Wrap Your Application
71
80
 
72
81
  ```tsx
82
+ // App.tsx
83
+ import { useSystemPlc, SystemPlcRoot } from './system';
84
+
73
85
  function App() {
74
- // Initialize state with initial properties
75
- const { api, useSelector } = useSystemPlc({ shopName: "My Store" });
86
+ const { api, useSelector } = useSystemPlc({ mode: "production" });
76
87
 
77
88
  return (
78
89
  <SystemPlcRoot api={api}>
79
- <nav>
80
- {/* Render slots from any feature */}
81
- {api.render("header.cart")}
82
- </nav>
83
90
  <main>
84
- <h1>Welcome to {useSelector(s => s.root.shopName)}</h1>
91
+ <h1>Welcome to {useSelector(s => s.root.appName)}</h1>
92
+ <div className="footer-area">
93
+ {api.render("table-footer")}
94
+ </div>
85
95
  </main>
86
96
  </SystemPlcRoot>
87
97
  );
88
98
  }
89
-
90
99
  ```
91
100
 
92
101
  ---
93
102
 
94
- ### API Reference
103
+ ## 📚 API Reference
95
104
 
96
- #### **UI Management (Slots)**
105
+ ### 🧬 State & Reactivity
97
106
 
98
- * `api.register(slotName, component)`: Adds a UI component to a slot.
99
- * `api.render(slotName)`: Renders all components registered in that slot.
100
- * `api.wrap(slot, wrapper)`: Wraps a slot's content with higher-order components.
107
+ | Method | Description |
108
+ | --------------------------------------- | ------------------------ |
109
+ | `createData(key, initial)` | Create a new store |
110
+ | `getData(key)` | Get snapshot of store |
111
+ | `update(key, updater, slot?, trigger?)` | Mutate store using Immer |
112
+ | `derive(target, deps, calc)` | Create reactive linkage |
113
+ | `watch(key, selector, cb)` | Listen to store changes |
101
114
 
102
- #### **Business Logic (Commands)**
115
+ ---
116
+
117
+ ### 🎨 UI Slots
103
118
 
104
- * `api.registerCommand(id, fn)`: Registers an executable action.
105
- * `api.execute(id, payload)`: Runs a command and returns a Promise.
106
- * `api.wrapCommand(id, (next) => ...)`: Intercepts a command to add side-effects or validations.
119
+ | Method | Description |
120
+ | ------------------------------------ | -------------------- |
121
+ | `register(slot, component, depKey?)` | Attach UI to slot |
122
+ | `render(slot, props?)` | Render slot pipeline |
123
+
124
+ ---
107
125
 
108
- #### **Data Processing (Transforms)**
126
+ ### 🧠 Business Logic (Commands)
127
+
128
+ | Method | Description |
129
+ | ----------------------------- | ----------------- |
130
+ | `registerCommand(id, fn)` | Register action |
131
+ | `execute(id, payload)` | Run command |
132
+ | `wrapCommand(id, middleware)` | Intercept command |
133
+
134
+ ---
135
+
136
+ ### 🔄 Data Transforms
137
+
138
+ | Method | Description |
139
+ | --------------------------------- | ------------- |
140
+ | `send(channel, id, fn, priority)` | Add transform |
141
+ | `receive(channel, data)` | Run pipeline |
142
+
143
+ ---
109
144
 
110
- * `api.send(channel, id, fn, priority)`: Adds a data transformer to a specific channel.
111
- * `api.receive(channel, initialData)`: Pipes data through all transformers in the channel.
145
+ ## 🌟 Best Practices
112
146
 
113
- #### **State Management**
147
+ * Keep **one feature per file**
148
+ * Prefer `derive` over manual syncing
149
+ * Always specify `dependencyKey` in `register`
150
+ * Avoid putting everything in `root` — create focused stores
114
151
 
115
- * `useSelector(selector)`: Reactively listen to state changes.
116
- * `api.update(key, updater)`: Update store data using Immer-powered drafts.
152
+ ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plug-code",
3
- "version": "1.1.13",
3
+ "version": "1.1.15",
4
4
  "description": "",
5
5
  "main": "src/plug-code.tsx",
6
6
  "types": "types/plug-code.d.ts",
@@ -35,20 +35,33 @@ export class PlcAPI<S extends ObjectType> {
35
35
  this.pipeline = new PlcPipeline(store)
36
36
  }
37
37
 
38
- createFeature(name: string, setupFn: (api: PlcAPI<S>) => void): PlcAPI<S> {
39
- if (this.installedFeatures.has(name)) {
40
- console.warn(`[PlcFramework] Feature '${name}' is already registered. It will be skipped to avoid conflicts.`);
41
- return this;
42
- }
38
+ watch<T>(
39
+ storeKey: string,
40
+ selector: (data: any) => T,
41
+ callback: (newValue: T, oldValue: T) => void
42
+ ): () => void {
43
+ let previousValue = selector(this.getData(storeKey));
44
+ const unsubscribe = this.store.subscribe(storeKey as any, () => {
45
+ const currentData = this.getData(storeKey);
46
+ const newValue = selector(currentData);
47
+
48
+ if (newValue !== previousValue) {
49
+ const old = previousValue;
50
+ previousValue = newValue;
51
+ callback(newValue, old);
52
+ }
53
+ });
43
54
 
44
- try {
45
- setupFn(this);
46
- this.installedFeatures.add(name);
47
- } catch (error) {
48
- console.error(`[PlcFramework] 💥 Critical error initializing feature '${name}':`, error);
49
- }
55
+ return unsubscribe;
56
+ }
50
57
 
51
- return this;
58
+ override<K extends string>(key: K & "root", data: any, slot?: string) {
59
+ this.substores.set(key, data);
60
+ this.store.set(key as any, data);
61
+
62
+ if (slot) {
63
+ this.invalidate(slot);
64
+ }
52
65
  }
53
66
 
54
67
  replace<K extends string>(key: K & "root", data: Partial<any>, slot?: string) {
@@ -71,7 +84,19 @@ export class PlcAPI<S extends ObjectType> {
71
84
  let lastValue: any
72
85
  let isComputing = false
73
86
 
74
- const runUpdate = () => {
87
+ let scheduled = false
88
+
89
+ const scheduleUpdate = () => {
90
+ if (scheduled) return
91
+ scheduled = true
92
+
93
+ queueMicrotask(() => {
94
+ scheduled = false
95
+ runCompute()
96
+ })
97
+ }
98
+
99
+ const runCompute = () => {
75
100
  if (isComputing) return
76
101
 
77
102
  isComputing = true
@@ -84,21 +109,19 @@ export class PlcAPI<S extends ObjectType> {
84
109
  }
85
110
  }
86
111
 
87
- runUpdate()
112
+
113
+ runCompute()
88
114
 
89
115
  dependencies.forEach(dep => {
90
116
  this.store.subscribe(dep as any, () => {
91
117
  if (isComputing) return
92
- runUpdate()
118
+ scheduleUpdate()
93
119
  })
94
120
  })
95
121
 
96
122
  return () => lastValue
97
123
  }
98
124
 
99
-
100
-
101
-
102
125
  register(slot: SlotKey, node: () => React.ReactNode): void;
103
126
  register<K extends string>(slot: SlotKey, node: (data: any) => React.ReactNode, dependencyKey: K): void;
104
127
  register(slot: SlotKey, node: (data?: any) => React.ReactNode, dependencyKey?: string) {
@@ -294,7 +317,7 @@ export class PlcAPI<S extends ObjectType> {
294
317
  this.substores.set(key, initialState)
295
318
  }
296
319
 
297
- update<K extends keyof S>(key: string & "root", updater: (draft: any) => void, slot?: string) {
320
+ update<K extends keyof S>(key: string & "root", updater: (draft: any) => void, slot?: string, triggerKey?: string) {
298
321
  const sub = this.substores.get(key)
299
322
  if (!sub) return
300
323
 
@@ -305,5 +328,16 @@ export class PlcAPI<S extends ObjectType> {
305
328
  if (slot) {
306
329
  this.invalidate(slot)
307
330
  }
331
+
332
+ if (triggerKey && triggerKey !== key) {
333
+
334
+ const triggerData = this.substores.get(triggerKey);
335
+
336
+ if (triggerData) {
337
+ const newTriggerRef = { ...triggerData };
338
+ this.substores.set(triggerKey, newTriggerRef);
339
+ this.store.set(triggerKey as any, newTriggerRef);
340
+ }
341
+ }
308
342
  }
309
343
  }
package/src/plug-code.tsx CHANGED
@@ -39,7 +39,7 @@ export function createPlugAndCode<S extends ObjectType>(
39
39
  }, []);
40
40
 
41
41
  useEffect(() => {
42
- api.replace("root", initialProps)
42
+ api.override("root", initialProps)
43
43
  }, [initialProps, api]);
44
44
 
45
45
  const useSelector = <Result,>(selector: (state: any) => Result): Result => {
@@ -52,8 +52,8 @@ export declare class PlcAPI<S extends ObjectType> {
52
52
  * @param name Identificador único para debugging y prevención de duplicados.
53
53
  * @param setupFn Función de configuración donde registras slots, comandos, etc.
54
54
  */
55
- createFeature(name: string, setupFn: (api: PlcAPI<S>) => void): PlcAPI<S>;
56
-
55
+ watch<T>(storeKey: string, selector: (data: any) => T, callback: (newValue: T, oldValue: T) => void): () => void
56
+ override<K extends string>(key: K & "root", data: any, slot?: string): void
57
57
  // --- Gestión de UI (Slots & Rendering) ---
58
58
 
59
59
  register(slot: string, node: () => React.ReactNode): void;
@@ -79,7 +79,7 @@ export declare class PlcAPI<S extends ObjectType> {
79
79
 
80
80
  derive<K extends string>(outputKey: K, dependencies: string[], calculator: () => any): void
81
81
 
82
- update(key: string | "root", updater: (draft: any) => void, slot?: string): void;
82
+ update(key: string | "root", updater: (draft: any) => void, slot?: string, triggerKey?: string): void;
83
83
 
84
84
  subscribe(listener: () => void): () => void;
85
85