plug-code 1.1.12 → 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.12",
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
+ }
57
+
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);
50
61
 
51
- return this;
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) {
@@ -69,26 +82,46 @@ export class PlcAPI<S extends ObjectType> {
69
82
  calculator: () => any
70
83
  ) {
71
84
  let lastValue: any
85
+ let isComputing = false
72
86
 
73
- const runUpdate = () => {
74
- const result = calculator()
75
- lastValue = result
76
- this.replace("root" as any, { [outputKey]: result })
87
+ let scheduled = false
88
+
89
+ const scheduleUpdate = () => {
90
+ if (scheduled) return
91
+ scheduled = true
92
+
93
+ queueMicrotask(() => {
94
+ scheduled = false
95
+ runCompute()
96
+ })
77
97
  }
78
98
 
79
- // cálculo inicial inmediato
80
- runUpdate()
99
+ const runCompute = () => {
100
+ if (isComputing) return
101
+
102
+ isComputing = true
103
+ try {
104
+ const result = calculator()
105
+ lastValue = result
106
+ this.replace("root" as any, { [outputKey]: result })
107
+ } finally {
108
+ isComputing = false
109
+ }
110
+ }
111
+
112
+
113
+ runCompute()
81
114
 
82
- // suscripción a dependencias
83
115
  dependencies.forEach(dep => {
84
- this.store.subscribe(dep as any, runUpdate)
116
+ this.store.subscribe(dep as any, () => {
117
+ if (isComputing) return
118
+ scheduleUpdate()
119
+ })
85
120
  })
86
121
 
87
122
  return () => lastValue
88
123
  }
89
124
 
90
-
91
-
92
125
  register(slot: SlotKey, node: () => React.ReactNode): void;
93
126
  register<K extends string>(slot: SlotKey, node: (data: any) => React.ReactNode, dependencyKey: K): void;
94
127
  register(slot: SlotKey, node: (data?: any) => React.ReactNode, dependencyKey?: string) {
@@ -284,7 +317,7 @@ export class PlcAPI<S extends ObjectType> {
284
317
  this.substores.set(key, initialState)
285
318
  }
286
319
 
287
- 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) {
288
321
  const sub = this.substores.get(key)
289
322
  if (!sub) return
290
323
 
@@ -295,5 +328,16 @@ export class PlcAPI<S extends ObjectType> {
295
328
  if (slot) {
296
329
  this.invalidate(slot)
297
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
+ }
298
342
  }
299
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