dolphin-server-modules 1.1.0 → 1.2.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/DOLPHIN_MASTER_GUIDE_NEPALI.md +71 -1
- package/README.md +16 -3
- package/TUTORIAL_NEPALI.md +21 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/realtime/codec.d.ts +18 -0
- package/dist/realtime/codec.js +65 -0
- package/dist/realtime/codec.js.map +1 -0
- package/dist/realtime/core.d.ts +63 -0
- package/dist/realtime/core.js +200 -0
- package/dist/realtime/core.js.map +1 -0
- package/dist/realtime/index.d.ts +4 -0
- package/dist/realtime/index.js +21 -0
- package/dist/realtime/index.js.map +1 -0
- package/dist/realtime/plugins.d.ts +34 -0
- package/dist/realtime/plugins.js +47 -0
- package/dist/realtime/plugins.js.map +1 -0
- package/dist/realtime/trie.d.ts +22 -0
- package/dist/realtime/trie.js +76 -0
- package/dist/realtime/trie.js.map +1 -0
- package/package.json +12 -3
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
- [११. स्केलिङ र पर्फर्मेन्स (Scaling & Performance)](#११-स्केलिङ-र-पर्फर्मेन्स-scaling--performance)
|
|
20
20
|
- [१२. टेस्टिङ र डेभप्स (Testing & DevOps)](#१२-टेस्टिङ-र-डेभप्स-testing--devops)
|
|
21
21
|
- [१३. भविष्य र योगदान (Future Roadmap)](#१३-भविष्य-र-योगदान-future-roadmap)
|
|
22
|
+
- [१४. रियलटाइम र IoT मास्टरक्लास (Realtime & IoT Masterclass) [NEW]](#१४-रियलटाइम-र-iot-मास्टरक्लास-realtime--iot-masterclass-new)
|
|
22
23
|
|
|
23
24
|
---
|
|
24
25
|
|
|
@@ -282,6 +283,30 @@ app.post('/auth/2fa/setup', auth.middleware(), async (ctx) => {
|
|
|
282
283
|
const result = await auth.setup2FA(ctx.req.user.id);
|
|
283
284
|
ctx.json(result); // QR Code URL यहाँ आउँछ
|
|
284
285
|
});
|
|
286
|
+
|
|
287
|
+
### ७.५ कस्टम कन्ट्रोलर (Custom Controllers)
|
|
288
|
+
यदि तपाईँलाई अटोमेटेड CRUD ले पुग्दैन भने, तपाईँ आफ्नै कन्ट्रोलर लेख्न सक्नुहुन्छ। Dolphin मा एउटा राम्रो कन्ट्रोलर यस्तो हुनुपर्छ:
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
// src/controllers/userController.ts
|
|
292
|
+
export const userController = {
|
|
293
|
+
getProfile: async (ctx) => {
|
|
294
|
+
const user = ctx.req.user;
|
|
295
|
+
if (!user) return ctx.status(401).json({ error: "Unauthorized" });
|
|
296
|
+
|
|
297
|
+
// केही जटिल लजिक यहाँ...
|
|
298
|
+
ctx.json({
|
|
299
|
+
id: user.id,
|
|
300
|
+
email: user.email,
|
|
301
|
+
serverTime: new Date()
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
```
|
|
306
|
+
यसलाई राउटमा यसरी प्रयोग गर्नुहोस्:
|
|
307
|
+
```typescript
|
|
308
|
+
import { userController } from './controllers/userController';
|
|
309
|
+
app.get('/me', auth.middleware(), userController.getProfile);
|
|
285
310
|
```
|
|
286
311
|
|
|
287
312
|
---
|
|
@@ -484,7 +509,8 @@ CMD ["npm", "start"]
|
|
|
484
509
|
|
|
485
510
|
Dolphin अझै विकसित हुँदैछ। हाम्रो आगामी योजनाहरू:
|
|
486
511
|
- **Dolphin CLI**: एउटा कमान्डले प्रोजेक्ट सेटअप गर्ने।
|
|
487
|
-
- **
|
|
512
|
+
- **Dolphin CLI**: एउटा कमान्डले प्रोजेक्ट सेटअप गर्ने।
|
|
513
|
+
- **Realtime & IoT Integration**: उच्च क्षमताको डाटा इन्जेसनको लागि। [DONE]
|
|
488
514
|
- **Native SQL Adapters**: PostgreSQL र MySQL का लागि विशेष एडाप्टरहरू।
|
|
489
515
|
|
|
490
516
|
### योगदान कसरी गर्ने?
|
|
@@ -492,6 +518,50 @@ Dolphin अझै विकसित हुँदैछ। हाम्रो
|
|
|
492
518
|
|
|
493
519
|
---
|
|
494
520
|
|
|
521
|
+
## १४. रियलटाइम र IoT मास्टरक्लास (Realtime & IoT Masterclass) [NEW]
|
|
522
|
+
|
|
523
|
+
आधुनिक एप्लिकेसनहरूलाई केवल "Request-Response" मात्र पुग्दैन। तिनीहरूलाई "Real-time" डाटा चाहिन्छ। Dolphin को Realtime मोड्युलले यसलाई सम्भव बनाउँछ।
|
|
524
|
+
|
|
525
|
+
### १४.१ रियलटाइम कोर (Realtime Core) के हो?
|
|
526
|
+
यो एउटा इन्टरनल "Event Bus" हो जसले मेसेजहरूलाई एक ठाउँबाट अर्को ठाउँमा तुरुन्तै पुर्याउँछ। यसले MQTT जस्तो "Topic-based" सिस्टम प्रयोग गर्छ।
|
|
527
|
+
|
|
528
|
+
### १४.२ मुख्य अवधारणाहरू (Key Concepts)
|
|
529
|
+
१. **TopicTrie**: मेसेजहरू कुन टपिकमा जाने भनेर छिटो पत्ता लगाउने इन्जिन।
|
|
530
|
+
२. **Binary Codec**: डाटालाई सानो बनाउन बाइनरी फर्म्याटमा लैजाने सफ्टवेयर।
|
|
531
|
+
३. **Plugins**: विभिन्न प्रोटोकलहरू (HL7, Modbus) सपोर्ट गर्न।
|
|
532
|
+
|
|
533
|
+
### १४.३ कोड उदाहरण: बेसिक पब-सब (Pub/Sub)
|
|
534
|
+
```typescript
|
|
535
|
+
import { RealtimeCore } from 'dolphin-server-modules/realtime';
|
|
536
|
+
|
|
537
|
+
const rt = new RealtimeCore({
|
|
538
|
+
maxMessageSize: 512 * 1024 // ५१२ KB म्याक्स साइज
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
// १. सब्सक्राइब (Subscribe) गर्ने
|
|
542
|
+
rt.subscribe('sensors/temperature/+', (ctx) => {
|
|
543
|
+
console.log(`नयाँ डाटा आयो: ${ctx.payload.value}°C`);
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
// २. पब्लिस (Publish) गर्ने
|
|
547
|
+
rt.publish('sensors/temperature/room1', { value: 22.5 });
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
### १४.४ वाइल्डकार्डको शक्ति (Power of Wildcards)
|
|
551
|
+
- `sensors/+`: `sensors/temp` र `sensors/hum` दुवै म्याच गर्छ।
|
|
552
|
+
- `sensors/#`: `sensors/a/b/c` जति पनि गहिराइ (depth) सम्म म्याच गर्छ।
|
|
553
|
+
|
|
554
|
+
### १४.५ रेडिस स्केलिङ (Redis Scaling)
|
|
555
|
+
यदि तपाईँको धेरै वटा सर्भरहरू छन् भने, तिनीहरूलाई रेडिस मार्फत जोड्न सक्नुहुन्छ:
|
|
556
|
+
```typescript
|
|
557
|
+
const rt = new RealtimeCore({
|
|
558
|
+
redisUrl: 'redis://localhost:6379'
|
|
559
|
+
});
|
|
560
|
+
```
|
|
561
|
+
यसो गर्दा एउटा सर्भरबाट पठाएको मेसेज अर्को सर्भरमा बस्ने युजरले पनि तुरुन्तै पाउँछ।
|
|
562
|
+
|
|
563
|
+
---
|
|
564
|
+
|
|
495
565
|
## निष्कर्ष (Conclusion)
|
|
496
566
|
|
|
497
567
|
बधाई छ! तपाईँले Dolphin Framework को **Master Guide** को अन्त्य सम्म पढ्नुभयो। अब तपाईँ कुनै पनि जटिल ब्याकइन्ड सिस्टम Dolphin प्रयोग गरेर बनाउन पूर्ण सक्षम हुनुहुन्छ।
|
package/README.md
CHANGED
|
@@ -6,6 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
### 📘 Official Master Guide (Nepal)
|
|
10
|
+
Dolphin Framework को विस्तृत र आधिकारिक गाइड अब उपलब्ध छ। यसमा **Auth, CRUD, Models, र Controllers** को १००% ट्युटोरियल समावेश छ।
|
|
11
|
+
|
|
12
|
+
👉 **[Dolphin Master Guide (PDF)](https://github.com/Phuyalshankar/dolphin-server-modules/blob/main/DOLPHIN_MASTER_GUIDE_NEPALI.pdf)**
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
9
16
|
## 🚀 Core Philosophy
|
|
10
17
|
- **Zero-Dependency Core**: Built on the native Node.js `http` module. No Express, no Fastify overhead.
|
|
11
18
|
- **Extreme Modularity**: Use only what you need. Auth, CRUD, and Routing are all independent.
|
|
@@ -108,13 +115,19 @@ Seamlessly switch between databases with the Adapter pattern.
|
|
|
108
115
|
### ✅ 5. Zod-Powered Validation (`/middleware/zod`)
|
|
109
116
|
Validate payloads and params with 100% type inference.
|
|
110
117
|
|
|
118
|
+
### 🌐 6. Realtime & IoT Core (`/realtime`) [NEW]
|
|
119
|
+
High-performance pub/sub with MQTT-style matching.
|
|
120
|
+
- **TopicTrie**: $O(1)$ pattern matching for `+` and `#`.
|
|
121
|
+
- **Binary Codec**: Ultra-lightweight ingestion for IoT devices.
|
|
122
|
+
- **Redis Scaling**: Seamlessly scale across multiple instances.
|
|
123
|
+
|
|
111
124
|
---
|
|
112
125
|
|
|
113
126
|
## 🗺️ Roadmap & Future Vision
|
|
114
127
|
1. **`defineModel` Engine**: Define a schema once, auto-generate CRUD, validation, and types.
|
|
115
|
-
2. **Plugin System**: A robust "hook" based system for extending the framework core.
|
|
116
|
-
3. **
|
|
117
|
-
4. **
|
|
128
|
+
2. **Plugin System**: A robust "hook" based system for extending the framework core. [DONE]
|
|
129
|
+
3. **CLI Presets**: `npx dolphin init` for instant project scaffolding.
|
|
130
|
+
4. **Adaptive Load Balancing**: Native cluster support for multi-core CPUs.
|
|
118
131
|
|
|
119
132
|
---
|
|
120
133
|
|
package/TUTORIAL_NEPALI.md
CHANGED
|
@@ -180,7 +180,27 @@ app.post('/register', validate(registerSchema), (ctx) => {
|
|
|
180
180
|
|
|
181
181
|
---
|
|
182
182
|
|
|
183
|
-
## ११.
|
|
183
|
+
## ११. रियलटाइम र IoT (Realtime & IoT Core) [NEW]
|
|
184
|
+
Dolphin ले अब उच्च क्षमताको रियलटाइम कम्युनिकेसन सपोर्ट गर्छ।
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
import { RealtimeCore, JSONPlugin } from 'dolphin-server-modules/realtime';
|
|
188
|
+
|
|
189
|
+
const rt = new RealtimeCore();
|
|
190
|
+
rt.use(JSONPlugin);
|
|
191
|
+
|
|
192
|
+
// टपिकहरूमा सब्सक्राइब (Subscribe) गर्नुहोस्
|
|
193
|
+
rt.subscribe('sensors/+', (ctx) => {
|
|
194
|
+
console.log(`टपिक: ${ctx.topic}, डाटा:`, ctx.payload);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// पब्लिस (Publish) गर्नुहोस्
|
|
198
|
+
rt.publish('sensors/temp', { value: 24.5 });
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## १२. अन्तिममा (Conclusion)
|
|
184
204
|
|
|
185
205
|
Dolphin Framework निकै छिटो र सजिलो छ। यसले तपाईँको ब्याकइन्ड डेभलपमेन्टको अनुभवलाई नयाँ उचाइमा पुर्याउँछ।
|
|
186
206
|
|
package/dist/index.d.ts
CHANGED
|
@@ -3,3 +3,4 @@ export * from './controller/controller';
|
|
|
3
3
|
export { createCRUD, BaseDocument, QueryFilter, PaginationOptions, DatabaseAdapter as CrudDatabaseAdapter } from './curd/crud';
|
|
4
4
|
export * from './server/server';
|
|
5
5
|
export * from './router/router';
|
|
6
|
+
export * from './realtime/index';
|
package/dist/index.js
CHANGED
|
@@ -25,4 +25,6 @@ Object.defineProperty(exports, "createCRUD", { enumerable: true, get: function (
|
|
|
25
25
|
// Re-export Server & Router
|
|
26
26
|
__exportStar(require("./server/server"), exports);
|
|
27
27
|
__exportStar(require("./router/router"), exports);
|
|
28
|
+
// Re-export Realtime
|
|
29
|
+
__exportStar(require("./realtime/index"), exports);
|
|
28
30
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,iBAAiB;AACjB,8CAA4B;AAC5B,uBAAuB;AACvB,0DAAwC;AACxC,8DAA8D;AAC9D,oCAMqB;AALnB,kGAAA,UAAU,OAAA;AAMZ,4BAA4B;AAC5B,kDAAgC;AAChC,kDAAgC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,iBAAiB;AACjB,8CAA4B;AAC5B,uBAAuB;AACvB,0DAAwC;AACxC,8DAA8D;AAC9D,oCAMqB;AALnB,kGAAA,UAAU,OAAA;AAMZ,4BAA4B;AAC5B,kDAAgC;AAChC,kDAAgC;AAEhC,qBAAqB;AACrB,mDAAiC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight binary/JSON codec for Dolphin Realtime.
|
|
3
|
+
* Optimized for small payloads and cross-platform compatibility.
|
|
4
|
+
*/
|
|
5
|
+
export declare function getSize(data: any): number;
|
|
6
|
+
/**
|
|
7
|
+
* Encode data to Buffer.
|
|
8
|
+
* Types:
|
|
9
|
+
* 1: Int32
|
|
10
|
+
* 2: String
|
|
11
|
+
* 3: JSON
|
|
12
|
+
* Default: Buffer as is
|
|
13
|
+
*/
|
|
14
|
+
export declare function encode(data: any): Buffer;
|
|
15
|
+
/**
|
|
16
|
+
* Decode Buffer to data.
|
|
17
|
+
*/
|
|
18
|
+
export declare function decode(buf: Buffer): any;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getSize = getSize;
|
|
4
|
+
exports.encode = encode;
|
|
5
|
+
exports.decode = decode;
|
|
6
|
+
/**
|
|
7
|
+
* Lightweight binary/JSON codec for Dolphin Realtime.
|
|
8
|
+
* Optimized for small payloads and cross-platform compatibility.
|
|
9
|
+
*/
|
|
10
|
+
function getSize(data) {
|
|
11
|
+
if (Buffer.isBuffer(data))
|
|
12
|
+
return data.length;
|
|
13
|
+
if (typeof data === 'string')
|
|
14
|
+
return Buffer.byteLength(data);
|
|
15
|
+
return Buffer.byteLength(JSON.stringify(data));
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Encode data to Buffer.
|
|
19
|
+
* Types:
|
|
20
|
+
* 1: Int32
|
|
21
|
+
* 2: String
|
|
22
|
+
* 3: JSON
|
|
23
|
+
* Default: Buffer as is
|
|
24
|
+
*/
|
|
25
|
+
function encode(data) {
|
|
26
|
+
if (Buffer.isBuffer(data))
|
|
27
|
+
return data;
|
|
28
|
+
if (typeof data === 'number') {
|
|
29
|
+
const b = Buffer.allocUnsafe(5);
|
|
30
|
+
b[0] = 1;
|
|
31
|
+
b.writeInt32BE(data, 1);
|
|
32
|
+
return b;
|
|
33
|
+
}
|
|
34
|
+
if (typeof data === 'string') {
|
|
35
|
+
const str = Buffer.from(data);
|
|
36
|
+
const len = str.length;
|
|
37
|
+
// Simple header: type(1 byte) + len(2 bytes)
|
|
38
|
+
const b = Buffer.allocUnsafe(3);
|
|
39
|
+
b[0] = 2;
|
|
40
|
+
b.writeUInt16BE(len, 1);
|
|
41
|
+
return Buffer.concat([b, str]);
|
|
42
|
+
}
|
|
43
|
+
const json = Buffer.from(JSON.stringify(data));
|
|
44
|
+
const b = Buffer.allocUnsafe(1);
|
|
45
|
+
b[0] = 3;
|
|
46
|
+
return Buffer.concat([b, json]);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Decode Buffer to data.
|
|
50
|
+
*/
|
|
51
|
+
function decode(buf) {
|
|
52
|
+
if (!buf || buf.length === 0)
|
|
53
|
+
return null;
|
|
54
|
+
const t = buf[0];
|
|
55
|
+
if (t === 1)
|
|
56
|
+
return buf.readInt32BE(1);
|
|
57
|
+
if (t === 2) {
|
|
58
|
+
const len = buf.readUInt16BE(1);
|
|
59
|
+
return buf.slice(3, 3 + len).toString();
|
|
60
|
+
}
|
|
61
|
+
if (t === 3)
|
|
62
|
+
return JSON.parse(buf.slice(1).toString());
|
|
63
|
+
return buf;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=codec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codec.js","sourceRoot":"","sources":["../../realtime/codec.ts"],"names":[],"mappings":";;AAIA,0BAIC;AAUD,wBAwBC;AAKD,wBAUC;AAzDD;;;GAGG;AACH,SAAgB,OAAO,CAAC,IAAS;IAC/B,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,MAAM,CAAC;IAC9C,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IAC7D,OAAO,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;AACjD,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,MAAM,CAAC,IAAS;IAC9B,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAEvC,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,MAAM,CAAC,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;QAChC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACT,CAAC,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACxB,OAAO,CAAC,CAAC;IACX,CAAC;IAED,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9B,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC;QACvB,6CAA6C;QAC7C,MAAM,CAAC,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;QAChC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACT,CAAC,CAAC,aAAa,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACxB,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IACjC,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;IAC/C,MAAM,CAAC,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACT,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;AAClC,CAAC;AAED;;GAEG;AACH,SAAgB,MAAM,CAAC,GAAW;IAChC,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1C,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;IACjB,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IACvC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACZ,MAAM,GAAG,GAAG,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAChC,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC1C,CAAC;IACD,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;IACxD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import { RealtimePlugin } from './plugins';
|
|
3
|
+
/**
|
|
4
|
+
* RealtimeCore - High performance unified pub/sub bus for Dolphin.
|
|
5
|
+
* Supports:
|
|
6
|
+
* - Local event emitter (in-process)
|
|
7
|
+
* - Distributed bus via Redis (optional)
|
|
8
|
+
* - Retained messages with TTL
|
|
9
|
+
* - Device/Client tracking
|
|
10
|
+
* - Plugin-based protocol handling
|
|
11
|
+
*/
|
|
12
|
+
export declare class RealtimeCore extends EventEmitter {
|
|
13
|
+
private config;
|
|
14
|
+
private trie;
|
|
15
|
+
private retained;
|
|
16
|
+
private devices;
|
|
17
|
+
private plugins;
|
|
18
|
+
private pending;
|
|
19
|
+
private msgId;
|
|
20
|
+
private redisPub?;
|
|
21
|
+
private redisSub?;
|
|
22
|
+
constructor(config?: {
|
|
23
|
+
maxMessageSize?: number;
|
|
24
|
+
redisUrl?: string;
|
|
25
|
+
acl?: {
|
|
26
|
+
canSubscribe: (deviceId: string, topic: string) => boolean;
|
|
27
|
+
canPublish: (deviceId: string, topic: string) => boolean;
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
/**
|
|
31
|
+
* Initialize Redis for distributed pub/sub.
|
|
32
|
+
*/
|
|
33
|
+
private initRedis;
|
|
34
|
+
/**
|
|
35
|
+
* Subscribe to a topic pattern.
|
|
36
|
+
*/
|
|
37
|
+
subscribe(topic: string, fn: (data: any) => void, deviceId?: string): void;
|
|
38
|
+
/**
|
|
39
|
+
* Publish a message to a topic.
|
|
40
|
+
*/
|
|
41
|
+
publish(topic: string, payload: any, opts?: {
|
|
42
|
+
retain?: boolean;
|
|
43
|
+
ttl?: number;
|
|
44
|
+
}, deviceId?: string): void;
|
|
45
|
+
private publishInternal;
|
|
46
|
+
/**
|
|
47
|
+
* Handle raw data from a socket.
|
|
48
|
+
*/
|
|
49
|
+
handle(raw: Buffer, socket?: any, deviceId?: string): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Register a plugin.
|
|
52
|
+
*/
|
|
53
|
+
use(plugin: RealtimePlugin): void;
|
|
54
|
+
/**
|
|
55
|
+
* Register a device/client.
|
|
56
|
+
*/
|
|
57
|
+
register(deviceId: string, socket?: any): void;
|
|
58
|
+
/**
|
|
59
|
+
* Heartbeat for a device.
|
|
60
|
+
*/
|
|
61
|
+
touch(deviceId: string): void;
|
|
62
|
+
private startCleanup;
|
|
63
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
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.RealtimeCore = void 0;
|
|
37
|
+
const events_1 = require("events");
|
|
38
|
+
const trie_1 = require("./trie");
|
|
39
|
+
const codec_1 = require("./codec");
|
|
40
|
+
/**
|
|
41
|
+
* RealtimeCore - High performance unified pub/sub bus for Dolphin.
|
|
42
|
+
* Supports:
|
|
43
|
+
* - Local event emitter (in-process)
|
|
44
|
+
* - Distributed bus via Redis (optional)
|
|
45
|
+
* - Retained messages with TTL
|
|
46
|
+
* - Device/Client tracking
|
|
47
|
+
* - Plugin-based protocol handling
|
|
48
|
+
*/
|
|
49
|
+
class RealtimeCore extends events_1.EventEmitter {
|
|
50
|
+
config;
|
|
51
|
+
trie = new trie_1.TopicTrie();
|
|
52
|
+
retained = new Map();
|
|
53
|
+
devices = new Map();
|
|
54
|
+
plugins = new Map();
|
|
55
|
+
pending = new Map();
|
|
56
|
+
msgId = 0;
|
|
57
|
+
redisPub;
|
|
58
|
+
redisSub;
|
|
59
|
+
constructor(config = {}) {
|
|
60
|
+
super();
|
|
61
|
+
this.config = config;
|
|
62
|
+
if (config.redisUrl) {
|
|
63
|
+
this.initRedis(config.redisUrl);
|
|
64
|
+
}
|
|
65
|
+
this.startCleanup();
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Initialize Redis for distributed pub/sub.
|
|
69
|
+
*/
|
|
70
|
+
async initRedis(url) {
|
|
71
|
+
try {
|
|
72
|
+
// @ts-ignore
|
|
73
|
+
const Redis = (await Promise.resolve().then(() => __importStar(require('ioredis')))).default;
|
|
74
|
+
this.redisPub = new Redis(url);
|
|
75
|
+
this.redisSub = new Redis(url);
|
|
76
|
+
this.redisSub.subscribe('dolphin-rt');
|
|
77
|
+
this.redisSub.on('message', (_, msg) => {
|
|
78
|
+
const { topic, payload } = JSON.parse(msg);
|
|
79
|
+
this.publishInternal(topic, payload, { skipRedis: true });
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
console.warn('Redis initialization failed (ioredis not found or connection error):', err);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Subscribe to a topic pattern.
|
|
88
|
+
*/
|
|
89
|
+
subscribe(topic, fn, deviceId) {
|
|
90
|
+
if (deviceId && this.config.acl && !this.config.acl.canSubscribe(deviceId, topic)) {
|
|
91
|
+
throw new Error('ACL deny');
|
|
92
|
+
}
|
|
93
|
+
this.trie.add(topic, fn);
|
|
94
|
+
// Replay retained messages
|
|
95
|
+
for (const [t, data] of this.retained.entries()) {
|
|
96
|
+
// Small hack: if we match the new subscription, replay
|
|
97
|
+
// We can improve this by matching the pattern against the topic
|
|
98
|
+
// For now, only exact match for simplicity or improve TopicTrie
|
|
99
|
+
if (t === topic)
|
|
100
|
+
fn(data.payload);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Publish a message to a topic.
|
|
105
|
+
*/
|
|
106
|
+
publish(topic, payload, opts = {}, deviceId) {
|
|
107
|
+
if ((0, codec_1.getSize)(payload) > (this.config.maxMessageSize || 256 * 1024)) {
|
|
108
|
+
throw new Error('Payload too large');
|
|
109
|
+
}
|
|
110
|
+
if (deviceId && this.config.acl && !this.config.acl.canPublish(deviceId, topic)) {
|
|
111
|
+
throw new Error('ACL deny');
|
|
112
|
+
}
|
|
113
|
+
this.publishInternal(topic, payload, opts);
|
|
114
|
+
}
|
|
115
|
+
publishInternal(topic, payload, opts = {}) {
|
|
116
|
+
if (opts.retain) {
|
|
117
|
+
this.retained.set(topic, { payload, ts: Date.now(), ttl: opts.ttl || 0 });
|
|
118
|
+
}
|
|
119
|
+
// Match and emit locally
|
|
120
|
+
this.trie.match(topic, (fn) => fn({ topic, payload }));
|
|
121
|
+
this.emit('message', { topic, payload });
|
|
122
|
+
// Publish to Redis if available
|
|
123
|
+
if (this.redisPub && !opts.skipRedis) {
|
|
124
|
+
this.redisPub.publish('dolphin-rt', JSON.stringify({ topic, payload }));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Handle raw data from a socket.
|
|
129
|
+
*/
|
|
130
|
+
async handle(raw, socket, deviceId) {
|
|
131
|
+
if (raw.length > (this.config.maxMessageSize || 256 * 1024))
|
|
132
|
+
return;
|
|
133
|
+
// Create initial context
|
|
134
|
+
const ctx = {
|
|
135
|
+
type: 'raw',
|
|
136
|
+
raw,
|
|
137
|
+
socket,
|
|
138
|
+
deviceId,
|
|
139
|
+
ts: Date.now(),
|
|
140
|
+
publish: this.publish.bind(this)
|
|
141
|
+
};
|
|
142
|
+
// Plugin matching and decoding
|
|
143
|
+
for (const p of this.plugins.values()) {
|
|
144
|
+
if (p.match(ctx)) {
|
|
145
|
+
if (p.decode)
|
|
146
|
+
ctx.payload = p.decode(raw);
|
|
147
|
+
p.onMessage?.(ctx);
|
|
148
|
+
// If plugin handled it, we might want to stop or continue.
|
|
149
|
+
// For now, let's continue to allow multiple plugins or default handling.
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// Default handling for 'pub' style packets if no plugin decoded it or just generic
|
|
153
|
+
try {
|
|
154
|
+
const decoded = (0, codec_1.decode)(raw);
|
|
155
|
+
if (decoded && typeof decoded === 'object' && decoded.type === 'pub') {
|
|
156
|
+
this.publish(decoded.topic, decoded.payload, {}, deviceId);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// Ignore decode errors for raw data
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Register a plugin.
|
|
165
|
+
*/
|
|
166
|
+
use(plugin) {
|
|
167
|
+
this.plugins.set(plugin.name, plugin);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Register a device/client.
|
|
171
|
+
*/
|
|
172
|
+
register(deviceId, socket) {
|
|
173
|
+
this.devices.set(deviceId, { lastSeen: Date.now(), socket });
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Heartbeat for a device.
|
|
177
|
+
*/
|
|
178
|
+
touch(deviceId) {
|
|
179
|
+
const d = this.devices.get(deviceId);
|
|
180
|
+
if (d)
|
|
181
|
+
d.lastSeen = Date.now();
|
|
182
|
+
}
|
|
183
|
+
startCleanup() {
|
|
184
|
+
setInterval(() => {
|
|
185
|
+
const now = Date.now();
|
|
186
|
+
// Clean retained messages
|
|
187
|
+
for (const [k, v] of this.retained) {
|
|
188
|
+
if (v.ttl && now - v.ts > v.ttl)
|
|
189
|
+
this.retained.delete(k);
|
|
190
|
+
}
|
|
191
|
+
// Clean inactive devices (1 minute timeout)
|
|
192
|
+
for (const [id, d] of this.devices) {
|
|
193
|
+
if (now - d.lastSeen > 60000)
|
|
194
|
+
this.devices.delete(id);
|
|
195
|
+
}
|
|
196
|
+
}, 5000);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
exports.RealtimeCore = RealtimeCore;
|
|
200
|
+
//# sourceMappingURL=core.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.js","sourceRoot":"","sources":["../../realtime/core.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
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
|
+
__exportStar(require("./core"), exports);
|
|
18
|
+
__exportStar(require("./trie"), exports);
|
|
19
|
+
__exportStar(require("./codec"), exports);
|
|
20
|
+
__exportStar(require("./plugins"), exports);
|
|
21
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../realtime/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,yCAAuB;AACvB,yCAAuB;AACvB,0CAAwB;AACxB,4CAA0B"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Realtime Plugins for Dolphin.
|
|
3
|
+
* Allows handling custom protocols (e.g. Modbus, HL7) seamlessly.
|
|
4
|
+
*/
|
|
5
|
+
export type RealtimeContext = {
|
|
6
|
+
id?: number;
|
|
7
|
+
type: string;
|
|
8
|
+
topic?: string;
|
|
9
|
+
payload?: any;
|
|
10
|
+
raw?: Buffer;
|
|
11
|
+
socket?: any;
|
|
12
|
+
deviceId?: string;
|
|
13
|
+
ts: number;
|
|
14
|
+
publish: (topic: string, payload: any, opts?: any) => void;
|
|
15
|
+
};
|
|
16
|
+
export type RealtimePlugin = {
|
|
17
|
+
name: string;
|
|
18
|
+
match: (ctx: RealtimeContext) => boolean;
|
|
19
|
+
decode?: (buf: Buffer) => any;
|
|
20
|
+
encode?: (data: any) => Buffer;
|
|
21
|
+
onMessage?: (ctx: RealtimeContext) => void;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Sample HL7 Plugin
|
|
25
|
+
*/
|
|
26
|
+
export declare const HL7Plugin: RealtimePlugin;
|
|
27
|
+
/**
|
|
28
|
+
* Sample Modbus Plugin
|
|
29
|
+
*/
|
|
30
|
+
export declare const ModbusPlugin: RealtimePlugin;
|
|
31
|
+
/**
|
|
32
|
+
* Standard JSON Plugin for Web
|
|
33
|
+
*/
|
|
34
|
+
export declare const JSONPlugin: RealtimePlugin;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Realtime Plugins for Dolphin.
|
|
4
|
+
* Allows handling custom protocols (e.g. Modbus, HL7) seamlessly.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.JSONPlugin = exports.ModbusPlugin = exports.HL7Plugin = void 0;
|
|
8
|
+
/**
|
|
9
|
+
* Sample HL7 Plugin
|
|
10
|
+
*/
|
|
11
|
+
exports.HL7Plugin = {
|
|
12
|
+
name: 'hl7',
|
|
13
|
+
match: (ctx) => ctx.raw?.includes?.(0x0b) ?? false,
|
|
14
|
+
decode: (buf) => ({ msg: buf.toString().split('\r') })
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Sample Modbus Plugin
|
|
18
|
+
*/
|
|
19
|
+
exports.ModbusPlugin = {
|
|
20
|
+
name: 'modbus',
|
|
21
|
+
match: (ctx) => ctx.raw?.length === 8,
|
|
22
|
+
decode: (buf) => ({
|
|
23
|
+
addr: buf[0],
|
|
24
|
+
func: buf[1],
|
|
25
|
+
value: buf.readUInt16BE(2)
|
|
26
|
+
})
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Standard JSON Plugin for Web
|
|
30
|
+
*/
|
|
31
|
+
exports.JSONPlugin = {
|
|
32
|
+
name: 'json',
|
|
33
|
+
match: (ctx) => {
|
|
34
|
+
try {
|
|
35
|
+
if (ctx.raw) {
|
|
36
|
+
JSON.parse(ctx.raw.toString());
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
},
|
|
45
|
+
decode: (buf) => JSON.parse(buf.toString())
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=plugins.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugins.js","sourceRoot":"","sources":["../../realtime/plugins.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAsBH;;GAEG;AACU,QAAA,SAAS,GAAmB;IACvC,IAAI,EAAE,KAAK;IACX,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK;IAClD,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;CACvD,CAAC;AAEF;;GAEG;AACU,QAAA,YAAY,GAAmB;IAC1C,IAAI,EAAE,QAAQ;IACd,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,CAAC;IACrC,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAChB,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;QACZ,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;QACZ,KAAK,EAAE,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC;KAC3B,CAAC;CACH,CAAC;AAEF;;GAEG;AACU,QAAA,UAAU,GAAmB;IACxC,IAAI,EAAE,MAAM;IACZ,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE;QACb,IAAI,CAAC;YACH,IAAI,GAAG,CAAC,GAAG,EAAE,CAAC;gBACZ,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;gBAC/B,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;CAC5C,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TopicTrie for high-performance MQTT-style topic matching.
|
|
3
|
+
* Supports:
|
|
4
|
+
* - Simple topics: "sensors/temp"
|
|
5
|
+
* - Single level wildcard: "sensors/+" (matches "sensors/temp", "sensors/hum")
|
|
6
|
+
* - Multi level wildcard: "sensors/#" (matches "sensors/temp", "sensors/room1/temp")
|
|
7
|
+
*/
|
|
8
|
+
export declare class TopicTrie {
|
|
9
|
+
private root;
|
|
10
|
+
/**
|
|
11
|
+
* Add a subscriber function to a topic pattern.
|
|
12
|
+
*/
|
|
13
|
+
add(topic: string, fn: Function): void;
|
|
14
|
+
/**
|
|
15
|
+
* Remove a subscriber function from a topic pattern.
|
|
16
|
+
*/
|
|
17
|
+
remove(topic: string, fn: Function): void;
|
|
18
|
+
/**
|
|
19
|
+
* Match a topic and execute callback for each matching subscriber.
|
|
20
|
+
*/
|
|
21
|
+
match(topic: string, cb: (fn: Function) => void): void;
|
|
22
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TopicTrie = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* TopicTrie for high-performance MQTT-style topic matching.
|
|
6
|
+
* Supports:
|
|
7
|
+
* - Simple topics: "sensors/temp"
|
|
8
|
+
* - Single level wildcard: "sensors/+" (matches "sensors/temp", "sensors/hum")
|
|
9
|
+
* - Multi level wildcard: "sensors/#" (matches "sensors/temp", "sensors/room1/temp")
|
|
10
|
+
*/
|
|
11
|
+
class TopicTrie {
|
|
12
|
+
root = {};
|
|
13
|
+
/**
|
|
14
|
+
* Add a subscriber function to a topic pattern.
|
|
15
|
+
*/
|
|
16
|
+
add(topic, fn) {
|
|
17
|
+
const parts = topic.split('/');
|
|
18
|
+
let node = this.root;
|
|
19
|
+
for (const p of parts) {
|
|
20
|
+
if (!node[p])
|
|
21
|
+
node[p] = {};
|
|
22
|
+
node = node[p];
|
|
23
|
+
}
|
|
24
|
+
if (!node._)
|
|
25
|
+
node._ = [];
|
|
26
|
+
node._.push(fn);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Remove a subscriber function from a topic pattern.
|
|
30
|
+
*/
|
|
31
|
+
remove(topic, fn) {
|
|
32
|
+
const parts = topic.split('/');
|
|
33
|
+
let node = this.root;
|
|
34
|
+
for (const p of parts) {
|
|
35
|
+
if (!node[p])
|
|
36
|
+
return;
|
|
37
|
+
node = node[p];
|
|
38
|
+
}
|
|
39
|
+
if (node._) {
|
|
40
|
+
node._ = node._.filter((f) => f !== fn);
|
|
41
|
+
if (node._.length === 0)
|
|
42
|
+
delete node._;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Match a topic and execute callback for each matching subscriber.
|
|
47
|
+
*/
|
|
48
|
+
match(topic, cb) {
|
|
49
|
+
const parts = topic.split('/');
|
|
50
|
+
const walk = (node, i) => {
|
|
51
|
+
if (!node)
|
|
52
|
+
return;
|
|
53
|
+
// Exact match level
|
|
54
|
+
if (i === parts.length) {
|
|
55
|
+
if (node._)
|
|
56
|
+
node._.forEach(cb);
|
|
57
|
+
// Also check if '#' exists at this level (e.g. pattern 'a/#' matches 'a')
|
|
58
|
+
if (node['#'] && node['#']._)
|
|
59
|
+
node['#']._.forEach(cb);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
// 1. Direct match
|
|
63
|
+
if (node[parts[i]])
|
|
64
|
+
walk(node[parts[i]], i + 1);
|
|
65
|
+
// 2. Single level wildcard '+'
|
|
66
|
+
if (node['+'])
|
|
67
|
+
walk(node['+'], i + 1);
|
|
68
|
+
// 3. Multi level wildcard '#'
|
|
69
|
+
if (node['#'] && node['#']._)
|
|
70
|
+
node['#']._.forEach(cb);
|
|
71
|
+
};
|
|
72
|
+
walk(this.root, 0);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
exports.TopicTrie = TopicTrie;
|
|
76
|
+
//# sourceMappingURL=trie.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trie.js","sourceRoot":"","sources":["../../realtime/trie.ts"],"names":[],"mappings":";;;AAAA;;;;;;GAMG;AACH,MAAa,SAAS;IACZ,IAAI,GAAQ,EAAE,CAAC;IAEvB;;OAEG;IACH,GAAG,CAAC,KAAa,EAAE,EAAY;QAC7B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QAErB,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;YACtB,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;gBAAE,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;YAC3B,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,CAAC;YAAE,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC;QACzB,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAClB,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,KAAa,EAAE,EAAY;QAChC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QAErB,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;YACtB,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;gBAAE,OAAO;YACrB,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC;QAED,IAAI,IAAI,CAAC,CAAC,EAAE,CAAC;YACX,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAW,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;YAClD,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAC,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,KAAa,EAAE,EAA0B;QAC7C,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAE/B,MAAM,IAAI,GAAG,CAAC,IAAS,EAAE,CAAS,EAAE,EAAE;YACpC,IAAI,CAAC,IAAI;gBAAE,OAAO;YAElB,oBAAoB;YACpB,IAAI,CAAC,KAAK,KAAK,CAAC,MAAM,EAAE,CAAC;gBACvB,IAAI,IAAI,CAAC,CAAC;oBAAE,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;gBAC/B,0EAA0E;gBAC1E,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;oBAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;gBACtD,OAAO;YACT,CAAC;YAED,kBAAkB;YAClB,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;YAEhD,+BAA+B;YAC/B,IAAI,IAAI,CAAC,GAAG,CAAC;gBAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;YAEtC,8BAA8B;YAC9B,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;gBAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACxD,CAAC,CAAC;QAEF,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACrB,CAAC;CACF;AAlED,8BAkEC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dolphin-server-modules",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"homepage": "https://github.com/Phuyalshankar/dolphin-server-modules#readme",
|
|
4
5
|
"description": "Core utility modules for Auth, CRUD, and Controllers",
|
|
5
6
|
"main": "dist/index.js",
|
|
6
7
|
"types": "dist/index.d.ts",
|
|
@@ -20,7 +21,12 @@
|
|
|
20
21
|
"./middleware/zod": "./dist/middleware/zod.js",
|
|
21
22
|
"./adapters/mongoose": "./dist/adapters/mongoose/index.js",
|
|
22
23
|
"./server": "./dist/server/server.js",
|
|
23
|
-
"./router": "./dist/router/router.js"
|
|
24
|
+
"./router": "./dist/router/router.js",
|
|
25
|
+
"./realtime": "./dist/realtime/index.js",
|
|
26
|
+
"./realtime/core": "./dist/realtime/core.js",
|
|
27
|
+
"./realtime/trie": "./dist/realtime/trie.js",
|
|
28
|
+
"./realtime/codec": "./dist/realtime/codec.js",
|
|
29
|
+
"./realtime/plugins": "./dist/realtime/plugins.js"
|
|
24
30
|
},
|
|
25
31
|
"scripts": {
|
|
26
32
|
"build": "tsc",
|
|
@@ -32,12 +38,15 @@
|
|
|
32
38
|
"server",
|
|
33
39
|
"auth",
|
|
34
40
|
"crud",
|
|
35
|
-
"controller"
|
|
41
|
+
"controller",
|
|
42
|
+
"realtime",
|
|
43
|
+
"iot"
|
|
36
44
|
],
|
|
37
45
|
"author": "",
|
|
38
46
|
"license": "ISC",
|
|
39
47
|
"dependencies": {
|
|
40
48
|
"argon2": "^0.40.1",
|
|
49
|
+
"ioredis": "^15.15.5",
|
|
41
50
|
"zod": "^4.3.6"
|
|
42
51
|
},
|
|
43
52
|
"devDependencies": {
|