@persistica/flux-mesh 0.0.2 → 0.0.3

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.
@@ -1,135 +0,0 @@
1
- /**
2
- * Register information on a specific network agent.
3
- */
4
- import type { RedisClient } from 'bun';
5
- import type {
6
- TAddress,
7
- TClientId,
8
- TAgentOwnUId,
9
- TNetworkId_S,
10
- } from '@flux/shared/types';
11
- import { NetworkAgentRedisSortedSet } from './hash/network-agent.redis.sorted-set';
12
-
13
- export class NetworkAgentRedis {
14
- private readonly networkAgentRedisSortedSet: NetworkAgentRedisSortedSet;
15
- private readonly cashedDataUsage: Map<string, number> = new Map();
16
-
17
- constructor(
18
- private readonly client: RedisClient,
19
- ) {
20
- this.networkAgentRedisSortedSet = new NetworkAgentRedisSortedSet(client);
21
-
22
- // Update the network agent data usage regularly
23
- setInterval(this.pushDataUsage.bind(this), 3_000);
24
- }
25
-
26
- /**
27
- * Registers a network agent in the sorted set.
28
- *
29
- * @param { TNetworkId_S } networkId
30
- * @param { TClientId } socketId
31
- *
32
- * @returns { Promise<void> }
33
- */
34
- public async registerNetworkAgent(
35
- networkId: TNetworkId_S,
36
- clientId: TAddress,
37
- socketId: TClientId,
38
- uid: TAgentOwnUId,
39
- ): Promise<void> {
40
- const key: string = `networks/${networkId}/agents/${socketId}`;
41
-
42
- await this.client.hmset(key, [
43
- 'data-usage',
44
- '0',
45
-
46
- 'clientId',
47
- clientId,
48
-
49
- 'name',
50
- uid,
51
-
52
- 'registerAt',
53
- new Date().toISOString(),
54
- ]);
55
-
56
- await this.client.expire(key, 500);
57
-
58
- await this.networkAgentRedisSortedSet
59
- .registerAgent(
60
- networkId,
61
- socketId,
62
- );
63
- }
64
-
65
- /**
66
- * Unregisters a network agent from the sorted set.
67
- *
68
- * @param { TNetworkId_S } networkId
69
- * @param { TClientId } socketId
70
- *
71
- * @returns { Promise<number> }
72
- */
73
- public async unregisterNetworkAgent(
74
- networkId: TNetworkId_S,
75
- socketId: TClientId,
76
- ): Promise<number> {
77
- const key: string = `networks/${networkId}/agents/${socketId}`;
78
-
79
- await this.client.hmset(key, [
80
- 'unregisteredAt',
81
- new Date().toISOString(),
82
- ]);
83
-
84
- return await this.networkAgentRedisSortedSet
85
- .unregisterAgent(
86
- networkId,
87
- socketId,
88
- );
89
- }
90
-
91
- /**
92
- *
93
- * @param socketId
94
- * @param usage
95
- *
96
- * @returns { void }
97
- */
98
- public registerDataUsage(
99
- networkId: TNetworkId_S,
100
- socketId: TClientId,
101
- usage: number,
102
- ): void {
103
- const key: string = `networks/${networkId}/agents/${socketId}`;
104
-
105
- const cashedDataUsage: number | undefined = this.cashedDataUsage.get(socketId);
106
-
107
- if (cashedDataUsage === undefined) {
108
- this.cashedDataUsage.set(key, usage);
109
- } else {
110
- this.cashedDataUsage.set(key, usage + cashedDataUsage);
111
- }
112
- }
113
-
114
- /**
115
- * Pushes the data usage to the Redis server.
116
- *
117
- * @returns { Promise<void> }
118
- */
119
- private async pushDataUsage(
120
-
121
- ): Promise<void> {
122
-
123
- for (const [redisKey, usage] of this.cashedDataUsage) {
124
- await this.client
125
- .hincrby(
126
- redisKey,
127
- 'data-usage',
128
- usage,
129
- );
130
-
131
- this.cashedDataUsage.delete(redisKey);
132
- }
133
-
134
- }
135
- }
@@ -1,261 +0,0 @@
1
- import {
2
- type TRedisEventChannel,
3
- BunRedisClient,
4
- BunRedisPubSub,
5
- } from '@core/redis/bun';
6
- import type { TAddress, TClientId, TProcessAddress } from '@flux/shared/types';
7
- import { NetworkAuthorityRedisSortedSet } from '@flux/mesh/store/redis/network-authority';
8
- import { NetworkAgentRedisService } from '@flux/mesh/store/redis/network-agent';
9
- import type { TGlobalChannel } from '../global-channel/global-channel-pubsub.class';
10
-
11
- let redisConnection: RedisConnection | undefined;
12
-
13
- if (!process.env['FLUX_MESH_REDIS_URL']) {
14
- throw new Error('Missing FLUX_MESH_REDIS_URL in .env');
15
- }
16
-
17
- /**
18
- * Singleton function to get the Redis connection
19
- *
20
- * @returns
21
- */
22
- export const getMeshRedisConnection = (
23
- connectionString?: string,
24
- ) => {
25
- // We have to read the env variable here, because otherwise it can't be modified in tests
26
- redisConnection ??= new RedisConnection(process.env['FLUX_MESH_REDIS_URL'] as string);
27
-
28
- return redisConnection;
29
- };
30
-
31
- export type MessageCallback = (message: string, channel?: string) => unknown;
32
-
33
- export class RedisConnection {
34
- // Dedicated client for handling hash
35
- private readonly cacheClient: BunRedisClient;
36
-
37
- // Dedicated clients for handling pub/sub
38
- private readonly pubSub: BunRedisPubSub;
39
-
40
- public readonly networkAuthoritySet: NetworkAuthorityRedisSortedSet;
41
- public readonly networkAgentRedisService: NetworkAgentRedisService;
42
-
43
- // Wrapper to not expose BunRedisClient functions
44
- public readonly hash;
45
-
46
- constructor(
47
- private readonly url: string,
48
- ) {
49
- this.cacheClient = new BunRedisClient({
50
- url: this.url,
51
- socket: {
52
- reconnectStrategy: (
53
- retries: number,
54
- ) => {
55
- // if (retries > 10) {
56
- // console.error('❌ Redis reconnect failed after 10 attempts.');
57
- // return new Error('Redis reconnect limit reached');
58
- // }
59
-
60
- console.warn(`🔄 Redis reconnection attempt #${retries}`);
61
-
62
- // Backoff in ms
63
- return Math.min(retries * 100, 3_000);
64
- },
65
- },
66
- });
67
-
68
- try {
69
- this.cacheClient.connect();
70
- } catch {
71
- console.log('caught');
72
- }
73
-
74
- this.cacheClient
75
- .on('error', (error) => {
76
- console.error('❌ Redis client error:', error.message);
77
-
78
- // Socket closed unexpectedly
79
- // Failed to connect
80
- })
81
- .on('reconnecting', () => {
82
- console.log('🔄 Redis reconnecting...');
83
- })
84
- .on('ready', () => {
85
- console.log('✅ Redis client ready');
86
- })
87
- .on('end', () => {
88
- console.warn('🚫 Redis connection closed');
89
- });
90
-
91
- this.networkAuthoritySet = new NetworkAuthorityRedisSortedSet(this.cacheClient.getClient());
92
- this.networkAgentRedisService = new NetworkAgentRedisService(this.cacheClient.getClient());
93
-
94
- // *** Create Redis subscriber
95
- this.pubSub = new BunRedisPubSub(
96
- {
97
- url: this.url,
98
- socket: {
99
- reconnectStrategy: (
100
- retries: number,
101
- ) => {
102
- console.warn(`🔄 Redis reconnection attempt #${retries}`);
103
-
104
- // Backoff in ms
105
- return Math.min(retries * 100, 3_000);
106
- },
107
- },
108
- });
109
-
110
- this.pubSub
111
- .connect()
112
- .catch(() => console.log('pubsub connection failed'));
113
-
114
- // Dedicated client
115
- const hashClient = this.cacheClient.getClient();
116
-
117
- this.hash = {
118
- sadd: hashClient.sadd.bind(hashClient),
119
- hmset: hashClient.hmset.bind(hashClient),
120
- smembers: hashClient.smembers.bind(hashClient),
121
- srem: hashClient.srem.bind(hashClient),
122
- scard: hashClient.scard.bind(hashClient),
123
- del: hashClient.del.bind(hashClient),
124
- hincrby: hashClient.hincrby.bind(hashClient),
125
- hmget: hashClient.hmget.bind(hashClient),
126
- expire: hashClient.expire.bind(hashClient),
127
- };
128
- }
129
-
130
- // ****************************************************************************
131
- // *** Publish To All Channels
132
- // ****************************************************************************
133
-
134
- /**
135
- *
136
- * @param { TProcessAddress } skipProcessAddress
137
- * @param { TGlobalChannel } channel
138
- * @param { string } message
139
- *
140
- * @returns { Promise<void> }
141
- */
142
- public async publishWebsocketChannelEvent(
143
- skipProcessAddress: TProcessAddress,
144
- channel: TGlobalChannel,
145
- message: string,
146
- ): Promise<void> {
147
- try {
148
- await this.pubSub.publish(`~${channel}`, `${skipProcessAddress}:${message}`);
149
- } catch {
150
- console.error(`Publish failed on global channel event: '${channel}'`);
151
- }
152
- }
153
-
154
- /**
155
- *
156
- * @returns { void }
157
- */
158
- public async subscribeWebsocketChannelEvent(
159
- callback: (
160
- channel: TGlobalChannel,
161
- skipProcessAddress: TProcessAddress,
162
- message: string,
163
- ) => unknown,
164
- ): Promise<void> {
165
- try {
166
- await this.pubSub.subscribe('~*', (
167
- message: string,
168
- preFixedChannel: TRedisEventChannel,
169
- ) => {
170
- const index = message.indexOf(':');
171
-
172
- const [skipProcessAddress, message_] = index !== -1
173
- ? [message.slice(0, index), message.slice(index + 1)]
174
- : [message, ''];
175
-
176
- callback(
177
- preFixedChannel.slice(1) as TGlobalChannel,
178
- skipProcessAddress as TProcessAddress,
179
- message_,
180
- );
181
- });
182
- } catch {
183
- console.log('Error caught while subscribing to websocket channel event globally');
184
- }
185
- }
186
-
187
- // ****************************************************************************
188
- // *** Publish Directly to Address
189
- // ****************************************************************************
190
-
191
- /**
192
- *
193
- * @param { TAddress | TProcessAddress } address
194
- * @param { string } message
195
- *
196
- * @returns { Promise<void> }
197
- */
198
- public async directPublish(
199
- address: TAddress | TProcessAddress,
200
- message: string,
201
- ): Promise<void> {
202
- try {
203
- await this.pubSub.publish(address, message);
204
- } catch {
205
- console.error(`Publish failed on address: ${address}`);
206
- }
207
- }
208
-
209
- public subscribe(
210
- channelId: TProcessAddress | TClientId,
211
- callback: MessageCallback
212
- ): void {
213
- try {
214
- const redisCallback = (message: string) => callback(message);
215
-
216
- this.pubSub.subscribe(channelId, redisCallback);
217
- } catch {
218
- console.log('error caught #2');
219
- }
220
- }
221
-
222
- public unsubscribe(
223
- channelId: string,
224
- callback: MessageCallback,
225
- ): void {
226
- try {
227
- this.pubSub.unsubscribe(channelId, callback);
228
- } catch {
229
- console.log('error caught #1');
230
- }
231
- }
232
-
233
- public async setConnected(
234
- address: string,
235
- ): Promise<void> {
236
- await this.hash.hmset(`machines/processes/${address}`, [
237
- 'status', 'connected',
238
- 'updatedAt', new Date().toISOString(),
239
- ]);
240
-
241
- await this.hash.expire(`machines/processes/${address}`, 5);
242
- }
243
-
244
- public async setDisconnected(
245
- _address: string,
246
- ): Promise<void> {
247
- // !TODO
248
- }
249
-
250
- /**
251
- * Disconnects the Redis cache and pub/sub.
252
- *
253
- * @returns { void }
254
- */
255
- public disconnect(
256
-
257
- ): void {
258
- this.cacheClient.disconnect();
259
- this.pubSub.disconnect();
260
- }
261
- }
@@ -1,215 +0,0 @@
1
- /**
2
- * The class to handling the *calling* side of the RPC.
3
- */
4
-
5
- import {
6
- type TAddress,
7
- type TProcessAddress,
8
- GlobalRPCTimeoutError,
9
- RPC_REQUEST,
10
- } from '@flux/shared/types';
11
- import type {
12
- TCallback,
13
- RPCRequest,
14
- RPCResponse,
15
- } from '@flux/shared/ws';
16
- import type { OutgoingMessageRouter } from '../../outgoing-message-router.class';
17
- import type { ProcessMessageRouter } from '../../process-message-router.class';
18
- import { readProcessAddress } from '../../addressing.utils';
19
-
20
- export class GlobalRPCClient<TMethods> {
21
- // TODO OD IS USED TO ROUTE THE MESSAGE; SO MAKE GLOBALLY UNIQUE IN THE FUTURE!!!
22
- private requestId: number = 0; // Math.floor(Math.random() * 100_000);
23
-
24
- // Success and error callbacks for each request.
25
- private readonly pendingRequests: Map<number, [TCallback, TCallback]> =
26
- new Map();
27
-
28
- private readonly processAddress: TProcessAddress = readProcessAddress();
29
-
30
- constructor(
31
- private readonly _outgoingMessageRouter: OutgoingMessageRouter,
32
- private readonly _processMessageRouter: ProcessMessageRouter,
33
- ) {
34
- this._processMessageRouter.subscribe((message: string): void => {
35
- const message_: RPCResponse = JSON.parse(
36
- message.substring(message.indexOf(':') + 1)
37
- ) as RPCResponse;
38
-
39
- this.handleResponseMessage(message_);
40
- });
41
- }
42
-
43
- /**
44
- * Calls a function on the other side.
45
- *
46
- * @param { TAddress } rpcServerClientAddress
47
- * @param { TMethods } method
48
- *
49
- * @returns { Promise<any> }
50
- */
51
- public call(
52
- rpcServerClientAddress: TAddress,
53
- method: TMethods,
54
- ...params: any
55
- ): Promise<any> {
56
- const id: number = this.requestId++;
57
-
58
- const rpcRequest: RPCRequest<TMethods> = {
59
- id,
60
- originProcessAddress: this.processAddress,
61
- method,
62
- params,
63
- };
64
-
65
- return new Promise((resolve, reject) => {
66
- this.pendingRequests.set(id, [resolve, reject]);
67
-
68
- try {
69
- this._outgoingMessageRouter.message(
70
- rpcServerClientAddress,
71
- `${RPC_REQUEST}:${JSON.stringify(rpcRequest)}`
72
- );
73
- } catch (error) {
74
- if (this.pendingRequests.get(id)) {
75
- this.pendingRequests.delete(id);
76
- reject(error instanceof Error ? error : new Error(error as string));
77
- }
78
- }
79
-
80
- setTimeout(() => {
81
- if (this.pendingRequests.get(id)) {
82
- this.pendingRequests.delete(id);
83
- reject(new GlobalRPCTimeoutError(method as string));
84
- }
85
- }, 5_000);
86
- });
87
- }
88
-
89
- /**
90
- * Handles the response from the server.
91
- *
92
- * @param { RPCResponse } response
93
- *
94
- * @returns { boolean }
95
- */
96
- private handleResponseMessage(
97
- response: RPCResponse,
98
- ): void {
99
- const cb: [TCallback, TCallback] | undefined = this.pendingRequests.get(
100
- response.id
101
- );
102
-
103
- if (cb) {
104
- this.pendingRequests.delete(response.id);
105
-
106
- if (response.error) {
107
- cb[1](response.error);
108
-
109
- return;
110
- // throw new Error(response.error);
111
- }
112
-
113
- cb[0](response.result);
114
- }
115
- }
116
- }
117
-
118
- export class GlobalRPCClient2<TMethods> {
119
- // TODOOD IS USED TO ROUTE THE MESSAGE; SO MAKE GLOBALLY UNIQWUE IN THE FUTURE!!!
120
- private requestId: number = 0; // Math.floor(Math.random() * 100_000);
121
-
122
- // Success and error callbacks for each request.
123
- private readonly pendingRequests: Map<number, [TCallback, TCallback]> =
124
- new Map();
125
-
126
- private readonly processAddress: TProcessAddress = readProcessAddress();
127
-
128
- constructor(
129
- private readonly _rpcServerClientAddress: TAddress,
130
- private readonly _outgoingMessageRouter: OutgoingMessageRouter,
131
- private readonly _processMessageRouter: ProcessMessageRouter
132
- ) {
133
- this._processMessageRouter.subscribe((message: string): void => {
134
- const message_: RPCResponse = JSON.parse(
135
- message.substring(message.indexOf(':') + 1)
136
- ) as RPCResponse;
137
-
138
- this.handleResponseMessage(message_);
139
- });
140
- }
141
-
142
- /**
143
- * Calls a function on the other side.
144
- *
145
- * @param { TAddress } rpcServerClientAddress
146
- * @param { TMethods } method
147
- *
148
- * @returns { Promise<any> }
149
- */
150
- public call(
151
- method: TMethods,
152
- ...params: any
153
- ): Promise<any> {
154
- const id: number = this.requestId++;
155
-
156
- const rpcRequest: RPCRequest<TMethods> = {
157
- id,
158
- originProcessAddress: this.processAddress,
159
- method,
160
- params,
161
- };
162
-
163
- return new Promise((resolve, reject) => {
164
-
165
- this.pendingRequests.set(id, [resolve, reject]);
166
-
167
- try {
168
- this._outgoingMessageRouter.message(
169
- this._rpcServerClientAddress,
170
- `${RPC_REQUEST}:${JSON.stringify(rpcRequest)}`
171
- );
172
- } catch (error) {
173
- if (this.pendingRequests.get(id)) {
174
- this.pendingRequests.delete(id);
175
- reject(error instanceof Error ? error : new Error(error as string));
176
- }
177
- }
178
-
179
- setTimeout(() => {
180
- if (this.pendingRequests.get(id)) {
181
- this.pendingRequests.delete(id);
182
- reject(new GlobalRPCTimeoutError(method as string));
183
- }
184
- }, 5_000);
185
- });
186
- }
187
-
188
- /**
189
- * Handles the response from the server.
190
- *
191
- * @param { RPCResponse } response
192
- *
193
- * @returns { boolean }
194
- */
195
- private handleResponseMessage(
196
- response: RPCResponse,
197
- ): void {
198
- const cb: [TCallback, TCallback] | undefined = this.pendingRequests.get(
199
- response.id
200
- );
201
-
202
- if (cb) {
203
- this.pendingRequests.delete(response.id);
204
-
205
- if (response.error) {
206
- cb[1](response.error);
207
-
208
- return;
209
- // throw new Error(response.error);
210
- }
211
-
212
- cb[0](response.result);
213
- }
214
- }
215
- }
package/src/test.spec.ts DELETED
@@ -1,7 +0,0 @@
1
- import { describe, it, expect } from 'bun:test';
2
-
3
- describe('should have a test', () => {
4
- it('should be true', () => {
5
- expect(true).toBe(true);
6
- });
7
- });
package/tsconfig.json DELETED
@@ -1,23 +0,0 @@
1
- {
2
- "extends": "../../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "module": "NodeNext",
5
- "forceConsistentCasingInFileNames": true,
6
- "strict": true,
7
- "importHelpers": true,
8
- "noImplicitOverride": true,
9
- "noImplicitReturns": true,
10
- "noFallthroughCasesInSwitch": true,
11
- "noPropertyAccessFromIndexSignature": true,
12
- "lib": [
13
- "es2021"
14
- ]
15
- },
16
- "files": [],
17
- "include": [],
18
- "references": [
19
- {
20
- "path": "./tsconfig.lib.json"
21
- }
22
- ]
23
- }
package/tsconfig.lib.json DELETED
@@ -1,20 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "../../../dist/out-tsc",
5
- "declaration": true,
6
- "types": [
7
- "node"
8
- ],
9
- "target": "ESNext",
10
- "moduleResolution": "bundler",
11
- "module": "EsNext"
12
- },
13
- "include": [
14
- "src/**/*.ts"
15
- ],
16
- "exclude": [
17
- "src/**/*.spec.ts",
18
- "src/**/*.test.ts"
19
- ]
20
- }