redux-cluster 1.10.0 → 2.0.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.
Files changed (57) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +345 -471
  3. package/dist/cjs/core/backup.d.ts +10 -0
  4. package/dist/cjs/core/backup.d.ts.map +1 -0
  5. package/dist/cjs/core/backup.js +166 -0
  6. package/dist/cjs/core/redux-cluster.d.ts +47 -0
  7. package/dist/cjs/core/redux-cluster.d.ts.map +1 -0
  8. package/dist/cjs/core/redux-cluster.js +367 -0
  9. package/dist/cjs/index.d.ts +22 -0
  10. package/dist/cjs/index.d.ts.map +1 -0
  11. package/dist/cjs/index.js +43 -0
  12. package/dist/cjs/network/client.d.ts +23 -0
  13. package/dist/cjs/network/client.d.ts.map +1 -0
  14. package/dist/cjs/network/client.js +251 -0
  15. package/dist/cjs/network/server.d.ts +39 -0
  16. package/dist/cjs/network/server.d.ts.map +1 -0
  17. package/dist/cjs/network/server.js +439 -0
  18. package/dist/cjs/package.json +1 -0
  19. package/dist/cjs/types/index.d.ts +125 -0
  20. package/dist/cjs/types/index.d.ts.map +1 -0
  21. package/dist/cjs/types/index.js +20 -0
  22. package/dist/cjs/utils/crypto.d.ts +22 -0
  23. package/dist/cjs/utils/crypto.d.ts.map +1 -0
  24. package/dist/cjs/utils/crypto.js +404 -0
  25. package/dist/esm/core/backup.d.ts +10 -0
  26. package/dist/esm/core/backup.d.ts.map +1 -0
  27. package/dist/esm/core/backup.js +134 -0
  28. package/dist/esm/core/redux-cluster.d.ts +47 -0
  29. package/dist/esm/core/redux-cluster.d.ts.map +1 -0
  30. package/dist/esm/core/redux-cluster.js +376 -0
  31. package/dist/esm/index.d.ts +22 -0
  32. package/dist/esm/index.d.ts.map +1 -0
  33. package/dist/esm/index.js +25 -0
  34. package/dist/esm/network/client.d.ts +23 -0
  35. package/dist/esm/network/client.d.ts.map +1 -0
  36. package/dist/esm/network/client.js +221 -0
  37. package/dist/esm/network/server.d.ts +39 -0
  38. package/dist/esm/network/server.d.ts.map +1 -0
  39. package/dist/esm/network/server.js +408 -0
  40. package/dist/esm/package.json +1 -0
  41. package/dist/esm/types/index.d.ts +125 -0
  42. package/dist/esm/types/index.d.ts.map +1 -0
  43. package/dist/esm/types/index.js +17 -0
  44. package/dist/esm/utils/crypto.d.ts +22 -0
  45. package/dist/esm/utils/crypto.d.ts.map +1 -0
  46. package/dist/esm/utils/crypto.js +351 -0
  47. package/package.json +115 -34
  48. package/index.js +0 -678
  49. package/test.auto.js +0 -94
  50. package/test.auto.proc1.js +0 -97
  51. package/test.auto.proc2.js +0 -85
  52. package/test.visual.client.highload.js +0 -102
  53. package/test.visual.client.js +0 -103
  54. package/test.visual.error.js +0 -45
  55. package/test.visual.js +0 -97
  56. package/test.visual.server.highload.js +0 -102
  57. package/test.visual.server.js +0 -103
@@ -0,0 +1,351 @@
1
+ import * as crypto from "crypto";
2
+ import { Transform } from "stream";
3
+ import { SerializationMode } from "../types";
4
+ // Import ObjectStream components - optional dependency
5
+ let Stringifer = null;
6
+ let Parser = null;
7
+ // Initialize ObjectStream if available
8
+ async function initializeObjectStream() {
9
+ try {
10
+ const objectStreamModule = await import("@sergdudko/objectstream");
11
+ Stringifer = objectStreamModule.Stringifer;
12
+ Parser = objectStreamModule.Parser;
13
+ }
14
+ catch {
15
+ // ObjectStream is optional
16
+ }
17
+ }
18
+ // Initialize once
19
+ initializeObjectStream();
20
+ // Import ProtoObject - optional dependency
21
+ let protoObjectConstructor = null;
22
+ // Initialize ProtoObject if available
23
+ async function initializeProtoObject() {
24
+ try {
25
+ const protoObjectModule = await import("protoobject");
26
+ protoObjectConstructor = protoObjectModule.ProtoObject ||
27
+ protoObjectModule.default ||
28
+ protoObjectModule;
29
+ }
30
+ catch {
31
+ // ProtoObject is optional
32
+ }
33
+ }
34
+ // Initialize once
35
+ initializeProtoObject();
36
+ // Check if ProtoObject is available
37
+ function isProtoObjectAvailable() {
38
+ return protoObjectConstructor !== null;
39
+ }
40
+ // Get ProtoObject class (proper TypeScript way)
41
+ function getProtoObjectClass() {
42
+ return protoObjectConstructor;
43
+ }
44
+ // Generate hash for reducer names
45
+ export function hasher(input) {
46
+ return crypto.createHash("md5").update(input).digest("hex");
47
+ }
48
+ // Create cipher for encryption
49
+ export function createCipher(key) {
50
+ const iv = crypto.randomBytes(16);
51
+ return crypto.createCipheriv("aes-256-ctr", Buffer.from(key), iv);
52
+ }
53
+ // Create decipher for decryption
54
+ export function createDecipher(key, iv) {
55
+ return crypto.createDecipheriv("aes-256-ctr", Buffer.from(key), iv);
56
+ }
57
+ // Encryption function for backup
58
+ export function encrypter(data, password) {
59
+ const key = crypto.scryptSync(password, "salt", 32);
60
+ const iv = crypto.randomBytes(16);
61
+ const cipher = crypto.createCipheriv("aes-256-ctr", key, iv);
62
+ let encrypted = cipher.update(data, "utf8", "hex");
63
+ encrypted += cipher.final("hex");
64
+ return iv.toString("hex") + ":" + encrypted;
65
+ }
66
+ // Decryption function for backup
67
+ export function decrypter(encryptedData, password) {
68
+ const [ivHex, encrypted] = encryptedData.split(":");
69
+ const key = crypto.scryptSync(password, "salt", 32);
70
+ const iv = Buffer.from(ivHex, "hex");
71
+ const decipher = crypto.createDecipheriv("aes-256-ctr", key, iv);
72
+ let decrypted = decipher.update(encrypted, "hex", "utf8");
73
+ decrypted += decipher.final("utf8");
74
+ return decrypted;
75
+ }
76
+ // Utility function for deep cloning objects
77
+ export function deepClone(obj) {
78
+ if (typeof structuredClone === "function") {
79
+ return structuredClone(obj);
80
+ }
81
+ return JSON.parse(JSON.stringify(obj));
82
+ }
83
+ // Universal cloning function based on mode
84
+ export function universalClone(obj, mode, _classRegistry) {
85
+ if (mode === SerializationMode.PROTOOBJECT && isProtoObjectAvailable()) {
86
+ return protoObjectClone(obj);
87
+ }
88
+ return deepClone(obj);
89
+ }
90
+ // ProtoObject cloning function that handles nested ProtoObject instances
91
+ export function protoObjectClone(obj) {
92
+ const ProtoObjectClass = getProtoObjectClass();
93
+ if (!ProtoObjectClass) {
94
+ return deepClone(obj);
95
+ }
96
+ // If it's not a ProtoObject, use regular deep clone
97
+ if (!(obj instanceof ProtoObjectClass)) {
98
+ return deepClone(obj);
99
+ }
100
+ // Create a new ProtoObject with all properties
101
+ const clonedData = {};
102
+ // Copy all enumerable properties
103
+ for (const key of Object.keys(obj)) {
104
+ const value = obj[key];
105
+ if (value instanceof ProtoObjectClass) {
106
+ // Recursively clone nested ProtoObject
107
+ clonedData[key] = protoObjectClone(value);
108
+ }
109
+ else if (Array.isArray(value)) {
110
+ // Clone arrays with potential ProtoObject elements
111
+ clonedData[key] = value.map((item) => item instanceof ProtoObjectClass
112
+ ? protoObjectClone(item)
113
+ : deepClone(item));
114
+ }
115
+ else if (value !== null && typeof value === "object") {
116
+ // Clone nested objects
117
+ clonedData[key] = deepClone(value);
118
+ }
119
+ else {
120
+ // Copy primitive values
121
+ clonedData[key] = value;
122
+ }
123
+ }
124
+ return new ProtoObjectClass(clonedData);
125
+ }
126
+ // Universal serialization function
127
+ export function universalSerialize(obj, mode, classRegistry) {
128
+ if (mode === SerializationMode.PROTOOBJECT && isProtoObjectAvailable()) {
129
+ return serializeProtoObject(obj, classRegistry);
130
+ }
131
+ // Default JSON serialization
132
+ return JSON.stringify(obj);
133
+ }
134
+ // Universal deserialization function
135
+ export function universalDeserialize(str, mode, classRegistry) {
136
+ if (mode === SerializationMode.PROTOOBJECT && isProtoObjectAvailable()) {
137
+ return deserializeProtoObject(str, classRegistry);
138
+ }
139
+ // Default JSON deserialization
140
+ return JSON.parse(str);
141
+ }
142
+ // ProtoObject serialization for IPC with class information
143
+ export function serializeProtoObject(obj, classRegistry) {
144
+ const ProtoObjectClass = getProtoObjectClass();
145
+ if (!ProtoObjectClass) {
146
+ return JSON.stringify(obj);
147
+ }
148
+ const serialize = (value) => {
149
+ if (value instanceof ProtoObjectClass) {
150
+ const data = {
151
+ __isProtoObject: true,
152
+ __className: value.constructor.name,
153
+ };
154
+ // Store class information if registry is provided
155
+ if (classRegistry && value.constructor.name !== "ProtoObject") {
156
+ classRegistry.set(value.constructor.name, value.constructor);
157
+ }
158
+ for (const key of Object.keys(value)) {
159
+ data[key] = serialize(value[key]);
160
+ }
161
+ return data;
162
+ }
163
+ else if (Array.isArray(value)) {
164
+ return value.map(serialize);
165
+ }
166
+ else if (value !== null && typeof value === "object") {
167
+ const result = {};
168
+ for (const [key, val] of Object.entries(value)) {
169
+ result[key] = serialize(val);
170
+ }
171
+ return result;
172
+ }
173
+ return value;
174
+ };
175
+ return JSON.stringify(serialize(obj));
176
+ }
177
+ // ProtoObject deserialization for IPC with class reconstruction
178
+ export function deserializeProtoObject(str, classRegistry) {
179
+ const ProtoObjectClass = getProtoObjectClass();
180
+ if (!ProtoObjectClass) {
181
+ return JSON.parse(str);
182
+ }
183
+ const deserialize = (value) => {
184
+ if (value && typeof value === "object" && value.__isProtoObject) {
185
+ const className = value.__className;
186
+ const data = {};
187
+ for (const [key, val] of Object.entries(value)) {
188
+ if (key !== "__isProtoObject" && key !== "__className") {
189
+ data[key] = deserialize(val);
190
+ }
191
+ }
192
+ // Try to use registered class, fallback to ProtoObject
193
+ if (className && classRegistry && classRegistry.has(className)) {
194
+ const ClassConstructor = classRegistry.get(className);
195
+ return new ClassConstructor(data);
196
+ }
197
+ else {
198
+ return new ProtoObjectClass(data);
199
+ }
200
+ }
201
+ else if (Array.isArray(value)) {
202
+ return value.map(deserialize);
203
+ }
204
+ else if (value !== null && typeof value === "object") {
205
+ const result = {};
206
+ for (const [key, val] of Object.entries(value)) {
207
+ result[key] = deserialize(val);
208
+ }
209
+ return result;
210
+ }
211
+ return value;
212
+ };
213
+ return deserialize(JSON.parse(str));
214
+ }
215
+ // Helper function to create a shared class registry
216
+ export function createClassRegistry() {
217
+ return new Map();
218
+ }
219
+ // Stream pipeline functions for buffer->json->class->json->buffer processing
220
+ // Create ObjectStream Parser for JSON parsing (Buffer -> Object)
221
+ export function createObjectStreamParser() {
222
+ if (Parser) {
223
+ return new Parser();
224
+ }
225
+ return null;
226
+ }
227
+ // Create ObjectStream Stringifier for JSON serialization (Object -> Buffer)
228
+ export function createObjectStreamStringifier() {
229
+ if (Stringifer) {
230
+ return new Stringifer();
231
+ }
232
+ return null;
233
+ }
234
+ // Legacy function for backward compatibility
235
+ export function createObjectStream() {
236
+ return createObjectStreamParser();
237
+ }
238
+ // Create transform stream that converts JSON objects to ProtoObject instances
239
+ export function createDeserializationStream(mode, classRegistry) {
240
+ return new Transform({
241
+ objectMode: true,
242
+ transform(chunk, encoding, callback) {
243
+ try {
244
+ if (mode === SerializationMode.PROTOOBJECT &&
245
+ isProtoObjectAvailable()) {
246
+ const deserialized = deserializeFromObject(chunk, classRegistry);
247
+ callback(null, deserialized);
248
+ }
249
+ else {
250
+ callback(null, chunk);
251
+ }
252
+ }
253
+ catch (error) {
254
+ callback(error instanceof Error ? error : new Error(String(error)));
255
+ }
256
+ },
257
+ });
258
+ }
259
+ // Create transform stream that converts ProtoObject instances to JSON objects
260
+ export function createSerializationStream(mode, classRegistry) {
261
+ return new Transform({
262
+ objectMode: true,
263
+ transform(chunk, encoding, callback) {
264
+ try {
265
+ if (mode === SerializationMode.PROTOOBJECT &&
266
+ isProtoObjectAvailable()) {
267
+ const serialized = serializeToObject(chunk, classRegistry);
268
+ callback(null, serialized);
269
+ }
270
+ else {
271
+ callback(null, chunk);
272
+ }
273
+ }
274
+ catch (error) {
275
+ callback(error instanceof Error ? error : new Error(String(error)));
276
+ }
277
+ },
278
+ });
279
+ }
280
+ // Helper functions for object-level serialization/deserialization (without JSON.stringify)
281
+ function serializeToObject(obj, classRegistry) {
282
+ const ProtoObjectClass = getProtoObjectClass();
283
+ if (!ProtoObjectClass) {
284
+ return obj;
285
+ }
286
+ const serialize = (value) => {
287
+ if (value instanceof ProtoObjectClass) {
288
+ const data = {
289
+ __isProtoObject: true,
290
+ __className: value.constructor.name,
291
+ };
292
+ // Store class information if registry is provided
293
+ if (classRegistry && value.constructor.name !== "ProtoObject") {
294
+ classRegistry.set(value.constructor.name, value.constructor);
295
+ }
296
+ for (const key of Object.keys(value)) {
297
+ data[key] = serialize(value[key]);
298
+ }
299
+ return data;
300
+ }
301
+ else if (Array.isArray(value)) {
302
+ return value.map(serialize);
303
+ }
304
+ else if (value !== null && typeof value === "object") {
305
+ const result = {};
306
+ for (const [key, val] of Object.entries(value)) {
307
+ result[key] = serialize(val);
308
+ }
309
+ return result;
310
+ }
311
+ return value;
312
+ };
313
+ return serialize(obj);
314
+ }
315
+ function deserializeFromObject(obj, classRegistry) {
316
+ const ProtoObjectClass = getProtoObjectClass();
317
+ if (!ProtoObjectClass) {
318
+ return obj;
319
+ }
320
+ const deserialize = (value) => {
321
+ if (value && typeof value === "object" && value.__isProtoObject) {
322
+ const className = value.__className;
323
+ const data = {};
324
+ for (const [key, val] of Object.entries(value)) {
325
+ if (key !== "__isProtoObject" && key !== "__className") {
326
+ data[key] = deserialize(val);
327
+ }
328
+ }
329
+ // Try to use registered class, fallback to ProtoObject
330
+ if (className && classRegistry && classRegistry.has(className)) {
331
+ const ClassConstructor = classRegistry.get(className);
332
+ return new ClassConstructor(data);
333
+ }
334
+ else {
335
+ return new ProtoObjectClass(data);
336
+ }
337
+ }
338
+ else if (Array.isArray(value)) {
339
+ return value.map(deserialize);
340
+ }
341
+ else if (value !== null && typeof value === "object") {
342
+ const result = {};
343
+ for (const [key, val] of Object.entries(value)) {
344
+ result[key] = deserialize(val);
345
+ }
346
+ return result;
347
+ }
348
+ return value;
349
+ };
350
+ return deserialize(obj);
351
+ }
package/package.json CHANGED
@@ -1,34 +1,115 @@
1
- {
2
- "name": "redux-cluster",
3
- "version": "1.10.0",
4
- "description": "Cluster module for redux synchronizes all redux store in cluster processes.",
5
- "main": "index.js",
6
- "scripts": {
7
- "test": "node test.js"
8
- },
9
- "repository": {
10
- "type": "git",
11
- "url": "git+ssh://git@github.com/siarheidudko/redux-cluster.git"
12
- },
13
- "keywords": [
14
- "redux",
15
- "cluster",
16
- "ipc",
17
- "process"
18
- ],
19
- "author": "Siarhei Dudko",
20
- "license": "MIT",
21
- "bugs": {
22
- "url": "https://github.com/siarheidudko/redux-cluster/issues"
23
- },
24
- "homepage": "https://github.com/siarheidudko/redux-cluster#readme",
25
- "dependencies": {
26
- "@sergdudko/objectstream": "^1.6.0",
27
- "event-stream": "^3.3.5",
28
- "lodash": "^4.17.15",
29
- "redux": "^4.0.4"
30
- },
31
- "devDependencies": {
32
- "colors": "^1.4.0"
33
- }
34
- }
1
+ {
2
+ "name": "redux-cluster",
3
+ "version": "2.0.0",
4
+ "type": "module",
5
+ "description": "TypeScript cluster module for Redux that synchronizes Redux stores across cluster processes via IPC and TCP sockets",
6
+ "main": "dist/cjs/index.js",
7
+ "module": "dist/esm/index.js",
8
+ "types": "dist/esm/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/esm/index.d.ts",
13
+ "default": "./dist/esm/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/cjs/index.d.ts",
17
+ "default": "./dist/cjs/index.js"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "dist/esm/**/*",
23
+ "dist/cjs/**/*",
24
+ "README.md",
25
+ "LICENSE"
26
+ ],
27
+ "funding": [
28
+ {
29
+ "type": "individual",
30
+ "url": "http://dudko.dev/donate"
31
+ },
32
+ {
33
+ "type": "buymeacoffee",
34
+ "url": "https://www.buymeacoffee.com/dudko.dev"
35
+ },
36
+ {
37
+ "type": "paypal",
38
+ "url": "https://paypal.me/dudkodev"
39
+ },
40
+ {
41
+ "type": "patreon",
42
+ "url": "https://patreon.com/dudko_dev"
43
+ }
44
+ ],
45
+ "scripts": {
46
+ "build": "npm run clean && npm run build:esm && npm run build:cjs && npm run build:pkg",
47
+ "build:esm": "tsc",
48
+ "build:cjs": "tsc -p tsconfig.cjs.json",
49
+ "build:pkg": "./scripts/create-package-files.sh",
50
+ "build:watch": "tsc --watch",
51
+ "clean": "rm -rf dist",
52
+ "prepublishOnly": "npm run clean && npm run build",
53
+ "test": "npm run test:all",
54
+ "test:all": "node --test --import tsx tests/unit.test.ts tests/error.test.ts tests/transport.test.ts",
55
+ "test:unit": "node --test --import tsx tests/unit.test.ts",
56
+ "test:transport": "node --test --import tsx tests/transport.test.ts",
57
+ "test:cluster": "node --test --import tsx tests/cluster.test.ts",
58
+ "test:integration": "node --test --import tsx tests/integration.test.ts",
59
+ "test:error": "node --test --import tsx tests/error.test.ts",
60
+ "test:integration-full": "./integration/run-tests.sh",
61
+ "lint": "eslint src/**/*.ts tests/**/*.ts",
62
+ "lint:fix": "eslint src/**/*.ts tests/**/*.ts --fix",
63
+ "example:basic": "node examples/basic.js",
64
+ "example:server": "node examples/server.js",
65
+ "example:client": "node examples/client.js"
66
+ },
67
+ "repository": {
68
+ "type": "git",
69
+ "url": "git+ssh://git@github.com/siarheidudko/redux-cluster.git"
70
+ },
71
+ "keywords": [
72
+ "redux",
73
+ "cluster",
74
+ "ipc",
75
+ "process",
76
+ "typescript",
77
+ "synchronization",
78
+ "distributed",
79
+ "tcp",
80
+ "socket"
81
+ ],
82
+ "author": {
83
+ "name": "Siarhei Dudko",
84
+ "email": "siarhei@dudko.dev",
85
+ "url": "https://dudko.dev"
86
+ },
87
+ "license": "MIT",
88
+ "bugs": {
89
+ "url": "https://github.com/siarheidudko/redux-cluster/issues"
90
+ },
91
+ "homepage": "https://github.com/siarheidudko/redux-cluster#readme",
92
+ "dependencies": {
93
+ "@sergdudko/objectstream": "^3.2.26",
94
+ "protoobject": "^1.1.30",
95
+ "redux": "^5.0.1"
96
+ },
97
+ "devDependencies": {
98
+ "@types/node": "^20.11.17",
99
+ "@typescript-eslint/eslint-plugin": "^8.42.0",
100
+ "@typescript-eslint/parser": "^8.42.0",
101
+ "eslint": "^9.35.0",
102
+ "ts-node": "^10.9.2",
103
+ "tsx": "^4.20.5",
104
+ "typescript": "^5.5.4"
105
+ },
106
+ "engines": {
107
+ "node": ">=14.0.0"
108
+ },
109
+ "overrides": {
110
+ "debug": ">=4.3.7"
111
+ },
112
+ "publishConfig": {
113
+ "access": "public"
114
+ }
115
+ }