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.
- package/LICENSE +21 -21
- package/README.md +345 -471
- package/dist/cjs/core/backup.d.ts +10 -0
- package/dist/cjs/core/backup.d.ts.map +1 -0
- package/dist/cjs/core/backup.js +166 -0
- package/dist/cjs/core/redux-cluster.d.ts +47 -0
- package/dist/cjs/core/redux-cluster.d.ts.map +1 -0
- package/dist/cjs/core/redux-cluster.js +367 -0
- package/dist/cjs/index.d.ts +22 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +43 -0
- package/dist/cjs/network/client.d.ts +23 -0
- package/dist/cjs/network/client.d.ts.map +1 -0
- package/dist/cjs/network/client.js +251 -0
- package/dist/cjs/network/server.d.ts +39 -0
- package/dist/cjs/network/server.d.ts.map +1 -0
- package/dist/cjs/network/server.js +439 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/types/index.d.ts +125 -0
- package/dist/cjs/types/index.d.ts.map +1 -0
- package/dist/cjs/types/index.js +20 -0
- package/dist/cjs/utils/crypto.d.ts +22 -0
- package/dist/cjs/utils/crypto.d.ts.map +1 -0
- package/dist/cjs/utils/crypto.js +404 -0
- package/dist/esm/core/backup.d.ts +10 -0
- package/dist/esm/core/backup.d.ts.map +1 -0
- package/dist/esm/core/backup.js +134 -0
- package/dist/esm/core/redux-cluster.d.ts +47 -0
- package/dist/esm/core/redux-cluster.d.ts.map +1 -0
- package/dist/esm/core/redux-cluster.js +376 -0
- package/dist/esm/index.d.ts +22 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +25 -0
- package/dist/esm/network/client.d.ts +23 -0
- package/dist/esm/network/client.d.ts.map +1 -0
- package/dist/esm/network/client.js +221 -0
- package/dist/esm/network/server.d.ts +39 -0
- package/dist/esm/network/server.d.ts.map +1 -0
- package/dist/esm/network/server.js +408 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/types/index.d.ts +125 -0
- package/dist/esm/types/index.d.ts.map +1 -0
- package/dist/esm/types/index.js +17 -0
- package/dist/esm/utils/crypto.d.ts +22 -0
- package/dist/esm/utils/crypto.d.ts.map +1 -0
- package/dist/esm/utils/crypto.js +351 -0
- package/package.json +115 -42
- package/index.js +0 -678
- package/test.auto.js +0 -94
- package/test.auto.proc1.js +0 -97
- package/test.auto.proc2.js +0 -85
- package/test.visual.client.highload.js +0 -102
- package/test.visual.client.js +0 -103
- package/test.visual.error.js +0 -45
- package/test.visual.js +0 -97
- package/test.visual.server.highload.js +0 -102
- 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
|
+
};
|