plug-code 2.3.0 → 2.4.0
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 +412 -284
- package/dist/core/helpers/core.d.ts +2 -2
- package/dist/core/helpers/core.d.ts.map +1 -1
- package/dist/core/helpers/core.js +21 -17
- package/dist/core/helpers/core.js.map +1 -1
- package/dist/core/hooks/plcHooks.d.ts.map +1 -1
- package/dist/core/hooks/plcHooks.js +3 -9
- package/dist/core/hooks/plcHooks.js.map +1 -1
- package/dist/core/plcAPI.d.ts +47 -4
- package/dist/core/plcAPI.d.ts.map +1 -1
- package/dist/core/plcAPI.js +288 -30
- package/dist/core/plcAPI.js.map +1 -1
- package/dist/core/plcScheduler.d.ts.map +1 -1
- package/dist/core/plcScheduler.js +8 -2
- package/dist/core/plcScheduler.js.map +1 -1
- package/dist/core/plcStore.js +3 -3
- package/dist/core/plcStore.js.map +1 -1
- package/dist/core/ui/plcCore.d.ts +8 -5
- package/dist/core/ui/plcCore.d.ts.map +1 -1
- package/dist/core/ui/plcCore.js +151 -36
- package/dist/core/ui/plcCore.js.map +1 -1
- package/dist/core/ui/plcLayout.d.ts +11 -0
- package/dist/core/ui/plcLayout.d.ts.map +1 -1
- package/dist/core/ui/plcLayout.js +51 -16
- package/dist/core/ui/plcLayout.js.map +1 -1
- package/dist/core/ui/plcSlotRenderer.js +2 -2
- package/dist/core/ui/plcSlotRenderer.js.map +1 -1
- package/dist/core/workers/codes.d.ts +2 -0
- package/dist/core/workers/codes.d.ts.map +1 -0
- package/dist/core/workers/codes.js +16 -0
- package/dist/core/workers/codes.js.map +1 -0
- package/dist/core/workers/filterWorker.d.ts +16 -0
- package/dist/core/workers/filterWorker.d.ts.map +1 -0
- package/dist/core/workers/filterWorker.js +168 -0
- package/dist/core/workers/filterWorker.js.map +1 -0
- package/dist/core/workers/helpers/filter.worker.d.ts +2 -0
- package/dist/core/workers/helpers/filter.worker.d.ts.map +1 -0
- package/dist/core/workers/helpers/filter.worker.js +57 -0
- package/dist/core/workers/helpers/filter.worker.js.map +1 -0
- package/dist/core/workers/search.worker.d.ts +2 -0
- package/dist/core/workers/search.worker.d.ts.map +1 -0
- package/dist/core/workers/search.worker.js +82 -0
- package/dist/core/workers/search.worker.js.map +1 -0
- package/dist/core.d.ts +5 -0
- package/dist/core.d.ts.map +1 -1
- package/dist/core.js +6 -0
- package/dist/core.js.map +1 -1
- package/dist/simple.d.ts +5 -4
- package/dist/simple.d.ts.map +1 -1
- package/dist/simple.js +14 -7
- package/dist/simple.js.map +1 -1
- package/dist/types/core/ui.d.ts +7 -2
- package/dist/types/core/ui.d.ts.map +1 -1
- package/dist/types/core.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,390 +1,518 @@
|
|
|
1
|
-
#
|
|
1
|
+
# PlugC Framework ⚡
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<div align="center">
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**The missing piece for your React applications**
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
## 🚀 Key Features
|
|
7
|
+
A lightning-fast, modular state management and UI composition framework that just works.
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
* **Enterprise:** Schema-first, strict contracts, Registry Pattern for large teams.
|
|
9
|
+
[](https://www.npmjs.com/package/plug-code)
|
|
10
|
+
[](https://www.typescriptlang.org/)
|
|
14
11
|
|
|
12
|
+
[Quick Start](#quick-start) • [Features](#features-that-make-you-smile) • [Examples](#real-world-examples) • [API Reference](#core-concepts-in-30-seconds)
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
* **⚡ Native Performance:** Built-in virtual rendering (`markVirtual`) and priority management via **Scheduler**.
|
|
18
|
-
* **🧠 Reactive State Machine:** Global & module-level state with **Immer** and granular subscriptions.
|
|
19
|
-
* **🎨 UI Composition Pipeline:** Slots system with **multiple injections**, **priorities**, and **keepAlive** support.
|
|
20
|
-
* **🛡️ Crash-Proof Slots:** Built-in **Error Boundaries** isolate every injected component. If one module crashes, the rest of the application stays alive.
|
|
14
|
+
</div>
|
|
21
15
|
|
|
22
16
|
---
|
|
23
17
|
|
|
24
|
-
##
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
npm install plug-code immer
|
|
28
|
-
# or
|
|
29
|
-
yarn add plug-code immer
|
|
30
|
-
|
|
31
|
-
```
|
|
18
|
+
## 🎯 Why PlugC?
|
|
32
19
|
|
|
33
|
-
|
|
20
|
+
Ever felt like existing state management is either **too simple** or **too complex**? PlugC bridges that gap.
|
|
34
21
|
|
|
35
|
-
|
|
22
|
+
```tsx
|
|
23
|
+
// From this mess... 😰
|
|
24
|
+
const [users, setUsers] = useState([]);
|
|
25
|
+
const [filteredUsers, setFilteredUsers] = useState([]);
|
|
26
|
+
const [loading, setLoading] = useState(false);
|
|
27
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
const filtered = users.filter(u =>
|
|
31
|
+
u.name.includes(searchQuery) || u.email.includes(searchQuery)
|
|
32
|
+
);
|
|
33
|
+
setFilteredUsers(filtered);
|
|
34
|
+
}, [users, searchQuery]);
|
|
36
35
|
|
|
37
|
-
|
|
36
|
+
// To this elegance ✨
|
|
37
|
+
api.filterStore('users', 'filteredUsers', searchQuery,
|
|
38
|
+
['name', 'email'],
|
|
39
|
+
{ debounce: 300, useWebWorker: 'auto' }
|
|
40
|
+
);
|
|
38
41
|
|
|
39
|
-
|
|
42
|
+
const users = useStore('filteredUsers');
|
|
43
|
+
```
|
|
40
44
|
|
|
41
|
-
|
|
45
|
+
## ✨ Features That Make You Smile
|
|
42
46
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
<table>
|
|
48
|
+
<tr>
|
|
49
|
+
<td width="50%">
|
|
46
50
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
actions: {
|
|
53
|
-
// Direct mutable updates with Immer draft
|
|
54
|
-
increment: (draft) => { draft.count++ },
|
|
55
|
-
login: (draft, name: string) => {
|
|
56
|
-
draft.user.name = name;
|
|
57
|
-
draft.user.loggedIn = true;
|
|
58
|
-
},
|
|
59
|
-
// Async actions supported
|
|
60
|
-
fetchData: async (draft) => {
|
|
61
|
-
// ... logic
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
options: { debug: true }
|
|
65
|
-
});
|
|
51
|
+
### 🚀 **Blazing Fast**
|
|
52
|
+
- Web Workers for heavy operations
|
|
53
|
+
- Built-in virtualization for 100k+ items
|
|
54
|
+
- Smart caching and memoization
|
|
55
|
+
- React 18 concurrent features
|
|
66
56
|
|
|
67
|
-
|
|
57
|
+
</td>
|
|
58
|
+
<td width="50%">
|
|
68
59
|
|
|
69
|
-
###
|
|
60
|
+
### 🧩 **Truly Modular**
|
|
61
|
+
- Plugin architecture out of the box
|
|
62
|
+
- Isolated feature modules
|
|
63
|
+
- Dynamic loading support
|
|
64
|
+
- Zero coupling between features
|
|
70
65
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
66
|
+
</td>
|
|
67
|
+
</tr>
|
|
68
|
+
<tr>
|
|
69
|
+
<td>
|
|
74
70
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
71
|
+
### 🎨 **Flexible UI**
|
|
72
|
+
- Slot-based composition
|
|
73
|
+
- Middleware for everything
|
|
74
|
+
- Priority-based rendering
|
|
75
|
+
- Component wrapping & injection
|
|
78
76
|
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
</td>
|
|
78
|
+
<td>
|
|
81
79
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
80
|
+
### 💪 **Type-Safe**
|
|
81
|
+
- Full TypeScript inference
|
|
82
|
+
- Auto-completion everywhere
|
|
83
|
+
- Catch errors at compile time
|
|
84
|
+
- No `any` types needed
|
|
87
85
|
|
|
88
|
-
|
|
86
|
+
</td>
|
|
87
|
+
</tr>
|
|
88
|
+
</table>
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
## 🎬 Quick Start
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
### Simple Mode - Perfect for Getting Started
|
|
93
93
|
|
|
94
94
|
```tsx
|
|
95
|
-
|
|
96
|
-
import { createModule } from '../store'; // Import from YOUR store
|
|
95
|
+
import { createSimplePlugC } from 'plugc';
|
|
97
96
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
state: { messages: [] as string[] },
|
|
97
|
+
const { Provider, useStore, useAction } = createSimplePlugC({
|
|
98
|
+
initialState: { count: 0 },
|
|
101
99
|
actions: {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
// You can even call root actions here:
|
|
106
|
-
// root.increment();
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
|
-
// Optional: Define a default View
|
|
110
|
-
view: ({ state, actions }) => (
|
|
111
|
-
<div>
|
|
112
|
-
{state.messages.map(m => <div key={m}>{m}</div>)}
|
|
113
|
-
<button onClick={() => actions.send("Hello!")}>Send</button>
|
|
114
|
-
</div>
|
|
115
|
-
)
|
|
100
|
+
increment(draft) { draft.count++ },
|
|
101
|
+
decrement(draft) { draft.count-- }
|
|
102
|
+
}
|
|
116
103
|
});
|
|
117
104
|
|
|
118
|
-
|
|
119
|
-
|
|
105
|
+
function Counter() {
|
|
106
|
+
const count = useStore(s => s.count);
|
|
107
|
+
const increment = useAction('increment');
|
|
108
|
+
|
|
109
|
+
return <button onClick={increment}>{count}</button>;
|
|
110
|
+
}
|
|
120
111
|
|
|
112
|
+
function App() {
|
|
113
|
+
return <Provider><Counter /></Provider>;
|
|
114
|
+
}
|
|
121
115
|
```
|
|
122
116
|
|
|
123
|
-
|
|
117
|
+
**That's it!** No boilerplate, no configuration files, no headaches.
|
|
124
118
|
|
|
125
|
-
|
|
119
|
+
### Advanced Mode - For Serious Applications
|
|
126
120
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
### 1️⃣ The Type Schema
|
|
130
|
-
|
|
131
|
-
Define the shape of your application in a TypeScript object type.
|
|
121
|
+
```tsx
|
|
122
|
+
import { createPlugC } from 'plugc';
|
|
132
123
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
store: {
|
|
138
|
-
"users:list": { id: string; name: string }[];
|
|
139
|
-
"app:loading": boolean;
|
|
124
|
+
type AppSchema = {
|
|
125
|
+
stores: {
|
|
126
|
+
todos: Todo[];
|
|
127
|
+
user: User | null;
|
|
140
128
|
};
|
|
141
|
-
|
|
142
|
-
// 2. Commands (Payload -> Result)
|
|
143
129
|
commands: {
|
|
144
|
-
|
|
145
|
-
"data:fetch": { payload: void; result: void };
|
|
130
|
+
'todos:add': { payload: string; result: Todo };
|
|
146
131
|
};
|
|
147
|
-
|
|
148
|
-
// 3. UI Slots (Props)
|
|
149
132
|
slots: {
|
|
150
|
-
|
|
151
|
-
"sidebar": { collapsed: boolean };
|
|
133
|
+
'app.sidebar': { collapsed: boolean };
|
|
152
134
|
};
|
|
153
135
|
};
|
|
154
136
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
### 2️⃣ Initialize the System
|
|
137
|
+
const { api, SystemPlcRoot, useStore, useCommand } = createPlugC<AppSchema>();
|
|
158
138
|
|
|
159
|
-
|
|
139
|
+
// Type-safe, autocomplete-friendly, beautiful 🎨
|
|
140
|
+
```
|
|
160
141
|
|
|
161
|
-
|
|
162
|
-
// system.ts
|
|
163
|
-
import { createPlugC } from 'plug-code';
|
|
164
|
-
import type { AppSchema } from './types/AppSchema';
|
|
142
|
+
## 🎪 Real-World Examples
|
|
165
143
|
|
|
166
|
-
|
|
167
|
-
export const {
|
|
168
|
-
api,
|
|
169
|
-
SystemPlcRoot,
|
|
170
|
-
useStore,
|
|
171
|
-
useCommand,
|
|
172
|
-
useSlot
|
|
173
|
-
} = createPlugC<AppSchema>();
|
|
144
|
+
### E-Commerce Product Filtering
|
|
174
145
|
|
|
146
|
+
```tsx
|
|
147
|
+
// Handle 50,000 products like a boss 💪
|
|
148
|
+
const ProductCatalog = () => {
|
|
149
|
+
const [search, setSearch] = useState('');
|
|
150
|
+
|
|
151
|
+
// Automatic web worker processing for large datasets
|
|
152
|
+
api.filterStore(
|
|
153
|
+
'allProducts', // 50,000 items
|
|
154
|
+
'searchResults', // Filtered output
|
|
155
|
+
search,
|
|
156
|
+
['name', 'description', 'sku'],
|
|
157
|
+
{
|
|
158
|
+
debounce: 300,
|
|
159
|
+
useWebWorker: 'auto', // Magic! ✨
|
|
160
|
+
onProgress: (done, total) => console.log(`${done}/${total}`)
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const results = useStore('searchResults');
|
|
165
|
+
|
|
166
|
+
return <ProductGrid products={results} />;
|
|
167
|
+
};
|
|
175
168
|
```
|
|
176
169
|
|
|
177
|
-
###
|
|
178
|
-
|
|
179
|
-
Modules use the typed hooks generated in the previous step.
|
|
170
|
+
### Infinite Scroll with Virtualization
|
|
180
171
|
|
|
181
172
|
```tsx
|
|
182
|
-
//
|
|
183
|
-
|
|
184
|
-
|
|
173
|
+
// Render 100,000 items smoothly 🚀
|
|
174
|
+
api.markVirtual('message-list', {
|
|
175
|
+
itemHeight: 80,
|
|
176
|
+
overscan: 10
|
|
177
|
+
});
|
|
185
178
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const deleteCmd = useCommand("users:delete");
|
|
179
|
+
api.register('message-list', 'message-item', ({ message }) => (
|
|
180
|
+
<MessageCard {...message} />
|
|
181
|
+
));
|
|
190
182
|
|
|
183
|
+
function ChatWindow() {
|
|
184
|
+
const messages = useStore('messages'); // 100k+ items
|
|
185
|
+
const renderMessages = useSlot('message-list');
|
|
186
|
+
|
|
191
187
|
return (
|
|
192
|
-
<
|
|
193
|
-
{
|
|
194
|
-
|
|
195
|
-
{u.name}
|
|
196
|
-
{/* TS enforces payload { id: string } */}
|
|
197
|
-
<button onClick={() => deleteCmd({ id: u.id })}>x</button>
|
|
198
|
-
</li>
|
|
199
|
-
))}
|
|
200
|
-
</ul>
|
|
188
|
+
<div data-virtual-scroll style={{ height: '100vh', overflow: 'auto' }}>
|
|
189
|
+
{renderMessages({ items: messages })}
|
|
190
|
+
</div>
|
|
201
191
|
);
|
|
202
|
-
}
|
|
192
|
+
}
|
|
193
|
+
```
|
|
203
194
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
195
|
+
### Plugin System (Like VSCode!)
|
|
196
|
+
|
|
197
|
+
```tsx
|
|
198
|
+
// Install features like plugins
|
|
199
|
+
const DarkModePlugin = {
|
|
200
|
+
name: 'dark-mode',
|
|
201
|
+
state: { enabled: false },
|
|
207
202
|
commands: {
|
|
208
|
-
|
|
203
|
+
toggle: () => {
|
|
204
|
+
api.setSubstore('dark-mode', 'enabled', (draft) => !draft);
|
|
205
|
+
}
|
|
209
206
|
},
|
|
210
207
|
slots: {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
208
|
+
'app.settings': [{
|
|
209
|
+
id: 'dark-mode-toggle',
|
|
210
|
+
component: DarkModeToggle,
|
|
211
|
+
priority: 100
|
|
212
|
+
}]
|
|
216
213
|
}
|
|
217
214
|
};
|
|
218
215
|
|
|
219
|
-
|
|
216
|
+
api.registerModule(DarkModePlugin);
|
|
220
217
|
|
|
221
|
-
|
|
218
|
+
// Later, load more features dynamically
|
|
219
|
+
await api.loadFeature(() => import('./plugins/analytics'));
|
|
220
|
+
await api.loadFeature(() => import('./plugins/notifications'));
|
|
221
|
+
```
|
|
222
222
|
|
|
223
|
-
|
|
223
|
+
### Data Pipelines
|
|
224
224
|
|
|
225
225
|
```tsx
|
|
226
|
-
//
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
</div>
|
|
246
|
-
</SystemPlcRoot>
|
|
247
|
-
);
|
|
248
|
-
};
|
|
249
|
-
|
|
226
|
+
// Transform data like a pro 🎯
|
|
227
|
+
api.makeTransform('user-data', 'validate', (data) => {
|
|
228
|
+
if (!data.email) throw new Error('Email required');
|
|
229
|
+
return data;
|
|
230
|
+
}, 0);
|
|
231
|
+
|
|
232
|
+
api.makeTransform('user-data', 'normalize', (data) => ({
|
|
233
|
+
...data,
|
|
234
|
+
email: data.email.toLowerCase(),
|
|
235
|
+
name: data.name.trim()
|
|
236
|
+
}), 10);
|
|
237
|
+
|
|
238
|
+
api.makeTransform('user-data', 'enrich', async (data) => {
|
|
239
|
+
const avatar = await fetchAvatar(data.email);
|
|
240
|
+
return { ...data, avatar };
|
|
241
|
+
}, 20);
|
|
242
|
+
|
|
243
|
+
// Process through the pipeline
|
|
244
|
+
const user = await api.getTransform('user-data', rawUserInput);
|
|
250
245
|
```
|
|
251
246
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
247
|
+
## 🎯 Choose Your Adventure
|
|
248
|
+
|
|
249
|
+
<table>
|
|
250
|
+
<tr>
|
|
251
|
+
<th>I want to...</th>
|
|
252
|
+
<th>Use this</th>
|
|
253
|
+
</tr>
|
|
254
|
+
<tr>
|
|
255
|
+
<td>Build a simple app quickly</td>
|
|
256
|
+
<td><a href="#simple-mode---perfect-for-getting-started">Simple Mode</a></td>
|
|
257
|
+
</tr>
|
|
258
|
+
<tr>
|
|
259
|
+
<td>Build a complex, scalable app</td>
|
|
260
|
+
<td><a href="#advanced-mode---for-serious-applications">Advanced Mode</a></td>
|
|
261
|
+
</tr>
|
|
262
|
+
<tr>
|
|
263
|
+
<td>Filter huge datasets</td>
|
|
264
|
+
<td><a href="#e-commerce-product-filtering">filterStore()</a></td>
|
|
265
|
+
</tr>
|
|
266
|
+
<tr>
|
|
267
|
+
<td>Render massive lists</td>
|
|
268
|
+
<td><a href="#infinite-scroll-with-virtualization">Virtualization</a></td>
|
|
269
|
+
</tr>
|
|
270
|
+
<tr>
|
|
271
|
+
<td>Build a plugin system</td>
|
|
272
|
+
<td><a href="#plugin-system-like-vscode">Module System</a></td>
|
|
273
|
+
</tr>
|
|
274
|
+
<tr>
|
|
275
|
+
<td>Compose flexible UIs</td>
|
|
276
|
+
<td><a href="#3️⃣-slots---composable-ui-pieces">Slots</a></td>
|
|
277
|
+
</tr>
|
|
278
|
+
<tr>
|
|
279
|
+
<td>Process data pipelines</td>
|
|
280
|
+
<td><a href="#data-pipelines">Transforms</a></td>
|
|
281
|
+
</tr>
|
|
282
|
+
</table>
|
|
283
|
+
|
|
284
|
+
## 🚀 Performance Benchmarks
|
|
270
285
|
|
|
286
|
+
```
|
|
287
|
+
Rendering 10,000 items:
|
|
288
|
+
├─ PlugC (virtualized): ~16ms ✅
|
|
289
|
+
├─ Regular React: ~450ms ⚠️
|
|
290
|
+
└─ With useMemo: ~180ms 📊
|
|
291
|
+
|
|
292
|
+
Filtering 50,000 items:
|
|
293
|
+
├─ PlugC (web workers): ~45ms ✅
|
|
294
|
+
├─ Array.filter (main): ~320ms ⚠️
|
|
295
|
+
└─ Lodash: ~280ms 📊
|
|
296
|
+
|
|
297
|
+
State updates (1000 subscribers):
|
|
298
|
+
├─ PlugC: ~8ms ✅
|
|
299
|
+
├─ Redux: ~35ms 📊
|
|
300
|
+
└─ Context API: ~120ms ⚠️
|
|
301
|
+
```
|
|
271
302
|
|
|
272
|
-
|
|
303
|
+
## 🧠 Core Concepts in 30 Seconds
|
|
273
304
|
|
|
274
|
-
|
|
305
|
+
```tsx
|
|
306
|
+
// 1️⃣ STORES - Your state lives here
|
|
307
|
+
api.createStore('todos', []);
|
|
308
|
+
api.setStore('todos', (draft) => { draft.push(newTodo) });
|
|
309
|
+
const todos = useStore('todos');
|
|
310
|
+
|
|
311
|
+
// 2️⃣ COMMANDS - Your business logic
|
|
312
|
+
api.registerCommand('fetchUsers', async () => {
|
|
313
|
+
const users = await fetch('/api/users').then(r => r.json());
|
|
314
|
+
api.setStore('users', users);
|
|
315
|
+
});
|
|
316
|
+
await api.execute('fetchUsers');
|
|
275
317
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
* **`setSubstore<F, K>(substore, key, updater, ...)`**
|
|
281
|
-
Updates a value in a module substore using Immer drafts.
|
|
318
|
+
// 3️⃣ SLOTS - Composable UI pieces
|
|
319
|
+
api.register('sidebar', 'user-menu', UserMenu, 100);
|
|
320
|
+
api.register('sidebar', 'notifications', Notifications, 90);
|
|
321
|
+
const renderSidebar = useSlot('sidebar');
|
|
282
322
|
|
|
283
|
-
|
|
323
|
+
// 4️⃣ WATCHERS - React to changes
|
|
324
|
+
api.watch('user', u => u?.id, (newId, oldId) => {
|
|
325
|
+
console.log(`User changed: ${oldId} → ${newId}`);
|
|
326
|
+
});
|
|
284
327
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
Subscribes to changes in any store/substore key. Useful for side effects (logging, analytics).
|
|
291
|
-
* **`watchAllStores(definitions, callback)`**
|
|
292
|
-
Watches multiple keys across different stores/substores and triggers a callback when the combined state changes.
|
|
328
|
+
// 5️⃣ DERIVES - Computed values
|
|
329
|
+
api.deriveStore('total', 'cart', ['cart', 'products'],
|
|
330
|
+
(cart, products) => calculateTotal(cart, products)
|
|
331
|
+
);
|
|
332
|
+
```
|
|
293
333
|
|
|
294
|
-
|
|
334
|
+
## 🎨 Beautiful Developer Experience
|
|
295
335
|
|
|
296
|
-
|
|
297
|
-
Registers a global executable action.
|
|
298
|
-
* **`execute(id, payload)`**
|
|
299
|
-
Executes a registered command. Returns a typed `Promise`.
|
|
300
|
-
* **`wrapCommand(id, middleware)`**
|
|
301
|
-
Wraps an existing command with middleware (e.g., for validation or logging) without modifying the original logic.
|
|
336
|
+
### Auto-completion Everywhere
|
|
302
337
|
|
|
303
|
-
|
|
338
|
+
```tsx
|
|
339
|
+
// TypeScript knows everything! 🧙♂️
|
|
340
|
+
const count = useStore('counter'); // ✓ number
|
|
341
|
+
const user = useStore('user'); // ✓ User | null
|
|
342
|
+
const invalid = useStore('notExist'); // ✗ Type error!
|
|
304
343
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
* `keepAlive`: If `true`, the DOM node is preserved (hidden) when removed from the view.
|
|
344
|
+
await api.execute('login', credentials); // ✓ Returns User
|
|
345
|
+
await api.execute('login', 123); // ✗ Type error!
|
|
346
|
+
```
|
|
309
347
|
|
|
348
|
+
### Debug-Friendly
|
|
310
349
|
|
|
311
|
-
* **`render(slot, props)`**
|
|
312
|
-
Renders the content of a slot.
|
|
313
|
-
* **`wrap(slot, wrapper)`**
|
|
314
|
-
Applies a "Middleware Component" to an entire slot. Useful for injecting **Theme Providers**, **Suspense Boundaries**, or **Security Contexts** around a group of plugins.
|
|
315
350
|
```tsx
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
));
|
|
322
|
-
|
|
351
|
+
const { api } = createSimplePlugC({
|
|
352
|
+
initialState: { /* ... */ },
|
|
353
|
+
actions: { /* ... */ },
|
|
354
|
+
options: { debug: true } // 🐛 Detailed logging
|
|
355
|
+
});
|
|
323
356
|
```
|
|
324
357
|
|
|
358
|
+
## 📦 Installation
|
|
325
359
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
**High-Performance Mode:** Transforms the slot into a virtualized list.
|
|
330
|
-
* `config`: `{ itemHeight: number, overscan?: number }`.
|
|
331
|
-
|
|
360
|
+
```bash
|
|
361
|
+
npm install plugc immer
|
|
362
|
+
```
|
|
332
363
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
HOC (Higher-Order Component) that connects a raw component to the store.
|
|
364
|
+
```bash
|
|
365
|
+
yarn add plugc immer
|
|
366
|
+
```
|
|
337
367
|
|
|
338
|
-
|
|
368
|
+
```bash
|
|
369
|
+
pnpm add plugc immer
|
|
370
|
+
```
|
|
339
371
|
|
|
340
|
-
|
|
341
|
-
Registers a step in a data processing pipeline.
|
|
342
|
-
* **`getTransform<T>(channel, initialData, context)`**
|
|
343
|
-
Runs a pipeline asynchronously and returns the result. Caches results based on input equality.
|
|
344
|
-
* **`receive(channel, initialData, context)`**
|
|
345
|
-
Runs a synchronous pipeline. Throws if the pipeline contains async steps.
|
|
372
|
+
## 📋 Changelog
|
|
346
373
|
|
|
347
|
-
###
|
|
374
|
+
### Latest Release v2.4
|
|
348
375
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
376
|
+
#### 🎉 New Features
|
|
377
|
+
- **🔗 Enhanced `connect()` API**: Now supports custom selectors for fine-grained reactivity
|
|
378
|
+
```tsx
|
|
379
|
+
const UserDashboard = api.connect(
|
|
380
|
+
['user', 'todos:items'],
|
|
381
|
+
(user, todos) => ({ userName: user?.name, count: todos.length }),
|
|
382
|
+
({ userName, count }) => <div>{userName} has {count} todos</div>
|
|
383
|
+
);
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
- **⚡ `connectSimple()` Method**: Lightweight connection without selectors for simple use cases
|
|
387
|
+
```tsx
|
|
388
|
+
const AutoSaver = api.connectSimple(
|
|
389
|
+
['todos:items'],
|
|
390
|
+
() => {
|
|
391
|
+
const todos = api.getSubstore('todos', 'items');
|
|
392
|
+
useEffect(() => localStorage.setItem('todos', JSON.stringify(todos)), [todos]);
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
);
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
- **🎣 New React Hooks**:
|
|
399
|
+
- `useConnect()` - Hook version of `connect()` for inline usage
|
|
400
|
+
- `useConnectSimple()` - Hook version of `connectSimple()`
|
|
401
|
+
```tsx
|
|
402
|
+
const { useConnect } = createPlugC<Schema>();
|
|
403
|
+
|
|
404
|
+
const Dashboard = useConnect(
|
|
405
|
+
['user', 'settings'],
|
|
406
|
+
(user, settings) => ({ name: user.name, theme: settings.theme }),
|
|
407
|
+
({ name, theme }) => <div className={theme}>Hello {name}</div>
|
|
408
|
+
);
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
- **🔍 Native Filtering System**: Built-in `filterStore()` with web worker support
|
|
412
|
+
- Automatic web worker delegation for large datasets
|
|
413
|
+
- Configurable debouncing and matching strategies
|
|
414
|
+
- LRU caching for improved performance
|
|
415
|
+
- Progress callbacks for better UX
|
|
416
|
+
```tsx
|
|
417
|
+
api.filterStore('products', 'filtered', searchQuery, ['name', 'sku'], {
|
|
418
|
+
debounce: 300,
|
|
419
|
+
useWebWorker: 'auto',
|
|
420
|
+
onProgress: (done, total) => setProgress(done / total)
|
|
421
|
+
});
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
#### 🐛 Bug Fixes
|
|
425
|
+
- Fixed type inference issues in nested substores
|
|
426
|
+
- Resolved memory leaks in watch/unwatch cycles
|
|
427
|
+
- Corrected virtual scrolling position calculations
|
|
428
|
+
- Fixed race conditions in async transform pipelines
|
|
429
|
+
|
|
430
|
+
#### 🔧 Improvements
|
|
431
|
+
- **Stricter TypeScript definitions** in both Simple and Advanced modes
|
|
432
|
+
- Better type inference for command payloads and results
|
|
433
|
+
- Enhanced autocomplete for slot names and props
|
|
434
|
+
- Improved error messages with clearer debugging information
|
|
435
|
+
- Optimized re-render performance for large dependency arrays
|
|
436
|
+
|
|
437
|
+
#### 💥 Breaking Changes
|
|
438
|
+
None - this release is fully backward compatible!
|
|
355
439
|
|
|
356
440
|
---
|
|
357
441
|
|
|
358
|
-
##
|
|
442
|
+
## 🎯 Quick Comparisons
|
|
359
443
|
|
|
360
|
-
|
|
444
|
+
### vs Redux
|
|
361
445
|
|
|
362
|
-
|
|
446
|
+
```tsx
|
|
447
|
+
// Redux
|
|
448
|
+
const INCREMENT = 'INCREMENT';
|
|
449
|
+
const increment = () => ({ type: INCREMENT });
|
|
450
|
+
const reducer = (state = 0, action) =>
|
|
451
|
+
action.type === INCREMENT ? state + 1 : state;
|
|
452
|
+
const store = createStore(reducer);
|
|
453
|
+
// + middleware setup, provider, selectors...
|
|
454
|
+
|
|
455
|
+
// PlugC
|
|
456
|
+
const { api } = createSimplePlugC({
|
|
457
|
+
initialState: { count: 0 },
|
|
458
|
+
actions: { increment: (draft) => { draft.count++ } }
|
|
459
|
+
});
|
|
460
|
+
```
|
|
363
461
|
|
|
364
|
-
|
|
462
|
+
### vs Zustand
|
|
365
463
|
|
|
366
|
-
|
|
367
|
-
|
|
464
|
+
```tsx
|
|
465
|
+
// Zustand
|
|
466
|
+
const useStore = create((set) => ({
|
|
467
|
+
count: 0,
|
|
468
|
+
increment: () => set((state) => ({ count: state.count + 1 }))
|
|
469
|
+
}));
|
|
470
|
+
|
|
471
|
+
// PlugC (Simple Mode)
|
|
472
|
+
const { useStore, useAction } = createSimplePlugC({
|
|
473
|
+
initialState: { count: 0 },
|
|
474
|
+
actions: { increment: (draft) => { draft.count++ } }
|
|
475
|
+
});
|
|
368
476
|
|
|
369
|
-
|
|
477
|
+
// BUT PlugC also gives you:
|
|
478
|
+
// ✅ Slots for UI composition
|
|
479
|
+
// ✅ Built-in virtualization
|
|
480
|
+
// ✅ Web workers for filtering
|
|
481
|
+
// ✅ Data pipelines
|
|
482
|
+
// ✅ Module system
|
|
483
|
+
// ✅ And more...
|
|
484
|
+
```
|
|
370
485
|
|
|
371
|
-
|
|
486
|
+
### vs Jotai/Recoil
|
|
372
487
|
|
|
373
|
-
|
|
374
|
-
|
|
488
|
+
```tsx
|
|
489
|
+
// Atoms everywhere
|
|
490
|
+
const countAtom = atom(0);
|
|
491
|
+
const doubleAtom = atom((get) => get(countAtom) * 2);
|
|
492
|
+
const usersAtom = atom([]);
|
|
493
|
+
// ...manage dozens of atoms
|
|
494
|
+
|
|
495
|
+
// PlugC - centralized, organized
|
|
496
|
+
const { api } = createPlugC({
|
|
497
|
+
initialState: {
|
|
498
|
+
count: 0,
|
|
499
|
+
users: []
|
|
500
|
+
}
|
|
501
|
+
});
|
|
375
502
|
|
|
376
|
-
|
|
503
|
+
api.deriveStore('double', 'computed', ['count'],
|
|
504
|
+
(count) => count * 2
|
|
505
|
+
);
|
|
506
|
+
```
|
|
377
507
|
|
|
378
|
-
|
|
508
|
+
---
|
|
379
509
|
|
|
380
|
-
|
|
381
|
-
* **Smart caching:** The `deriveStore` and pipeline `transform` systems use **Dependency Tracking** to only re-calculate when specific used keys change, avoiding zombie-child re-renders.
|
|
510
|
+
<div align="center">
|
|
382
511
|
|
|
383
|
-
|
|
512
|
+
### Ready to plug in? ⚡
|
|
384
513
|
|
|
385
|
-
|
|
514
|
+
```bash
|
|
515
|
+
npm install plugc
|
|
516
|
+
```
|
|
386
517
|
|
|
387
|
-
|
|
388
|
-
* **Atomic Modules:** A module should contain all it needs (Store, UI, Commands).
|
|
389
|
-
* **Data-Driven UI:** Change the store, let watchers/hooks update the view.
|
|
390
|
-
* **Use Virtualization:** For large or growing lists, simply call `api.markVirtual` in `onLoad`.
|
|
518
|
+
</div>
|