redux-cluster 1.9.2 → 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 -42
  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,10 @@
1
+ import { BackupSettings, ReduxClusterStore } from "../types";
2
+ export declare class BackupManager<S = any> {
3
+ private store;
4
+ private settings;
5
+ private createBackupInstance?;
6
+ constructor(store: ReduxClusterStore<S>, settings: BackupSettings);
7
+ initialize(): Promise<boolean>;
8
+ private loadBackup;
9
+ }
10
+ //# sourceMappingURL=backup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backup.d.ts","sourceRoot":"","sources":["../../../src/core/backup.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAe,MAAM,UAAU,CAAC;AAG1E,qBAAa,aAAa,CAAC,CAAC,GAAG,GAAG;IAI9B,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,QAAQ;IAJlB,OAAO,CAAC,oBAAoB,CAAC,CAAoB;gBAGvC,KAAK,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAC3B,QAAQ,EAAE,cAAc;IAGrB,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC;YAiB7B,UAAU;CA+CzB"}
@@ -0,0 +1,166 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.BackupManager = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const types_1 = require("../types");
39
+ const crypto_1 = require("../utils/crypto");
40
+ class BackupManager {
41
+ constructor(store, settings) {
42
+ this.store = store;
43
+ this.settings = settings;
44
+ }
45
+ async initialize() {
46
+ try {
47
+ await this.loadBackup();
48
+ this.createBackupInstance = new BackupInstance(this.store, this.settings);
49
+ return true;
50
+ }
51
+ catch (err) {
52
+ if (err.message.toLowerCase().includes("no such file or directory")) {
53
+ this.createBackupInstance = new BackupInstance(this.store, this.settings);
54
+ return true;
55
+ }
56
+ throw err;
57
+ }
58
+ }
59
+ async loadBackup() {
60
+ return new Promise((resolve, reject) => {
61
+ fs.readFile(this.settings.path, (err, data) => {
62
+ if (err) {
63
+ reject(new Error(`ReduxCluster.backup load error: ${err.message}`));
64
+ return;
65
+ }
66
+ try {
67
+ let content = data.toString();
68
+ // Decrypt if key is provided
69
+ if (this.settings.key) {
70
+ content = (0, crypto_1.decrypter)(content, this.settings.key);
71
+ }
72
+ const state = JSON.parse(content);
73
+ // Restore state using internal method
74
+ if ("_internalSync" in this.store &&
75
+ typeof this.store._internalSync === "function") {
76
+ this.store._internalSync(state);
77
+ }
78
+ else {
79
+ // Fallback: use dispatchNEW if available, otherwise skip restore
80
+ if ("dispatchNEW" in this.store &&
81
+ typeof this.store.dispatchNEW === "function") {
82
+ this.store.dispatchNEW({
83
+ type: types_1.MessageType.SYNC,
84
+ payload: state,
85
+ _internal: true,
86
+ });
87
+ }
88
+ }
89
+ setTimeout(() => resolve(), 500);
90
+ }
91
+ catch (parseErr) {
92
+ reject(new Error(`ReduxCluster.backup decoding error: ${parseErr.message}`));
93
+ }
94
+ });
95
+ });
96
+ }
97
+ }
98
+ exports.BackupManager = BackupManager;
99
+ class BackupInstance {
100
+ constructor(store, settings) {
101
+ this.store = store;
102
+ this.settings = settings;
103
+ this.count = 0;
104
+ this.allowed = true;
105
+ this.unsubscribe = null;
106
+ this.unsubscribe = this.store.subscribe(() => {
107
+ this.handleStateChange();
108
+ });
109
+ }
110
+ handleStateChange() {
111
+ if (typeof this.settings.timeout === "number") {
112
+ // Priority setting - timeout based backup
113
+ if (this.allowed) {
114
+ this.allowed = false;
115
+ setTimeout(() => {
116
+ this.write(true);
117
+ }, this.settings.timeout * 1000);
118
+ }
119
+ }
120
+ else if (typeof this.settings.count === "number") {
121
+ // Count based backup
122
+ this.count++;
123
+ if (this.count >= this.settings.count) {
124
+ this.count = 0;
125
+ this.write();
126
+ }
127
+ }
128
+ }
129
+ write(restart = false, callback) {
130
+ if (this.allowed || restart) {
131
+ try {
132
+ let content = JSON.stringify(this.store.getState());
133
+ // Encrypt if key is provided
134
+ if (this.settings.key) {
135
+ content = (0, crypto_1.encrypter)(content, this.settings.key);
136
+ }
137
+ this.writeToFile(content, callback);
138
+ }
139
+ catch (err) {
140
+ this.store.stderr(`ReduxCluster.backup write error: ${err.message}`);
141
+ this.allowed = false;
142
+ setTimeout(() => this.write(true, callback), 1000);
143
+ }
144
+ }
145
+ }
146
+ writeToFile(content, callback) {
147
+ try {
148
+ fs.writeFileSync(this.settings.path, content);
149
+ this.allowed = true;
150
+ if (callback) {
151
+ callback(true);
152
+ }
153
+ }
154
+ catch (err) {
155
+ this.store.stderr(`ReduxCluster.backup write error: ${err.message}`);
156
+ this.allowed = false;
157
+ setTimeout(() => this.write(true, callback), 1000);
158
+ }
159
+ }
160
+ dispose() {
161
+ if (this.unsubscribe) {
162
+ this.unsubscribe();
163
+ this.unsubscribe = null;
164
+ }
165
+ }
166
+ }
@@ -0,0 +1,47 @@
1
+ import { Store, Reducer, Action } from "redux";
2
+ import { ReduxClusterStore, SyncMode, Role, ClusterMessage, ServerSettings, ClientSettings, BackupSettings, ErrorHandler, ReduxClusterConfig } from "../types";
3
+ import { ClusterServer } from "../network/server";
4
+ import { ClusterClient } from "../network/client";
5
+ export declare class ReduxCluster<S = any, A extends Action = Action> implements ReduxClusterStore<S, A> {
6
+ readonly dispatch: Store<S, A>["dispatch"];
7
+ readonly getState: Store<S, A>["getState"];
8
+ readonly subscribe: Store<S, A>["subscribe"];
9
+ readonly replaceReducer: Store<S, A>["replaceReducer"];
10
+ readonly [Symbol.observable]: Store<S, A>[typeof Symbol.observable];
11
+ readonly RCHash: string;
12
+ readonly version: string;
13
+ readonly homepage: string;
14
+ readonly role: Role[];
15
+ connected: boolean;
16
+ mode: SyncMode;
17
+ resync: number;
18
+ stderr: ErrorHandler;
19
+ readonly config: ReduxClusterConfig;
20
+ private readonly altReducer;
21
+ private readonly defaultState;
22
+ private readonly store;
23
+ private readonly allsock;
24
+ private counter?;
25
+ private dispatchNEW?;
26
+ private unsubscribe?;
27
+ private classRegistry;
28
+ constructor(reducer: Reducer<S, A>, config?: ReduxClusterConfig);
29
+ private internalSync;
30
+ _internalSync(payload: S): void;
31
+ private createNewReducer;
32
+ private updateCounter;
33
+ private sendActionsToNodes;
34
+ private initializeClusterRole;
35
+ private initializeMaster;
36
+ private initializeWorker;
37
+ private handleMasterMessage;
38
+ private handleWorkerMessage;
39
+ sendtoall(message?: ClusterMessage): void;
40
+ sendtoallsock(message?: ClusterMessage): void;
41
+ createServer(settings?: ServerSettings): ClusterServer;
42
+ createClient(settings?: ClientSettings): ClusterClient;
43
+ backup(settings: BackupSettings): Promise<boolean>;
44
+ registerClass(name: string, classConstructor: any): void;
45
+ getRegisteredClasses(): string[];
46
+ }
47
+ //# sourceMappingURL=redux-cluster.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redux-cluster.d.ts","sourceRoot":"","sources":["../../../src/core/redux-cluster.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAW5D,OAAO,EACL,iBAAiB,EACjB,QAAQ,EACR,IAAI,EACJ,cAAc,EAEd,cAAc,EACd,cAAc,EACd,cAAc,EACd,YAAY,EACZ,kBAAkB,EAEnB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAMlD,qBAAa,YAAY,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,SAAS,MAAM,GAAG,MAAM,CAC1D,YAAW,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAAC;IAGlC,SAAgB,QAAQ,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;IAClD,SAAgB,QAAQ,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;IAClD,SAAgB,SAAS,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;IACpD,SAAgB,cAAc,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC;IAC9D,SAAgB,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,MAAM,CAAC,UAAU,CAAC,CAAC;IAG3E,SAAgB,MAAM,EAAE,MAAM,CAAC;IAC/B,SAAgB,OAAO,EAAE,MAAM,CAAC;IAChC,SAAgB,QAAQ,EAAE,MAAM,CAAC;IACjC,SAAgB,IAAI,EAAE,IAAI,EAAE,CAAM;IAC3B,SAAS,UAAS;IAClB,IAAI,EAAE,QAAQ,CAAY;IAC1B,MAAM,SAAQ;IACd,MAAM,EAAE,YAAY,CAAiB;IAC5C,SAAgB,MAAM,EAAE,kBAAkB,CAAC;IAE3C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAgB;IAC3C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAI;IACjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAc;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA2B;IACnD,OAAO,CAAC,OAAO,CAAC,CAAS;IACzB,OAAO,CAAC,WAAW,CAAC,CAA0B;IAC9C,OAAO,CAAC,WAAW,CAAC,CAAa;IACjC,OAAO,CAAC,aAAa,CAAyB;gBAElC,OAAO,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,GAAE,kBAAuB;IA4EnE,OAAO,CAAC,YAAY;IASb,aAAa,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI;IAItC,OAAO,CAAC,gBAAgB;IA2BxB,OAAO,CAAC,aAAa;IAkBrB,OAAO,CAAC,kBAAkB;IA0B1B,OAAO,CAAC,qBAAqB;IAU7B,OAAO,CAAC,gBAAgB;IA0BxB,OAAO,CAAC,gBAAgB;IAiCxB,OAAO,CAAC,mBAAmB;IAwD3B,OAAO,CAAC,mBAAmB;IAoCpB,SAAS,CAAC,OAAO,CAAC,EAAE,cAAc,GAAG,IAAI;IA6BzC,aAAa,CAAC,OAAO,CAAC,EAAE,cAAc,GAAG,IAAI;IAW7C,YAAY,CAAC,QAAQ,CAAC,EAAE,cAAc,GAAG,aAAa;IAetD,YAAY,CAAC,QAAQ,CAAC,EAAE,cAAc,GAAG,aAAa;IAetD,MAAM,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC;IAKlD,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,gBAAgB,EAAE,GAAG,GAAG,IAAI;IASxD,oBAAoB,IAAI,MAAM,EAAE;CAMxC"}
@@ -0,0 +1,367 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ReduxCluster = void 0;
7
+ const redux_1 = require("redux");
8
+ const cluster_1 = __importDefault(require("cluster"));
9
+ const fs_1 = require("fs");
10
+ const path_1 = require("path");
11
+ const crypto_1 = require("../utils/crypto");
12
+ const types_1 = require("../types");
13
+ const server_1 = require("../network/server");
14
+ const client_1 = require("../network/client");
15
+ const backup_1 = require("./backup");
16
+ // Global reducers registry to prevent name conflicts
17
+ const reducers = {};
18
+ class ReduxCluster {
19
+ constructor(reducer, config = {}) {
20
+ this.role = [];
21
+ this.connected = false;
22
+ this.mode = "action";
23
+ this.resync = 1000;
24
+ this.stderr = console.error;
25
+ this.allsock = {};
26
+ this.classRegistry = (0, crypto_1.createClassRegistry)();
27
+ this.altReducer = reducer;
28
+ this.RCHash = (0, crypto_1.hasher)(reducer.name) || "";
29
+ // Set configuration with defaults
30
+ this.config = {
31
+ serializationMode: types_1.SerializationMode.JSON,
32
+ debug: false,
33
+ ...config,
34
+ };
35
+ // Apply configuration to instance properties
36
+ if (config.mode)
37
+ this.mode = config.mode;
38
+ if (config.role)
39
+ this.role.push(...config.role);
40
+ if (config.stderr)
41
+ this.stderr = config.stderr;
42
+ if (config.resync)
43
+ this.resync = config.resync;
44
+ // Initialize class registry only if ProtoObject mode is enabled
45
+ if (this.config.serializationMode === types_1.SerializationMode.PROTOOBJECT) {
46
+ this.classRegistry = (0, crypto_1.createClassRegistry)();
47
+ }
48
+ // Load package info
49
+ try {
50
+ let packagePath;
51
+ try {
52
+ // Try CommonJS approach first (__dirname available)
53
+ packagePath = (0, path_1.join)(__dirname, "../../package.json");
54
+ }
55
+ catch {
56
+ // Fallback - use relative path from process.cwd()
57
+ packagePath = (0, path_1.join)(process.cwd(), "package.json");
58
+ }
59
+ const packageJson = JSON.parse((0, fs_1.readFileSync)(packagePath, "utf8"));
60
+ this.version = packageJson.version;
61
+ this.homepage = packageJson.homepage;
62
+ }
63
+ catch {
64
+ this.version = "2.0.0";
65
+ this.homepage = "https://github.com/siarheidudko/redux-cluster";
66
+ }
67
+ // Validate reducer uniqueness (only if different hash)
68
+ if (typeof reducers[reducer.name] !== "undefined" &&
69
+ reducers[reducer.name] !== this.RCHash) {
70
+ throw new Error("Please don't use a reducer with the same name!");
71
+ }
72
+ reducers[reducer.name] = this.RCHash;
73
+ // Get default state
74
+ try {
75
+ const defaultState = this.altReducer(undefined, {});
76
+ if (typeof defaultState === "object" && defaultState !== null) {
77
+ this.defaultState = defaultState;
78
+ }
79
+ else {
80
+ throw new Error("The returned value is not an object.");
81
+ }
82
+ }
83
+ catch {
84
+ this.defaultState = {};
85
+ }
86
+ // Create Redux store with custom reducer
87
+ this.store = (0, redux_1.createStore)(this.createNewReducer());
88
+ // Bind Redux methods
89
+ this.dispatch = this.store.dispatch;
90
+ this.getState = this.store.getState;
91
+ this.subscribe = this.store.subscribe;
92
+ this.replaceReducer = this.store.replaceReducer;
93
+ this[Symbol.observable] = this.store[Symbol.observable];
94
+ this.initializeClusterRole();
95
+ }
96
+ // Internal method for sync actions
97
+ internalSync(payload) {
98
+ this.store.dispatch({
99
+ type: types_1.MessageType.SYNC,
100
+ payload,
101
+ _internal: true,
102
+ });
103
+ }
104
+ // Expose internal sync for backup purposes
105
+ _internalSync(payload) {
106
+ this.internalSync(payload);
107
+ }
108
+ createNewReducer() {
109
+ return (state = this.defaultState, action) => {
110
+ // Handle sync action (internal use only)
111
+ if (action.type === types_1.MessageType.SYNC) {
112
+ // Check if this is an internal sync call
113
+ const syncAction = action;
114
+ if (syncAction._internal) {
115
+ return (0, crypto_1.universalClone)(syncAction.payload, this.config.serializationMode, this.classRegistry);
116
+ }
117
+ else {
118
+ throw new Error("Please don't use REDUX_CLUSTER_SYNC action type!");
119
+ }
120
+ }
121
+ // Handle sync mode
122
+ if (this.mode === "action") {
123
+ this.updateCounter();
124
+ this.sendActionsToNodes(action);
125
+ }
126
+ return this.altReducer(state, action);
127
+ };
128
+ }
129
+ updateCounter() {
130
+ if (typeof this.counter === "undefined" || this.counter === this.resync) {
131
+ this.counter = 1;
132
+ }
133
+ else {
134
+ this.counter++;
135
+ }
136
+ // Periodic full sync
137
+ if (this.counter === this.resync) {
138
+ if (this.role.includes("master")) {
139
+ setTimeout(() => this.sendtoall(), 100);
140
+ }
141
+ if (this.role.includes("server")) {
142
+ setTimeout(() => this.sendtoallsock(), 100);
143
+ }
144
+ }
145
+ }
146
+ sendActionsToNodes(action) {
147
+ if (this.role.includes("master")) {
148
+ setTimeout(() => this.sendtoall({
149
+ _msg: types_1.MessageType.MSG_TO_WORKER,
150
+ _hash: this.RCHash,
151
+ _action: action,
152
+ }), 1);
153
+ }
154
+ if (this.role.includes("server")) {
155
+ setTimeout(() => this.sendtoallsock({
156
+ _msg: types_1.MessageType.MSG_TO_WORKER,
157
+ _hash: this.RCHash,
158
+ _action: action,
159
+ }), 1);
160
+ }
161
+ }
162
+ initializeClusterRole() {
163
+ // Assign "master" role only to primary process in cluster
164
+ if (cluster_1.default.isPrimary) {
165
+ this.initializeMaster();
166
+ }
167
+ else {
168
+ // Assign "worker" role to non-master processes
169
+ this.initializeWorker();
170
+ }
171
+ }
172
+ initializeMaster() {
173
+ if (!this.role.includes("master")) {
174
+ this.role.push("master");
175
+ }
176
+ // Subscribe to changes in snapshot mode
177
+ this.unsubscribe = this.subscribe(() => {
178
+ if (this.mode === "snapshot") {
179
+ this.sendtoall();
180
+ }
181
+ });
182
+ // Listen for messages from workers
183
+ cluster_1.default.on("message", (worker, message, _handle) => {
184
+ if (arguments.length === 2) {
185
+ _handle = message;
186
+ message = worker;
187
+ worker = undefined;
188
+ }
189
+ this.handleMasterMessage(message, worker);
190
+ });
191
+ this.connected = true;
192
+ }
193
+ initializeWorker() {
194
+ if (!this.role.includes("worker")) {
195
+ this.role.push("worker");
196
+ }
197
+ // Override dispatch to send to master
198
+ this.dispatchNEW = this.dispatch;
199
+ this.dispatch = (action) => {
200
+ if (process.send) {
201
+ process.send({
202
+ _msg: types_1.MessageType.MSG_TO_MASTER,
203
+ _hash: this.RCHash,
204
+ _action: action,
205
+ });
206
+ }
207
+ };
208
+ // Listen for messages from master
209
+ process.on("message", (data) => {
210
+ this.handleWorkerMessage(data);
211
+ });
212
+ this.connected = true;
213
+ // Request initial state
214
+ if (process.send) {
215
+ process.send({
216
+ _msg: types_1.MessageType.START,
217
+ _hash: this.RCHash,
218
+ });
219
+ }
220
+ }
221
+ handleMasterMessage(message, worker) {
222
+ if (message._hash === this.RCHash) {
223
+ switch (message._msg) {
224
+ case types_1.MessageType.MSG_TO_MASTER:
225
+ if (message._action.type === types_1.MessageType.SYNC) {
226
+ throw new Error("Please don't use REDUX_CLUSTER_SYNC action type!");
227
+ }
228
+ // Deserialize action if it contains ProtoObject
229
+ let action = message._action;
230
+ if (message._serialized) {
231
+ try {
232
+ action = (0, crypto_1.universalDeserialize)(message._serialized, this.config.serializationMode, this.classRegistry)._action;
233
+ }
234
+ catch {
235
+ // Fallback to regular action
236
+ action = message._action;
237
+ }
238
+ }
239
+ this.store.dispatch(action);
240
+ break;
241
+ case types_1.MessageType.START:
242
+ const responseMsg = {
243
+ _msg: types_1.MessageType.MSG_TO_WORKER,
244
+ _hash: this.RCHash,
245
+ _action: {
246
+ type: types_1.MessageType.SYNC,
247
+ payload: this.getState(),
248
+ },
249
+ };
250
+ const serializedResponse = {
251
+ ...responseMsg,
252
+ _serialized: (0, crypto_1.universalSerialize)(responseMsg, this.config.serializationMode, this.classRegistry),
253
+ };
254
+ if (worker && cluster_1.default.workers && cluster_1.default.workers[worker.id]) {
255
+ cluster_1.default.workers[worker.id].send(serializedResponse);
256
+ }
257
+ else {
258
+ this.sendtoall();
259
+ }
260
+ break;
261
+ default:
262
+ // Ignore unknown message types
263
+ break;
264
+ }
265
+ }
266
+ }
267
+ handleWorkerMessage(data) {
268
+ if (data._hash === this.RCHash && this.role.includes("worker")) {
269
+ // Deserialize ProtoObject if needed
270
+ let processedData = data;
271
+ if (data._serialized) {
272
+ try {
273
+ processedData = JSON.parse(data._serialized);
274
+ processedData = (0, crypto_1.universalDeserialize)(data._serialized, this.config.serializationMode, this.classRegistry);
275
+ }
276
+ catch {
277
+ // Fallback to regular data if deserialization fails
278
+ processedData = data;
279
+ }
280
+ }
281
+ switch (processedData._msg) {
282
+ case types_1.MessageType.MSG_TO_WORKER:
283
+ if (this.dispatchNEW) {
284
+ this.dispatchNEW(processedData._action);
285
+ }
286
+ break;
287
+ case types_1.MessageType.CONN_STATUS:
288
+ this.connected = processedData._connected;
289
+ break;
290
+ default:
291
+ // Ignore unknown message types
292
+ break;
293
+ }
294
+ }
295
+ }
296
+ sendtoall(message) {
297
+ if (cluster_1.default.isPrimary && cluster_1.default.workers) {
298
+ const msg = message || {
299
+ _msg: types_1.MessageType.MSG_TO_WORKER,
300
+ _hash: this.RCHash,
301
+ _action: {
302
+ type: types_1.MessageType.SYNC,
303
+ payload: this.getState(),
304
+ },
305
+ };
306
+ // Serialize ProtoObject instances for IPC
307
+ const serializedMsg = {
308
+ ...msg,
309
+ _serialized: (0, crypto_1.universalSerialize)(msg, this.config.serializationMode, this.classRegistry),
310
+ };
311
+ for (const id in cluster_1.default.workers) {
312
+ if (cluster_1.default.workers[id]) {
313
+ cluster_1.default.workers[id].send(serializedMsg);
314
+ }
315
+ }
316
+ }
317
+ }
318
+ sendtoallsock(message) {
319
+ for (const id in this.allsock) {
320
+ if (typeof this.allsock[id] === "object" &&
321
+ typeof this.allsock[id].sendtoall === "function") {
322
+ setTimeout(() => this.allsock[id].sendtoall(message), 1);
323
+ }
324
+ }
325
+ }
326
+ createServer(settings) {
327
+ if (!cluster_1.default.isPrimary && settings?.path && process.platform === "win32") {
328
+ throw new Error("Named channel is not supported in the child process, please use TCP-server");
329
+ }
330
+ if (!this.role.includes("server")) {
331
+ this.role.push("server");
332
+ }
333
+ this.connected = false;
334
+ return new server_1.ClusterServer(this, settings);
335
+ }
336
+ createClient(settings) {
337
+ if (this.role.includes("client")) {
338
+ throw new Error("One storage cannot be connected to two servers at the same time.");
339
+ }
340
+ if (!this.role.includes("client")) {
341
+ this.role.push("client");
342
+ }
343
+ this.connected = false;
344
+ return new client_1.ClusterClient(this, settings);
345
+ }
346
+ backup(settings) {
347
+ return new backup_1.BackupManager(this, settings).initialize();
348
+ }
349
+ // Register custom ProtoObject classes for proper serialization
350
+ registerClass(name, classConstructor) {
351
+ if (this.config.serializationMode === types_1.SerializationMode.PROTOOBJECT) {
352
+ this.classRegistry.set(name, classConstructor);
353
+ }
354
+ else {
355
+ console.warn("registerClass() is only available in ProtoObject mode");
356
+ }
357
+ }
358
+ // Get registered classes (useful for debugging)
359
+ getRegisteredClasses() {
360
+ if (this.config.serializationMode === types_1.SerializationMode.PROTOOBJECT) {
361
+ return Array.from(this.classRegistry.keys());
362
+ }
363
+ return [];
364
+ }
365
+ }
366
+ exports.ReduxCluster = ReduxCluster;
367
+ Symbol.observable;
@@ -0,0 +1,22 @@
1
+ import { Reducer, Action, combineReducers, bindActionCreators, applyMiddleware, compose } from "redux";
2
+ import { ReduxCluster } from "./core/redux-cluster";
3
+ import { ReduxClusterConfig } from "./types";
4
+ import { hasher } from "./utils/crypto";
5
+ export * from "redux";
6
+ export * from "./types";
7
+ export declare const functions: {
8
+ hasher: typeof hasher;
9
+ };
10
+ export declare function createStore<S = any, A extends Action = Action>(reducer: Reducer<S, A>, config?: ReduxClusterConfig): ReduxCluster<S, A>;
11
+ declare const _default: {
12
+ createStore: typeof createStore;
13
+ functions: {
14
+ hasher: typeof hasher;
15
+ };
16
+ combineReducers: typeof combineReducers;
17
+ bindActionCreators: typeof bindActionCreators;
18
+ applyMiddleware: typeof applyMiddleware;
19
+ compose: typeof compose;
20
+ };
21
+ export default _default;
22
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EACP,MAAM,EACN,eAAe,EACf,kBAAkB,EAClB,eAAe,EACf,OAAO,EACR,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAGxC,cAAc,OAAO,CAAC;AAGtB,cAAc,SAAS,CAAC;AAGxB,eAAO,MAAM,SAAS;;CAErB,CAAC;AAGF,wBAAgB,WAAW,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,SAAS,MAAM,GAAG,MAAM,EAC5D,OAAO,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,EACtB,MAAM,CAAC,EAAE,kBAAkB,GAC1B,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAEpB;;;;;;;;;;;AAGD,wBAQE"}
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.functions = void 0;
18
+ exports.createStore = createStore;
19
+ const redux_1 = require("redux");
20
+ const redux_cluster_1 = require("./core/redux-cluster");
21
+ const crypto_1 = require("./utils/crypto");
22
+ // Re-export Redux functions and types
23
+ __exportStar(require("redux"), exports);
24
+ // Export our types
25
+ __exportStar(require("./types"), exports);
26
+ // Export utility functions
27
+ exports.functions = {
28
+ hasher: crypto_1.hasher,
29
+ };
30
+ // Main function to create a ReduxCluster store
31
+ function createStore(reducer, config) {
32
+ return new redux_cluster_1.ReduxCluster(reducer, config);
33
+ }
34
+ // Default export for CommonJS compatibility
35
+ exports.default = {
36
+ createStore,
37
+ functions: exports.functions,
38
+ // Re-export Redux
39
+ combineReducers: redux_1.combineReducers,
40
+ bindActionCreators: redux_1.bindActionCreators,
41
+ applyMiddleware: redux_1.applyMiddleware,
42
+ compose: redux_1.compose,
43
+ };