redux-cluster-ws 2.0.0 ā 2.0.2
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/package.json +8 -3
- package/FUNDING.yml +0 -7
- package/eslint.config.js +0 -143
- package/examples/browser-example.cjs +0 -350
- package/examples/browser.html +0 -255
- package/examples/client.cjs +0 -155
- package/examples/cross-library-browser.html +0 -655
- package/examples/cross-library-client.cjs +0 -190
- package/examples/cross-library-server.cjs +0 -213
- package/examples/server.cjs +0 -96
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Cross-library WebSocket Client
|
|
5
|
-
*
|
|
6
|
-
* This client connects to the cross-library server via WebSocket
|
|
7
|
-
* while the server also handles cluster workers via IPC/TCP
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const { createStore } = require("../dist/cjs/index.js");
|
|
11
|
-
|
|
12
|
-
// Same reducer as server
|
|
13
|
-
function todoReducer(
|
|
14
|
-
state = { todos: [], users: [], stats: { total: 0 } },
|
|
15
|
-
action
|
|
16
|
-
) {
|
|
17
|
-
switch (action.type) {
|
|
18
|
-
case "ADD_TODO":
|
|
19
|
-
return {
|
|
20
|
-
...state,
|
|
21
|
-
todos: [
|
|
22
|
-
...state.todos,
|
|
23
|
-
{
|
|
24
|
-
id: Date.now(),
|
|
25
|
-
text: action.payload.text,
|
|
26
|
-
completed: false,
|
|
27
|
-
createdBy: action.payload.user || "anonymous",
|
|
28
|
-
timestamp: new Date().toISOString(),
|
|
29
|
-
},
|
|
30
|
-
],
|
|
31
|
-
stats: { total: state.stats.total + 1 },
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
case "TOGGLE_TODO":
|
|
35
|
-
return {
|
|
36
|
-
...state,
|
|
37
|
-
todos: state.todos.map((todo) =>
|
|
38
|
-
todo.id === action.payload.id
|
|
39
|
-
? { ...todo, completed: !todo.completed }
|
|
40
|
-
: todo
|
|
41
|
-
),
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
case "ADD_USER":
|
|
45
|
-
const userExists = state.users.find(
|
|
46
|
-
(u) => u.name === action.payload.name
|
|
47
|
-
);
|
|
48
|
-
if (userExists) return state;
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
...state,
|
|
52
|
-
users: [
|
|
53
|
-
...state.users,
|
|
54
|
-
{
|
|
55
|
-
id: Date.now(),
|
|
56
|
-
name: action.payload.name,
|
|
57
|
-
joinedAt: new Date().toISOString(),
|
|
58
|
-
type: action.payload.type || "web",
|
|
59
|
-
},
|
|
60
|
-
],
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
case "REMOVE_USER":
|
|
64
|
-
return {
|
|
65
|
-
...state,
|
|
66
|
-
users: state.users.filter((u) => u.id !== action.payload.id),
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
default:
|
|
70
|
-
return state;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
console.log("š Starting WebSocket client for cross-library demo...");
|
|
75
|
-
|
|
76
|
-
// Create store
|
|
77
|
-
const store = createStore(todoReducer);
|
|
78
|
-
|
|
79
|
-
// Connect to WebSocket server
|
|
80
|
-
store.createWSClient({
|
|
81
|
-
host: "ws://127.0.0.1",
|
|
82
|
-
port: 8890,
|
|
83
|
-
login: "web-client",
|
|
84
|
-
password: "web123",
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
const clientName = `WebSocket Client ${process.pid}`;
|
|
88
|
-
|
|
89
|
-
// Wait for connection and add user
|
|
90
|
-
setTimeout(() => {
|
|
91
|
-
if (store.connected) {
|
|
92
|
-
store.dispatch({
|
|
93
|
-
type: "ADD_USER",
|
|
94
|
-
payload: { name: clientName, type: "web" },
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
console.log(`ā
Connected as "${clientName}"`);
|
|
98
|
-
}
|
|
99
|
-
}, 1000);
|
|
100
|
-
|
|
101
|
-
// Subscribe to state changes
|
|
102
|
-
store.subscribe(() => {
|
|
103
|
-
const state = store.getState();
|
|
104
|
-
console.log(`\\nš Current state:`);
|
|
105
|
-
console.log(` Todos: ${state.todos.length}`);
|
|
106
|
-
console.log(` Users: ${state.users.length}`);
|
|
107
|
-
console.log(` Total created: ${state.stats.total}`);
|
|
108
|
-
|
|
109
|
-
if (state.todos.length > 0) {
|
|
110
|
-
console.log(`\\nš Recent todos:`);
|
|
111
|
-
state.todos.slice(-3).forEach((todo) => {
|
|
112
|
-
const status = todo.completed ? "ā
" : "ā³";
|
|
113
|
-
console.log(` ${status} "${todo.text}" by ${todo.createdBy}`);
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (state.users.length > 0) {
|
|
118
|
-
console.log(`\\nš„ Connected users:`);
|
|
119
|
-
state.users.forEach((user) => {
|
|
120
|
-
const icon = user.type === "cluster" ? "š§" : "š";
|
|
121
|
-
console.log(` ${icon} ${user.name} (${user.type})`);
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
// Interactive commands
|
|
127
|
-
process.stdin.setEncoding("utf8");
|
|
128
|
-
console.log("\\nš Commands:");
|
|
129
|
-
console.log(' Type "add <text>" to add a todo');
|
|
130
|
-
console.log(' Type "toggle <id>" to toggle a todo');
|
|
131
|
-
console.log(' Type "list" to show current state');
|
|
132
|
-
console.log(' Type "quit" to exit');
|
|
133
|
-
console.log("\\n> ");
|
|
134
|
-
|
|
135
|
-
process.stdin.on("data", (input) => {
|
|
136
|
-
const command = input.trim();
|
|
137
|
-
|
|
138
|
-
if (command.startsWith("add ")) {
|
|
139
|
-
const text = command.substring(4);
|
|
140
|
-
if (text) {
|
|
141
|
-
store.dispatch({
|
|
142
|
-
type: "ADD_TODO",
|
|
143
|
-
payload: {
|
|
144
|
-
text,
|
|
145
|
-
user: clientName,
|
|
146
|
-
},
|
|
147
|
-
});
|
|
148
|
-
console.log(`ā
Added todo: "${text}"`);
|
|
149
|
-
}
|
|
150
|
-
} else if (command.startsWith("toggle ")) {
|
|
151
|
-
const id = parseInt(command.substring(7));
|
|
152
|
-
if (id) {
|
|
153
|
-
store.dispatch({
|
|
154
|
-
type: "TOGGLE_TODO",
|
|
155
|
-
payload: { id },
|
|
156
|
-
});
|
|
157
|
-
console.log(`š Toggled todo ${id}`);
|
|
158
|
-
}
|
|
159
|
-
} else if (command === "list") {
|
|
160
|
-
const state = store.getState();
|
|
161
|
-
console.log("\\nš Current todos:");
|
|
162
|
-
state.todos.forEach((todo) => {
|
|
163
|
-
const status = todo.completed ? "ā
" : "ā³";
|
|
164
|
-
console.log(
|
|
165
|
-
` ${todo.id}: ${status} "${todo.text}" by ${todo.createdBy}`
|
|
166
|
-
);
|
|
167
|
-
});
|
|
168
|
-
} else if (command === "quit") {
|
|
169
|
-
console.log("š Goodbye!");
|
|
170
|
-
process.exit(0);
|
|
171
|
-
} else if (command) {
|
|
172
|
-
console.log(
|
|
173
|
-
'ā Unknown command. Use "add <text>", "toggle <id>", "list", or "quit"'
|
|
174
|
-
);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
process.stdout.write("> ");
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
// Cleanup on exit
|
|
181
|
-
process.on("SIGINT", () => {
|
|
182
|
-
console.log("\\nš Disconnecting...");
|
|
183
|
-
if (store.connected) {
|
|
184
|
-
store.dispatch({
|
|
185
|
-
type: "REMOVE_USER",
|
|
186
|
-
payload: { name: clientName },
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
process.exit(0);
|
|
190
|
-
});
|
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Cross-library Example: Redux-Cluster server + Redux-Cluster-WS client
|
|
5
|
-
*
|
|
6
|
-
* This example demonstrates how redux-cluster (IPC/TCP) can work alongside
|
|
7
|
-
* redux-cluster-ws (WebSocket) to create a hybrid architecture where:
|
|
8
|
-
* - Node.js processes communicate via IPC/TCP (redux-cluster)
|
|
9
|
-
* - Web clients communicate via WebSocket (redux-cluster-ws)
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
const { createStore } = require("redux-cluster");
|
|
13
|
-
const { server } = require("../dist/cjs/index.js");
|
|
14
|
-
const cluster = require("cluster");
|
|
15
|
-
|
|
16
|
-
// Shared reducer
|
|
17
|
-
function todoReducer(
|
|
18
|
-
state = { todos: [], users: [], stats: { total: 0 } },
|
|
19
|
-
action
|
|
20
|
-
) {
|
|
21
|
-
switch (action.type) {
|
|
22
|
-
case "ADD_TODO":
|
|
23
|
-
return {
|
|
24
|
-
...state,
|
|
25
|
-
todos: [
|
|
26
|
-
...state.todos,
|
|
27
|
-
{
|
|
28
|
-
id: Date.now(),
|
|
29
|
-
text: action.payload.text,
|
|
30
|
-
completed: false,
|
|
31
|
-
createdBy: action.payload.user || "anonymous",
|
|
32
|
-
timestamp: new Date().toISOString(),
|
|
33
|
-
},
|
|
34
|
-
],
|
|
35
|
-
stats: { total: state.stats.total + 1 },
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
case "TOGGLE_TODO":
|
|
39
|
-
return {
|
|
40
|
-
...state,
|
|
41
|
-
todos: state.todos.map((todo) =>
|
|
42
|
-
todo.id === action.payload.id
|
|
43
|
-
? { ...todo, completed: !todo.completed }
|
|
44
|
-
: todo
|
|
45
|
-
),
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
case "ADD_USER":
|
|
49
|
-
const userExists = state.users.find(
|
|
50
|
-
(u) => u.name === action.payload.name
|
|
51
|
-
);
|
|
52
|
-
if (userExists) return state;
|
|
53
|
-
|
|
54
|
-
return {
|
|
55
|
-
...state,
|
|
56
|
-
users: [
|
|
57
|
-
...state.users,
|
|
58
|
-
{
|
|
59
|
-
id: Date.now(),
|
|
60
|
-
name: action.payload.name,
|
|
61
|
-
joinedAt: new Date().toISOString(),
|
|
62
|
-
type: action.payload.type || "web", // 'web' or 'cluster'
|
|
63
|
-
},
|
|
64
|
-
],
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
case "REMOVE_USER":
|
|
68
|
-
return {
|
|
69
|
-
...state,
|
|
70
|
-
users: state.users.filter((u) => u.id !== action.payload.id),
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
default:
|
|
74
|
-
return state;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (cluster.isMaster) {
|
|
79
|
-
console.log("š Starting cross-library demo...");
|
|
80
|
-
|
|
81
|
-
// Create main store with redux-cluster
|
|
82
|
-
const store = createStore(todoReducer, {
|
|
83
|
-
mode: "snapshot",
|
|
84
|
-
enableObjectStream: true, // Use protoobject for better performance
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
// Add WebSocket server capability
|
|
88
|
-
server(store);
|
|
89
|
-
|
|
90
|
-
// Start WebSocket server for web clients
|
|
91
|
-
store.createWSServer({
|
|
92
|
-
host: "127.0.0.1",
|
|
93
|
-
port: 8890,
|
|
94
|
-
logins: {
|
|
95
|
-
"web-client": "web123",
|
|
96
|
-
admin: "admin123",
|
|
97
|
-
},
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
console.log("ā
WebSocket server started on port 8890");
|
|
101
|
-
console.log("š§ Web clients can connect to ws://127.0.0.1:8890");
|
|
102
|
-
|
|
103
|
-
// Fork cluster workers for internal processing
|
|
104
|
-
const worker1 = cluster.fork();
|
|
105
|
-
const worker2 = cluster.fork();
|
|
106
|
-
|
|
107
|
-
console.log("ā
Cluster workers started");
|
|
108
|
-
|
|
109
|
-
// Add main process as admin user
|
|
110
|
-
store.dispatch({
|
|
111
|
-
type: "ADD_USER",
|
|
112
|
-
payload: { name: "Main Process", type: "cluster" },
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
// Simulate periodic tasks from main process
|
|
116
|
-
let counter = 0;
|
|
117
|
-
setInterval(() => {
|
|
118
|
-
store.dispatch({
|
|
119
|
-
type: "ADD_TODO",
|
|
120
|
-
payload: {
|
|
121
|
-
text: `Scheduled task ${++counter} from main process`,
|
|
122
|
-
user: "Main Process",
|
|
123
|
-
},
|
|
124
|
-
});
|
|
125
|
-
}, 15000);
|
|
126
|
-
|
|
127
|
-
// Log state changes
|
|
128
|
-
store.subscribe(() => {
|
|
129
|
-
const state = store.getState();
|
|
130
|
-
console.log(
|
|
131
|
-
`š State update: ${state.todos.length} todos, ${state.users.length} users`
|
|
132
|
-
);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
// Handle worker messages
|
|
136
|
-
cluster.on("message", (worker, message) => {
|
|
137
|
-
console.log(`š¬ Message from worker ${worker.process.pid}:`, message);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
// Cleanup on exit
|
|
141
|
-
process.on("SIGINT", () => {
|
|
142
|
-
console.log("\\nš Shutting down cross-library demo...");
|
|
143
|
-
for (const id in cluster.workers) {
|
|
144
|
-
cluster.workers[id].kill();
|
|
145
|
-
}
|
|
146
|
-
process.exit(0);
|
|
147
|
-
});
|
|
148
|
-
} else {
|
|
149
|
-
// Worker process
|
|
150
|
-
const store = createStore(todoReducer, {
|
|
151
|
-
mode: "action",
|
|
152
|
-
enableObjectStream: true,
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
// Connect to master via cluster IPC
|
|
156
|
-
store.createClient({
|
|
157
|
-
host: "master",
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
const workerId = process.pid;
|
|
161
|
-
console.log(`š· Worker ${workerId} started and connected to master`);
|
|
162
|
-
|
|
163
|
-
// Add worker as user
|
|
164
|
-
store.dispatch({
|
|
165
|
-
type: "ADD_USER",
|
|
166
|
-
payload: { name: `Worker ${workerId}`, type: "cluster" },
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
// Worker-specific tasks
|
|
170
|
-
let taskCount = 0;
|
|
171
|
-
setInterval(() => {
|
|
172
|
-
if (Math.random() > 0.7) {
|
|
173
|
-
// 30% chance
|
|
174
|
-
store.dispatch({
|
|
175
|
-
type: "ADD_TODO",
|
|
176
|
-
payload: {
|
|
177
|
-
text: `Background task ${++taskCount} from worker ${workerId}`,
|
|
178
|
-
user: `Worker ${workerId}`,
|
|
179
|
-
},
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
}, 8000);
|
|
183
|
-
|
|
184
|
-
// Send periodic status to master
|
|
185
|
-
setInterval(() => {
|
|
186
|
-
if (process.send) {
|
|
187
|
-
process.send({
|
|
188
|
-
type: "status",
|
|
189
|
-
workerId,
|
|
190
|
-
timestamp: new Date().toISOString(),
|
|
191
|
-
memory: process.memoryUsage(),
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
}, 30000);
|
|
195
|
-
|
|
196
|
-
// Subscribe to state changes
|
|
197
|
-
store.subscribe(() => {
|
|
198
|
-
const state = store.getState();
|
|
199
|
-
console.log(`š· Worker ${workerId} sees: ${state.todos.length} todos`);
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
console.log("\\nš Instructions:");
|
|
204
|
-
console.log(
|
|
205
|
-
"1. This demo shows redux-cluster (IPC) + redux-cluster-ws (WebSocket)"
|
|
206
|
-
);
|
|
207
|
-
console.log("2. Main process and workers communicate via cluster IPC");
|
|
208
|
-
console.log("3. Web clients can connect via WebSocket to the same store");
|
|
209
|
-
console.log("4. Open examples/cross-library-browser.html to see web client");
|
|
210
|
-
console.log(
|
|
211
|
-
"5. Or run examples/cross-library-client.js for Node.js WebSocket client"
|
|
212
|
-
);
|
|
213
|
-
console.log("");
|
package/examples/server.cjs
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Basic Redux-Cluster-WS Server Example
|
|
5
|
-
*
|
|
6
|
-
* This example demonstrates how to create a simple WebSocket server
|
|
7
|
-
* that synchronizes Redux state across multiple clients.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const { createStore } = require("redux-cluster");
|
|
11
|
-
const { server } = require("../dist/cjs/index.js");
|
|
12
|
-
|
|
13
|
-
// Simple counter reducer
|
|
14
|
-
function counterReducer(state = { count: 0, lastUpdate: null }, action) {
|
|
15
|
-
switch (action.type) {
|
|
16
|
-
case "INCREMENT":
|
|
17
|
-
return {
|
|
18
|
-
count: state.count + 1,
|
|
19
|
-
lastUpdate: new Date().toISOString(),
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
case "DECREMENT":
|
|
23
|
-
return {
|
|
24
|
-
count: state.count - 1,
|
|
25
|
-
lastUpdate: new Date().toISOString(),
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
case "RESET":
|
|
29
|
-
return {
|
|
30
|
-
count: 0,
|
|
31
|
-
lastUpdate: new Date().toISOString(),
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
default:
|
|
35
|
-
return state;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
console.log("š Starting Redux-Cluster-WS Server Example...");
|
|
40
|
-
|
|
41
|
-
// Create Redux store
|
|
42
|
-
const store = createStore(counterReducer);
|
|
43
|
-
|
|
44
|
-
// Add WebSocket server capability
|
|
45
|
-
server(store);
|
|
46
|
-
|
|
47
|
-
// Configure synchronization mode
|
|
48
|
-
store.mode = "snapshot"; // Send full state on changes
|
|
49
|
-
|
|
50
|
-
// Subscribe to state changes for logging
|
|
51
|
-
store.subscribe(() => {
|
|
52
|
-
const state = store.getState();
|
|
53
|
-
console.log(
|
|
54
|
-
`š State updated: count=${state.count}, lastUpdate=${state.lastUpdate}`
|
|
55
|
-
);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
// Start WebSocket server
|
|
59
|
-
store.createWSServer({
|
|
60
|
-
host: "0.0.0.0",
|
|
61
|
-
port: 8088,
|
|
62
|
-
logins: {
|
|
63
|
-
admin: "password123",
|
|
64
|
-
user: "secret456",
|
|
65
|
-
demo: "demo",
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
console.log("ā
WebSocket server started on ws://localhost:8088");
|
|
70
|
-
console.log("š Available logins:");
|
|
71
|
-
console.log(" - admin:password123");
|
|
72
|
-
console.log(" - user:secret456");
|
|
73
|
-
console.log(" - demo:demo");
|
|
74
|
-
console.log("");
|
|
75
|
-
console.log("š” Try connecting with the client example or browser!");
|
|
76
|
-
console.log("");
|
|
77
|
-
|
|
78
|
-
// Simulate some activity on the server
|
|
79
|
-
let counter = 0;
|
|
80
|
-
setInterval(() => {
|
|
81
|
-
counter++;
|
|
82
|
-
|
|
83
|
-
if (counter % 3 === 0) {
|
|
84
|
-
store.dispatch({ type: "INCREMENT" });
|
|
85
|
-
console.log("šÆ Server auto-increment");
|
|
86
|
-
}
|
|
87
|
-
}, 5000);
|
|
88
|
-
|
|
89
|
-
// Handle graceful shutdown
|
|
90
|
-
process.on("SIGINT", () => {
|
|
91
|
-
console.log("\\nš Shutting down server...");
|
|
92
|
-
process.exit(0);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
console.log("š Server will auto-increment every 5 seconds");
|
|
96
|
-
console.log("Press Ctrl+C to stop");
|