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 +123 -68
- package/package.json +1 -1
- package/src/core/plcAPI.tsx +53 -19
- package/src/plug-code.tsx +1 -1
- package/types/plug-code.d.ts +3 -3
package/README.md
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
# Plug&Code
|
|
1
|
+
# Plug&Code 🔌
|
|
2
2
|
|
|
3
|
-
Plug&Code is a multipurpose framework
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
22
|
+
Stores (State): Isolated state containers (substores) that can be linked reactively.
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
Slots (UI Pipeline): Inject components into pre-defined locations from any feature.
|
|
25
25
|
|
|
26
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
64
|
+
TypeScript
|
|
37
65
|
|
|
38
|
-
|
|
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
|
-
|
|
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
|
-
|
|
82
|
+
TypeScript
|
|
69
83
|
|
|
70
|
-
|
|
84
|
+
// App.tsx
|
|
85
|
+
import { useSystemPlc, SystemPlcRoot } from './system';
|
|
71
86
|
|
|
72
|
-
```tsx
|
|
73
87
|
function App() {
|
|
74
|
-
// Initialize
|
|
75
|
-
const { api, useSelector } = useSystemPlc({
|
|
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.
|
|
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
|
-
|
|
152
|
+
Business Logic (Commands)
|
|
153
|
+
api.registerCommand(id, fn): Registers an executable action.
|
|
95
154
|
|
|
96
|
-
|
|
155
|
+
api.execute(id, payload): Runs a command and returns a Promise.
|
|
97
156
|
|
|
98
|
-
|
|
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
|
-
|
|
159
|
+
Data Processing (Transforms)
|
|
160
|
+
api.send(channel, id, fn, priority): Adds a transformation step to a data pipeline.
|
|
103
161
|
|
|
104
|
-
|
|
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
|
-
|
|
164
|
+
🌟 Best Practices
|
|
165
|
+
Independent Files: Keep each feature in its own file (e.g., AuthFeature.ts, TableFeature.ts).
|
|
109
166
|
|
|
110
|
-
|
|
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
|
-
|
|
169
|
+
Scope Dependencies: Always pass the dependencyKey (3rd arg) in api.register to ensure optimal performance.
|
|
114
170
|
|
|
115
|
-
|
|
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
package/src/core/plcAPI.tsx
CHANGED
|
@@ -35,20 +35,33 @@ export class PlcAPI<S extends ObjectType> {
|
|
|
35
35
|
this.pipeline = new PlcPipeline(store)
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
112
|
+
|
|
113
|
+
runCompute()
|
|
88
114
|
|
|
89
115
|
dependencies.forEach(dep => {
|
|
90
116
|
this.store.subscribe(dep as any, () => {
|
|
91
117
|
if (isComputing) return
|
|
92
|
-
|
|
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.
|
|
42
|
+
api.override("root", initialProps)
|
|
43
43
|
}, [initialProps, api]);
|
|
44
44
|
|
|
45
45
|
const useSelector = <Result,>(selector: (state: any) => Result): Result => {
|
package/types/plug-code.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|