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 +102 -66
- 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,116 +1,152 @@
|
|
|
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**.
|
|
4
|
+
It empowers developers to build complex applications by **plugging in independent feature modules** without tightly coupling the codebase.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
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
|
-
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 🧠 Core Concepts
|
|
23
23
|
|
|
24
|
-
Plug&Code is built around the **PLC (Pipeline
|
|
24
|
+
Plug&Code is built around the **PLC (Pipeline–Logic–Command)** pattern combined with a specialized **Reactive State Machine**.
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
36
|
+
## 🚀 Quick Start
|
|
33
37
|
|
|
34
|
-
|
|
38
|
+
### 1️⃣ Create a Feature Module
|
|
35
39
|
|
|
36
|
-
|
|
40
|
+
```ts
|
|
41
|
+
// features/PaginationFeature.ts
|
|
42
|
+
import type { PlcAPI } from 'plug-code';
|
|
37
43
|
|
|
38
|
-
|
|
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
|
-
|
|
77
|
+
---
|
|
69
78
|
|
|
70
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
103
|
+
## 📚 API Reference
|
|
95
104
|
|
|
96
|
-
|
|
105
|
+
### 🧬 State & Reactivity
|
|
97
106
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
### 🎨 UI Slots
|
|
103
118
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
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
|
-
|
|
111
|
-
* `api.receive(channel, initialData)`: Pipes data through all transformers in the channel.
|
|
145
|
+
## 🌟 Best Practices
|
|
112
146
|
|
|
113
|
-
|
|
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
|
-
|
|
116
|
-
* `api.update(key, updater)`: Update store data using Immer-powered drafts.
|
|
152
|
+
---
|
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
|
|