@j3r3mcdev/oast-server 1.1.7 → 1.1.8

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 (95) hide show
  1. package/.github/workflows/ci.yml +29 -29
  2. package/.github/workflows/publish.yml +31 -31
  3. package/README.md +192 -192
  4. package/dist/core/router.d.ts +1 -0
  5. package/dist/core/router.js +3 -3
  6. package/jest.config.js +14 -14
  7. package/package.json +45 -45
  8. package/sadmin list shadows +9 -9
  9. package/src/api/controllers/__tests__/tasks.controller.test.ts +74 -74
  10. package/src/api/controllers/events.controller.ts +10 -10
  11. package/src/api/controllers/health.controller.ts +7 -7
  12. package/src/api/controllers/tasks.controller.ts +41 -41
  13. package/src/api/dto/__tests__/create-task.dto.test.ts +41 -41
  14. package/src/api/dto/__tests__/filter-tasks.dto.test.ts +35 -35
  15. package/src/api/dto/create-task.dto.ts +33 -33
  16. package/src/api/dto/filter-tasks.dto.ts +33 -33
  17. package/src/api/services/__tests__/events.service.test.ts +41 -41
  18. package/src/api/services/__tests__/tasks.service.test.ts +41 -41
  19. package/src/api/services/events.service.ts +17 -17
  20. package/src/api/services/tasks.service.ts +79 -79
  21. package/src/api/sse/events.stream.ts +90 -90
  22. package/src/bootstrap.ts +89 -89
  23. package/src/core/__tests__/core-router.test.ts +30 -30
  24. package/src/core/__tests__/core-server.test.ts +44 -44
  25. package/src/core/__tests__/event.normalizer.test.ts +56 -56
  26. package/src/core/__tests__/event.router.test.ts +89 -89
  27. package/src/core/__tests__/logger.test.ts +32 -32
  28. package/src/core/__tests__/storage-manager.test.ts +74 -74
  29. package/src/core/event.normalizer.ts +147 -147
  30. package/src/core/event.router.ts +13 -13
  31. package/src/core/http/__tests__/adapter-node.test.ts +52 -52
  32. package/src/core/http/__tests__/body-parser-multipart.test.ts +41 -41
  33. package/src/core/http/__tests__/body-parser-raw.test.ts +28 -28
  34. package/src/core/http/__tests__/body-parser-text.test.ts +28 -28
  35. package/src/core/http/__tests__/compile-path.test.ts +39 -39
  36. package/src/core/http/__tests__/middleware-pipeline.test.ts +51 -51
  37. package/src/core/http/__tests__/request.test.ts +34 -34
  38. package/src/core/http/__tests__/response.test.ts +35 -35
  39. package/src/core/http/__tests__/router-match.test.ts +171 -171
  40. package/src/core/http/adapter-node.ts +51 -51
  41. package/src/core/http/buildRequest.ts +18 -18
  42. package/src/core/http/compile-path.ts +32 -32
  43. package/src/core/http/errors.ts +37 -37
  44. package/src/core/http/http-server.ts +52 -52
  45. package/src/core/http/middleware.ts +160 -160
  46. package/src/core/http/request.ts +55 -55
  47. package/src/core/http/response.ts +93 -93
  48. package/src/core/http/router.ts +138 -138
  49. package/src/core/id-generator.ts +8 -8
  50. package/src/core/logger.ts +113 -113
  51. package/src/core/router.ts +44 -44
  52. package/src/core/server.ts +85 -85
  53. package/src/core/storage.ts +64 -64
  54. package/src/index.ts +14 -14
  55. package/src/listeners/api/__tests__/api.controller.test.ts +116 -116
  56. package/src/listeners/api/__tests__/api.extractor.test.ts +46 -46
  57. package/src/listeners/api/__tests__/api.listener.test.ts +82 -82
  58. package/src/listeners/api/__tests__/api.routes.test.ts +155 -155
  59. package/src/listeners/api/__tests__/api.sse.test.ts +105 -105
  60. package/src/listeners/api/api.controllers.ts +67 -67
  61. package/src/listeners/api/api.extractor.ts +43 -43
  62. package/src/listeners/api/api.listener.ts +50 -50
  63. package/src/listeners/api/api.routes.ts +76 -76
  64. package/src/listeners/api/api.sse.ts +38 -38
  65. package/src/listeners/dns/__tests__/dns.test.ts +118 -118
  66. package/src/listeners/dns/dns.extractor.ts +14 -14
  67. package/src/listeners/dns/dns.listener.ts +61 -61
  68. package/src/listeners/http/__tests__/http.extractor.test.ts +59 -59
  69. package/src/listeners/http/__tests__/http.listener.test.ts +133 -133
  70. package/src/listeners/http/http.extractor.ts +15 -15
  71. package/src/listeners/http/http.listener.ts +110 -110
  72. package/src/listeners/listener.interface.ts +4 -4
  73. package/src/listeners/smtp/__tests__/smtp.extractor.test.ts +69 -69
  74. package/src/listeners/smtp/__tests__/smtp.listener.test.ts +150 -150
  75. package/src/listeners/smtp/smtp.extractor.ts +18 -18
  76. package/src/listeners/smtp/smtp.listener.ts +60 -60
  77. package/src/listeners/ssrf/__tests__/ssrf.extractor.test.ts +41 -41
  78. package/src/listeners/ssrf/__tests__/ssrf.listener.test.ts +87 -87
  79. package/src/listeners/ssrf/ssrf.extractor.ts +14 -14
  80. package/src/listeners/ssrf/ssrf.listener.ts +37 -37
  81. package/src/listeners/tcp/tcp.extractor.ts +16 -16
  82. package/src/listeners/tcp/tcp.listener.ts +61 -61
  83. package/src/listeners/webhook/__tests__/webhook.extractor.test.ts +35 -35
  84. package/src/listeners/webhook/__tests__/webhook.listener.test.ts +122 -122
  85. package/src/listeners/webhook/webhook.extractor.ts +12 -12
  86. package/src/listeners/webhook/webhook.listener.ts +58 -58
  87. package/src/listeners/websocket/__tests__/websocket.extractor.test.ts +33 -33
  88. package/src/listeners/websocket/__tests__/websocket.listener.test.ts +90 -90
  89. package/src/listeners/websocket/websocket.extractor.ts +11 -11
  90. package/src/listeners/websocket/websocket.listener.ts +40 -40
  91. package/src/storage-adapters/adapters/__tests__/memory.storage.test.ts +75 -75
  92. package/src/storage-adapters/adapters/memory.storage.ts +64 -64
  93. package/src/storage-adapters/storage.interface.ts +26 -26
  94. package/src/types/event.types.ts +147 -147
  95. package/tsconfig.json +20 -20
@@ -1,41 +1,41 @@
1
- import { describe, it, expect, jest } from "@jest/globals";
2
- import { TasksService } from "../tasks.service";
3
- import { EventsService } from "../events.service";
4
- import { EventStreamManager } from "../../sse/events.stream";
5
-
6
- describe("TasksService", () => {
7
- it("émet un événement lors de la création d'une tâche", () => {
8
- const stream = new EventStreamManager({ heartbeatInterval: 999999 });
9
- const events = new EventsService(stream);
10
-
11
- const spy = jest.spyOn(stream, "broadcast");
12
-
13
- const service = new TasksService(events);
14
-
15
- const task = service.create({ type: "x", payload: {} });
16
-
17
- expect(spy).toHaveBeenCalledWith(
18
- "tasks",
19
- "task.created",
20
- expect.objectContaining({ id: task.id }),
21
- );
22
- });
23
-
24
- it("émet un événement lors de l'annulation d'une tâche", () => {
25
- const stream = new EventStreamManager({ heartbeatInterval: 999999 });
26
- const events = new EventsService(stream);
27
-
28
- const spy = jest.spyOn(stream, "broadcast");
29
-
30
- const service = new TasksService(events);
31
-
32
- const task = service.create({ type: "x", payload: {} });
33
- service.cancel(task.id);
34
-
35
- expect(spy).toHaveBeenCalledWith(
36
- "tasks",
37
- "task.cancelled",
38
- expect.objectContaining({ id: task.id }),
39
- );
40
- });
41
- });
1
+ import { describe, it, expect, jest } from "@jest/globals";
2
+ import { TasksService } from "../tasks.service";
3
+ import { EventsService } from "../events.service";
4
+ import { EventStreamManager } from "../../sse/events.stream";
5
+
6
+ describe("TasksService", () => {
7
+ it("émet un événement lors de la création d'une tâche", () => {
8
+ const stream = new EventStreamManager({ heartbeatInterval: 999999 });
9
+ const events = new EventsService(stream);
10
+
11
+ const spy = jest.spyOn(stream, "broadcast");
12
+
13
+ const service = new TasksService(events);
14
+
15
+ const task = service.create({ type: "x", payload: {} });
16
+
17
+ expect(spy).toHaveBeenCalledWith(
18
+ "tasks",
19
+ "task.created",
20
+ expect.objectContaining({ id: task.id }),
21
+ );
22
+ });
23
+
24
+ it("émet un événement lors de l'annulation d'une tâche", () => {
25
+ const stream = new EventStreamManager({ heartbeatInterval: 999999 });
26
+ const events = new EventsService(stream);
27
+
28
+ const spy = jest.spyOn(stream, "broadcast");
29
+
30
+ const service = new TasksService(events);
31
+
32
+ const task = service.create({ type: "x", payload: {} });
33
+ service.cancel(task.id);
34
+
35
+ expect(spy).toHaveBeenCalledWith(
36
+ "tasks",
37
+ "task.cancelled",
38
+ expect.objectContaining({ id: task.id }),
39
+ );
40
+ });
41
+ });
@@ -1,17 +1,17 @@
1
- import { EventStreamManager } from "../sse/events.stream";
2
-
3
- export class EventsService {
4
- constructor(private stream: EventStreamManager) {}
5
-
6
- connect(res: any, channels: string[] = []) {
7
- return this.stream.addClient(res, channels);
8
- }
9
-
10
- emit(channel: string, event: string, data: any) {
11
- this.stream.broadcast(channel, event, data);
12
- }
13
-
14
- emitAll(event: string, data: any) {
15
- this.stream.broadcastAll(event, data);
16
- }
17
- }
1
+ import { EventStreamManager } from "../sse/events.stream";
2
+
3
+ export class EventsService {
4
+ constructor(private stream: EventStreamManager) {}
5
+
6
+ connect(res: any, channels: string[] = []) {
7
+ return this.stream.addClient(res, channels);
8
+ }
9
+
10
+ emit(channel: string, event: string, data: any) {
11
+ this.stream.broadcast(channel, event, data);
12
+ }
13
+
14
+ emitAll(event: string, data: any) {
15
+ this.stream.broadcastAll(event, data);
16
+ }
17
+ }
@@ -1,79 +1,79 @@
1
- import { CreateTaskDto } from "../dto/create-task.dto";
2
- import { FilterTasksDto } from "../dto/filter-tasks.dto";
3
- import { EventsService } from "./events.service";
4
-
5
- export interface Task {
6
- id: string;
7
- type: string;
8
- payload: Record<string, any>;
9
- priority: "low" | "normal" | "high";
10
- metadata: Record<string, any>;
11
- status: "pending" | "running" | "completed" | "failed";
12
- createdAt: number;
13
- updatedAt: number;
14
- }
15
-
16
- export class TasksService {
17
- private tasks = new Map<string, Task>();
18
- private events: EventsService;
19
-
20
- constructor(events: EventsService) {
21
- this.events = events;
22
- }
23
-
24
- create(dto: CreateTaskDto): Task {
25
- const id = crypto.randomUUID();
26
- const now = Date.now();
27
-
28
- const task: Task = {
29
- id,
30
- type: dto.type,
31
- payload: dto.payload,
32
- priority: dto.priority ?? "normal",
33
- metadata: dto.metadata ?? {},
34
- status: "pending",
35
- createdAt: now,
36
- updatedAt: now,
37
- };
38
-
39
- this.tasks.set(id, task);
40
-
41
- this.events.emit("tasks", "task.created", task);
42
-
43
- return task;
44
- }
45
-
46
- get(id: string): Task | undefined {
47
- return this.tasks.get(id);
48
- }
49
-
50
- list(filters: FilterTasksDto): Task[] {
51
- let results = Array.from(this.tasks.values());
52
-
53
- if (filters.status) {
54
- results = results.filter((t) => t.status === filters.status);
55
- }
56
-
57
- if (filters.type) {
58
- results = results.filter((t) => t.type === filters.type);
59
- }
60
-
61
- if (filters.limit) {
62
- results = results.slice(0, filters.limit);
63
- }
64
-
65
- return results;
66
- }
67
-
68
- cancel(id: string): boolean {
69
- const task = this.tasks.get(id);
70
- if (!task) return false;
71
-
72
- task.status = "failed";
73
- task.updatedAt = Date.now();
74
-
75
- this.events.emit("tasks", "task.cancelled", task);
76
-
77
- return true;
78
- }
79
- }
1
+ import { CreateTaskDto } from "../dto/create-task.dto";
2
+ import { FilterTasksDto } from "../dto/filter-tasks.dto";
3
+ import { EventsService } from "./events.service";
4
+
5
+ export interface Task {
6
+ id: string;
7
+ type: string;
8
+ payload: Record<string, any>;
9
+ priority: "low" | "normal" | "high";
10
+ metadata: Record<string, any>;
11
+ status: "pending" | "running" | "completed" | "failed";
12
+ createdAt: number;
13
+ updatedAt: number;
14
+ }
15
+
16
+ export class TasksService {
17
+ private tasks = new Map<string, Task>();
18
+ private events: EventsService;
19
+
20
+ constructor(events: EventsService) {
21
+ this.events = events;
22
+ }
23
+
24
+ create(dto: CreateTaskDto): Task {
25
+ const id = crypto.randomUUID();
26
+ const now = Date.now();
27
+
28
+ const task: Task = {
29
+ id,
30
+ type: dto.type,
31
+ payload: dto.payload,
32
+ priority: dto.priority ?? "normal",
33
+ metadata: dto.metadata ?? {},
34
+ status: "pending",
35
+ createdAt: now,
36
+ updatedAt: now,
37
+ };
38
+
39
+ this.tasks.set(id, task);
40
+
41
+ this.events.emit("tasks", "task.created", task);
42
+
43
+ return task;
44
+ }
45
+
46
+ get(id: string): Task | undefined {
47
+ return this.tasks.get(id);
48
+ }
49
+
50
+ list(filters: FilterTasksDto): Task[] {
51
+ let results = Array.from(this.tasks.values());
52
+
53
+ if (filters.status) {
54
+ results = results.filter((t) => t.status === filters.status);
55
+ }
56
+
57
+ if (filters.type) {
58
+ results = results.filter((t) => t.type === filters.type);
59
+ }
60
+
61
+ if (filters.limit) {
62
+ results = results.slice(0, filters.limit);
63
+ }
64
+
65
+ return results;
66
+ }
67
+
68
+ cancel(id: string): boolean {
69
+ const task = this.tasks.get(id);
70
+ if (!task) return false;
71
+
72
+ task.status = "failed";
73
+ task.updatedAt = Date.now();
74
+
75
+ this.events.emit("tasks", "task.cancelled", task);
76
+
77
+ return true;
78
+ }
79
+ }
@@ -1,90 +1,90 @@
1
- // src/api/sse/event-stream.ts
2
-
3
- export interface EventStreamConfig {
4
- retry?: number;
5
- heartbeatInterval?: number;
6
- logger?: { info: Function; error: Function };
7
- }
8
-
9
- export interface StreamClient {
10
- id: string;
11
- res: any;
12
- channels: Set<string>;
13
- connectedAt: number;
14
- }
15
-
16
- export class EventStreamManager {
17
- private clients = new Map<string, StreamClient>();
18
- private config: Required<EventStreamConfig>;
19
- private heartbeatTimer: NodeJS.Timeout | null = null;
20
-
21
- constructor(config: EventStreamConfig = {}) {
22
- this.config = {
23
- retry: config.retry ?? 2000,
24
- heartbeatInterval: config.heartbeatInterval ?? 15000,
25
- logger: config.logger ?? console,
26
- };
27
-
28
- this.startHeartbeat();
29
- }
30
-
31
- private startHeartbeat() {
32
- if (process.env.NODE_ENV === "test") return;
33
-
34
- this.heartbeatTimer = setInterval(() => {
35
- for (const client of this.clients.values()) {
36
- client.res.write(`: heartbeat\n\n`);
37
- }
38
- }, this.config.heartbeatInterval);
39
- }
40
-
41
- addClient(res: any, channels: string[] = []): string {
42
- const id = crypto.randomUUID();
43
-
44
- const client: StreamClient = {
45
- id,
46
- res,
47
- channels: new Set(channels),
48
- connectedAt: Date.now(),
49
- };
50
-
51
- this.clients.set(id, client);
52
-
53
- res.write(`retry: ${this.config.retry}\n`);
54
- res.write(`event: connected\ndata: {"id":"${id}"}\n\n`);
55
-
56
- res.on("close", () => this.removeClient(id));
57
-
58
- this.config.logger.info(`SSE client connected: ${id}`);
59
-
60
- return id;
61
- }
62
-
63
- removeClient(id: string) {
64
- if (this.clients.has(id)) {
65
- this.clients.delete(id);
66
- this.config.logger.info(`SSE client disconnected: ${id}`);
67
- }
68
- }
69
-
70
- broadcast(channel: string, event: string, data: any) {
71
- const payload =
72
- `event: ${event}\n` +
73
- `data: ${JSON.stringify(data)}\n` +
74
- `channel: ${channel}\n\n`;
75
-
76
- for (const client of this.clients.values()) {
77
- if (client.channels.has(channel)) {
78
- client.res.write(payload);
79
- }
80
- }
81
- }
82
-
83
- broadcastAll(event: string, data: any) {
84
- const payload = `event: ${event}\n` + `data: ${JSON.stringify(data)}\n\n`;
85
-
86
- for (const client of this.clients.values()) {
87
- client.res.write(payload);
88
- }
89
- }
90
- }
1
+ // src/api/sse/event-stream.ts
2
+
3
+ export interface EventStreamConfig {
4
+ retry?: number;
5
+ heartbeatInterval?: number;
6
+ logger?: { info: Function; error: Function };
7
+ }
8
+
9
+ export interface StreamClient {
10
+ id: string;
11
+ res: any;
12
+ channels: Set<string>;
13
+ connectedAt: number;
14
+ }
15
+
16
+ export class EventStreamManager {
17
+ private clients = new Map<string, StreamClient>();
18
+ private config: Required<EventStreamConfig>;
19
+ private heartbeatTimer: NodeJS.Timeout | null = null;
20
+
21
+ constructor(config: EventStreamConfig = {}) {
22
+ this.config = {
23
+ retry: config.retry ?? 2000,
24
+ heartbeatInterval: config.heartbeatInterval ?? 15000,
25
+ logger: config.logger ?? console,
26
+ };
27
+
28
+ this.startHeartbeat();
29
+ }
30
+
31
+ private startHeartbeat() {
32
+ if (process.env.NODE_ENV === "test") return;
33
+
34
+ this.heartbeatTimer = setInterval(() => {
35
+ for (const client of this.clients.values()) {
36
+ client.res.write(`: heartbeat\n\n`);
37
+ }
38
+ }, this.config.heartbeatInterval);
39
+ }
40
+
41
+ addClient(res: any, channels: string[] = []): string {
42
+ const id = crypto.randomUUID();
43
+
44
+ const client: StreamClient = {
45
+ id,
46
+ res,
47
+ channels: new Set(channels),
48
+ connectedAt: Date.now(),
49
+ };
50
+
51
+ this.clients.set(id, client);
52
+
53
+ res.write(`retry: ${this.config.retry}\n`);
54
+ res.write(`event: connected\ndata: {"id":"${id}"}\n\n`);
55
+
56
+ res.on("close", () => this.removeClient(id));
57
+
58
+ this.config.logger.info(`SSE client connected: ${id}`);
59
+
60
+ return id;
61
+ }
62
+
63
+ removeClient(id: string) {
64
+ if (this.clients.has(id)) {
65
+ this.clients.delete(id);
66
+ this.config.logger.info(`SSE client disconnected: ${id}`);
67
+ }
68
+ }
69
+
70
+ broadcast(channel: string, event: string, data: any) {
71
+ const payload =
72
+ `event: ${event}\n` +
73
+ `data: ${JSON.stringify(data)}\n` +
74
+ `channel: ${channel}\n\n`;
75
+
76
+ for (const client of this.clients.values()) {
77
+ if (client.channels.has(channel)) {
78
+ client.res.write(payload);
79
+ }
80
+ }
81
+ }
82
+
83
+ broadcastAll(event: string, data: any) {
84
+ const payload = `event: ${event}\n` + `data: ${JSON.stringify(data)}\n\n`;
85
+
86
+ for (const client of this.clients.values()) {
87
+ client.res.write(payload);
88
+ }
89
+ }
90
+ }
package/src/bootstrap.ts CHANGED
@@ -1,89 +1,89 @@
1
- import { Logger } from "./core/logger";
2
- import { StorageManager } from "./core/storage";
3
- import { CoreRouter } from "./core/router";
4
- import { CoreServer } from "./core/server";
5
-
6
- // Listeners
7
- import { ApiListener } from "./listeners/api/api.listener";
8
- import { HttpListener } from "./listeners/http/http.listener";
9
- import { DnsListener } from "./listeners/dns/dns.listener";
10
- import { SmtpListener } from "./listeners/smtp/smtp.listener";
11
- import { TcpListener } from "./listeners/tcp/tcp.listener";
12
-
13
- async function main() {
14
- const logger = new Logger({ context: "Bootstrap" });
15
-
16
- // Core components
17
- const storage = new StorageManager({
18
- logger: logger.withContext("Storage"),
19
- });
20
-
21
- const router = new CoreRouter(storage, {
22
- logger: logger.withContext("CoreRouter"),
23
- });
24
-
25
- // ---------------------------
26
- // LISTENERS
27
- // ---------------------------
28
-
29
- // API → (router, storage, options)
30
- const apiListener = new ApiListener(storage, {
31
- port: 3100,
32
- logger: logger.withContext("ApiListener"),
33
- });
34
-
35
- // HTTP → (router, options)
36
- const httpListener = new HttpListener(router, {
37
- port: 8080,
38
- logger: logger.withContext("HttpListener"),
39
- });
40
-
41
- // DNS → (router, options)
42
- const dnsListener = new DnsListener(router, {
43
- server: {} as any,
44
- logger: logger.withContext("DnsListener"),
45
- });
46
-
47
- const smtpListener = new SmtpListener(router, {
48
- server: {} as any,
49
- logger: logger.withContext("SmtpListener"),
50
- });
51
-
52
- // TCP → (router, storage, options)
53
- const tcpListener = new TcpListener(router, storage, {
54
- port: 9000,
55
- logger: logger.withContext("TcpListener"),
56
- });
57
-
58
- // ---------------------------
59
- // CORE SERVER
60
- // ---------------------------
61
- const server = new CoreServer({
62
- logger: logger.withContext("CoreServer"),
63
- storage,
64
- listeners: [
65
- apiListener,
66
- httpListener,
67
- dnsListener,
68
- smtpListener,
69
- tcpListener,
70
- ],
71
- });
72
-
73
- await server.start();
74
-
75
- // Graceful shutdown
76
- const shutdown = async () => {
77
- logger.warn("Received shutdown signal, stopping server...");
78
- await server.stop();
79
- process.exit(0);
80
- };
81
-
82
- process.on("SIGINT", shutdown);
83
- process.on("SIGTERM", shutdown);
84
- }
85
-
86
- main().catch((err) => {
87
- console.error("Fatal error in bootstrap:", err);
88
- process.exit(1);
89
- });
1
+ import { Logger } from "./core/logger";
2
+ import { StorageManager } from "./core/storage";
3
+ import { CoreRouter } from "./core/router";
4
+ import { CoreServer } from "./core/server";
5
+
6
+ // Listeners
7
+ import { ApiListener } from "./listeners/api/api.listener";
8
+ import { HttpListener } from "./listeners/http/http.listener";
9
+ import { DnsListener } from "./listeners/dns/dns.listener";
10
+ import { SmtpListener } from "./listeners/smtp/smtp.listener";
11
+ import { TcpListener } from "./listeners/tcp/tcp.listener";
12
+
13
+ async function main() {
14
+ const logger = new Logger({ context: "Bootstrap" });
15
+
16
+ // Core components
17
+ const storage = new StorageManager({
18
+ logger: logger.withContext("Storage"),
19
+ });
20
+
21
+ const router = new CoreRouter(storage, {
22
+ logger: logger.withContext("CoreRouter"),
23
+ });
24
+
25
+ // ---------------------------
26
+ // LISTENERS
27
+ // ---------------------------
28
+
29
+ // API → (router, storage, options)
30
+ const apiListener = new ApiListener(storage, {
31
+ port: 3100,
32
+ logger: logger.withContext("ApiListener"),
33
+ });
34
+
35
+ // HTTP → (router, options)
36
+ const httpListener = new HttpListener(router, {
37
+ port: 8080,
38
+ logger: logger.withContext("HttpListener"),
39
+ });
40
+
41
+ // DNS → (router, options)
42
+ const dnsListener = new DnsListener(router, {
43
+ server: {} as any,
44
+ logger: logger.withContext("DnsListener"),
45
+ });
46
+
47
+ const smtpListener = new SmtpListener(router, {
48
+ server: {} as any,
49
+ logger: logger.withContext("SmtpListener"),
50
+ });
51
+
52
+ // TCP → (router, storage, options)
53
+ const tcpListener = new TcpListener(router, storage, {
54
+ port: 9000,
55
+ logger: logger.withContext("TcpListener"),
56
+ });
57
+
58
+ // ---------------------------
59
+ // CORE SERVER
60
+ // ---------------------------
61
+ const server = new CoreServer({
62
+ logger: logger.withContext("CoreServer"),
63
+ storage,
64
+ listeners: [
65
+ apiListener,
66
+ httpListener,
67
+ dnsListener,
68
+ smtpListener,
69
+ tcpListener,
70
+ ],
71
+ });
72
+
73
+ await server.start();
74
+
75
+ // Graceful shutdown
76
+ const shutdown = async () => {
77
+ logger.warn("Received shutdown signal, stopping server...");
78
+ await server.stop();
79
+ process.exit(0);
80
+ };
81
+
82
+ process.on("SIGINT", shutdown);
83
+ process.on("SIGTERM", shutdown);
84
+ }
85
+
86
+ main().catch((err) => {
87
+ console.error("Fatal error in bootstrap:", err);
88
+ process.exit(1);
89
+ });