plug-code 1.1.13 → 1.1.14

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,14 +1,12 @@
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**. It empowers developers to build complex applications by "plugging in" independent feature modules without tightly coupling the codebase.
4
4
 
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.
5
+ > **License:** You are welcome to use Plug&Code in your personal or commercial projects. Modification or redistribution of the framework source code is prohibited without explicit permission.
8
6
 
9
7
  ---
10
8
 
11
- ### Installation
9
+ ## 📦 Installation
12
10
 
13
11
  Install the framework via npm or yarn:
14
12
 
@@ -16,101 +14,158 @@ Install the framework via npm or yarn:
16
14
  npm install plug-code
17
15
  # or
18
16
  yarn add plug-code
17
+ 🧠 Core Concepts
18
+ Plug&Code is built around the PLC (Pipeline-Logic-Command) pattern combined with a specialized Reactive State Machine:
19
19
 
20
- ```
20
+ Features: Independent functions that encapsulate Logic, UI, and Data. No more monolithic configurations.
21
21
 
22
- ### Core Concepts
22
+ Stores (State): Isolated state containers (substores) that can be linked reactively.
23
23
 
24
- Plug&Code is built around the **PLC (Pipeline-Logic-Command)** pattern:
24
+ Slots (UI Pipeline): Inject components into pre-defined locations from any feature.
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
+ Commands (Logic): Execute and wrap business actions (e.g., checkout, print) with middleware support.
29
27
 
30
- ---
28
+ Transforms (Data): Pass data through named channels to be modified by different features before rendering.
29
+
30
+ 🚀 Quick Start Guide
31
+ 1. Create a Feature Module
32
+ Define features in separate files. A feature is simply a function that receives the api.
33
+
34
+ TypeScript
35
+
36
+ // features/PaginationFeature.ts
37
+ import type { PlcAPI } from 'plug-code';
38
+
39
+ export const PaginationFeature = (api: PlcAPI<any>) => {
40
+ // 1. Initialize State
41
+ api.createData("pagination", { currentPage: 1, pageSize: 10, total: 0 });
42
+
43
+ // 2. Reactive Linking (Derive)
44
+ // Automatically update 'root.activePage' when 'pagination' changes
45
+ api.derive("activePage", ["pagination"], () => api.getData("pagination"));
31
46
 
32
- ### Quick Start Guide
47
+ // 3. Register UI Component
48
+ // The 3rd argument ("pagination") subscribes this component to the store.
49
+ api.register("table-footer", (pageData) => {
50
+ const { currentPage } = pageData;
51
+
52
+ // Use the extended update to modify data
53
+ const goNext = () => api.update(
54
+ "pagination", // Store to update
55
+ d => { d.currentPage++ } // Updater (Immer draft)
56
+ );
33
57
 
34
- #### 1. Initialize your System
58
+ return <button onClick={goNext}>Page {currentPage}</button>;
59
+ }, "pagination");
60
+ };
61
+ 2. Initialize your System
62
+ Import your feature functions and inject them into the system.
35
63
 
36
- Define your system and register features using the fluent API.
64
+ TypeScript
37
65
 
38
- ```tsx
66
+ // system.ts
39
67
  import { createPlugAndCode } from 'plug-code';
68
+ import { PaginationFeature } from './features/PaginationFeature';
69
+ import { SalesFeature } from './features/SalesFeature';
40
70
 
41
71
  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
- });
72
+ // Initialize global root data
73
+ api.createData("root", { appName: "My Dashboard", theme: "dark" });
65
74
 
66
- ```
75
+ // Install Features
76
+ PaginationFeature(api);
77
+ SalesFeature(api);
78
+ });
79
+ 3. Wrap your Application
80
+ Use the hooks to provide context and render slots.
67
81
 
68
- #### 2. Wrap your Application
82
+ TypeScript
69
83
 
70
- Use the `useSystemPlc` hook to manage the state and `SystemPlcRoot` to provide the context.
84
+ // App.tsx
85
+ import { useSystemPlc, SystemPlcRoot } from './system';
71
86
 
72
- ```tsx
73
87
  function App() {
74
- // Initialize state with initial properties
75
- const { api, useSelector } = useSystemPlc({ shopName: "My Store" });
88
+ // Initialize system with props (synced to "root" store automatically)
89
+ const { api, useSelector } = useSystemPlc({ mode: "production" });
76
90
 
77
91
  return (
78
92
  <SystemPlcRoot api={api}>
79
- <nav>
80
- {/* Render slots from any feature */}
81
- {api.render("header.cart")}
82
- </nav>
83
93
  <main>
84
- <h1>Welcome to {useSelector(s => s.root.shopName)}</h1>
94
+ <h1>Welcome to {useSelector(s => s.root.appName)}</h1>
95
+
96
+ {/* Render slots: The pipeline assembles all registered components */}
97
+ <div className="footer-area">
98
+ {api.render("table-footer")}
99
+ </div>
85
100
  </main>
86
101
  </SystemPlcRoot>
87
102
  );
88
103
  }
104
+ 📚 API Reference
105
+ State Management & Reactivity
106
+ The framework uses an isolated store architecture with reactive capabilities using Immer.
89
107
 
90
- ```
108
+ api.createData(key, initialData)
109
+ Initializes a new substore.
91
110
 
92
- ---
111
+ key: Unique identifier (e.g., "pagination").
112
+
113
+ initialData: The starting object.
114
+
115
+ api.getData(key)
116
+ Imperatively retrieves the current snapshot of a store.
117
+
118
+ api.update(key, updater, [slot], [triggerKey])
119
+ Updates the state using Immer-powered drafts.
120
+
121
+ key: The store to update.
122
+
123
+ updater: (draft) => void. Mutate the draft directly.
124
+
125
+ slot (Optional): Name of the UI slot to invalidate (clears visual cache).
126
+
127
+ triggerKey (Optional): Name of another store to force-update (useful for manual dependency triggering without derive).
128
+
129
+ api.derive(targetKey, dependencies, calculator)
130
+ Creates a reactive link. When dependencies change, the target is recalculated automatically.
131
+
132
+ targetKey: Where to save the result.
133
+
134
+ dependencies: Array of store keys to listen to.
135
+
136
+ api.watch(key, selector, callback)
137
+ Listens for changes to perform side effects (logging, analytics, etc.).
138
+
139
+ UI Management (Slots)
140
+ api.register(slotName, componentFn, [dependencyKey])
141
+ Adds a component to a pipeline.
142
+
143
+ slotName: Where to inject the component.
144
+
145
+ componentFn: Function receiving data and returning JSX.
146
+
147
+ dependencyKey: The store key this component subscribes to. It triggers a re-render only when that specific store changes.
148
+
149
+ api.render(slotName, [props])
150
+ Renders the pipeline for the given slot name.
93
151
 
94
- ### API Reference
152
+ Business Logic (Commands)
153
+ api.registerCommand(id, fn): Registers an executable action.
95
154
 
96
- #### **UI Management (Slots)**
155
+ api.execute(id, payload): Runs a command and returns a Promise.
97
156
 
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.
157
+ api.wrapCommand(id, middlewareFn): Intercepts a command to add logic before/after execution.
101
158
 
102
- #### **Business Logic (Commands)**
159
+ Data Processing (Transforms)
160
+ api.send(channel, id, fn, priority): Adds a transformation step to a data pipeline.
103
161
 
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.
162
+ api.receive(channel, initialData): Pipes data through all transformers in the channel.
107
163
 
108
- #### **Data Processing (Transforms)**
164
+ 🌟 Best Practices
165
+ Independent Files: Keep each feature in its own file (e.g., AuthFeature.ts, TableFeature.ts).
109
166
 
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.
167
+ Use Derive: Prefer api.derive over manual updates to sync data between stores.
112
168
 
113
- #### **State Management**
169
+ Scope Dependencies: Always pass the dependencyKey (3rd arg) in api.register to ensure optimal performance.
114
170
 
115
- * `useSelector(selector)`: Reactively listen to state changes.
116
- * `api.update(key, updater)`: Update store data using Immer-powered drafts.
171
+ Avoid "Root" Spam: Create specific stores ("filters", "auth", "cart") instead of putting everything in "root". This prevents unnecessary re-renders.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plug-code",
3
- "version": "1.1.13",
3
+ "version": "1.1.14",
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