@principal-ai/control-tower-core 0.1.6 → 0.1.7
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/README.md +63 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -19
- package/dist/index.js.map +7 -5
- package/dist/index.mjs +178 -2
- package/dist/index.mjs.map +7 -5
- package/dist/server/BaseServer.d.ts +13 -1
- package/dist/server/BaseServer.d.ts.map +1 -1
- package/dist/server/BaseServer.js +15 -0
- package/dist/server/ExperimentalAPI.d.ts +137 -0
- package/dist/server/ExperimentalAPI.d.ts.map +1 -0
- package/dist/server/ExperimentalAPI.js +239 -0
- package/dist/server/ServerBuilder.d.ts +11 -1
- package/dist/server/ServerBuilder.d.ts.map +1 -1
- package/dist/server/ServerBuilder.js +18 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3 -1
- package/dist/types/experimental.d.ts +136 -0
- package/dist/types/experimental.d.ts.map +1 -0
- package/dist/types/experimental.js +32 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +3 -0
- package/package.json +1 -1
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import type { Event, Room, RoomConfig, Lock } from '../types/index.js';
|
|
1
|
+
import type { Event, Room, RoomConfig, Lock, ExperimentalFeatureConfig, ExperimentalUsageEvent } from '../types/index.js';
|
|
2
2
|
import type { ITransportAdapter } from '../abstractions/TransportAdapter.js';
|
|
3
3
|
import type { IAuthAdapter } from '../abstractions/AuthAdapter.js';
|
|
4
4
|
import type { IStorageAdapter } from '../abstractions/StorageAdapter.js';
|
|
5
5
|
import type { RoomManager } from '../abstractions/RoomManager.js';
|
|
6
6
|
import type { LockManager } from '../abstractions/LockManager.js';
|
|
7
7
|
import { TypedEventEmitter } from '../abstractions/EventEmitter.js';
|
|
8
|
+
import { ExperimentalAPI } from './ExperimentalAPI.js';
|
|
8
9
|
import type { Server as HttpServer } from 'http';
|
|
9
10
|
import type { Server as HttpsServer } from 'https';
|
|
10
11
|
import type { WebSocketServer } from 'ws';
|
|
@@ -18,6 +19,7 @@ export interface ServerConfig {
|
|
|
18
19
|
httpServer?: HttpServer | HttpsServer;
|
|
19
20
|
webSocketPath?: string;
|
|
20
21
|
webSocketServer?: WebSocketServer;
|
|
22
|
+
experimental?: ExperimentalFeatureConfig;
|
|
21
23
|
}
|
|
22
24
|
export interface ConnectedClient {
|
|
23
25
|
id: string;
|
|
@@ -73,6 +75,7 @@ export interface ServerEvents {
|
|
|
73
75
|
error: Error;
|
|
74
76
|
context?: string;
|
|
75
77
|
};
|
|
78
|
+
experimental_usage: ExperimentalUsageEvent;
|
|
76
79
|
[key: string]: unknown;
|
|
77
80
|
}
|
|
78
81
|
export declare class BaseServer extends TypedEventEmitter<ServerEvents> {
|
|
@@ -87,6 +90,15 @@ export declare class BaseServer extends TypedEventEmitter<ServerEvents> {
|
|
|
87
90
|
private running;
|
|
88
91
|
private initialized;
|
|
89
92
|
private mode;
|
|
93
|
+
/**
|
|
94
|
+
* Experimental APIs namespace
|
|
95
|
+
*
|
|
96
|
+
* ⚠️ WARNING: Experimental features are for development only.
|
|
97
|
+
* APIs may change or be removed without major version bumps.
|
|
98
|
+
*
|
|
99
|
+
* @see docs/EXPERIMENTAL_BROADCAST.md
|
|
100
|
+
*/
|
|
101
|
+
readonly experimental: ExperimentalAPI;
|
|
90
102
|
constructor(config: ServerConfig);
|
|
91
103
|
start(port?: number): Promise<void>;
|
|
92
104
|
initialize(): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BaseServer.d.ts","sourceRoot":"","sources":["../../src/server/BaseServer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,KAAK,EACL,IAAI,EAEJ,UAAU,EACV,IAAI,
|
|
1
|
+
{"version":3,"file":"BaseServer.d.ts","sourceRoot":"","sources":["../../src/server/BaseServer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,KAAK,EACL,IAAI,EAEJ,UAAU,EACV,IAAI,EAEJ,yBAAyB,EACzB,sBAAsB,EACvB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;AAC7E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,MAAM,CAAC;AACjD,OAAO,KAAK,EAAE,MAAM,IAAI,WAAW,EAAE,MAAM,OAAO,CAAC;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,IAAI,CAAC;AAE1C,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,iBAAiB,CAAC;IAC7B,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,WAAW,EAAE,WAAW,CAAC;IACzB,WAAW,EAAE,WAAW,CAAC;IACzB,iBAAiB,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IACxC,UAAU,CAAC,EAAE,UAAU,GAAG,WAAW,CAAC;IACtC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,YAAY,CAAC,EAAE,yBAAyB,CAAC;CAC1C;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1B,OAAO,EAAE,EAAE,CAAC;IACZ,gBAAgB,EAAE;QAAE,MAAM,EAAE,eAAe,CAAA;KAAE,CAAC;IAC9C,mBAAmB,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1D,oBAAoB,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3D,YAAY,EAAE;QAAE,IAAI,EAAE,IAAI,CAAA;KAAE,CAAC;IAC7B,YAAY,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACjC,kBAAkB,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACzD,gBAAgB,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACvD,eAAe,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;IACxE,aAAa,EAAE;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,aAAa,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACpD,KAAK,EAAE;QAAE,KAAK,EAAE,KAAK,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,kBAAkB,EAAE,sBAAsB,CAAC;IAC3C,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,qBAAa,UAAW,SAAQ,iBAAiB,CAAC,YAAY,CAAC;IAC7D,OAAO,CAAC,SAAS,CAAoB;IACrC,OAAO,CAAC,IAAI,CAAC,CAAe;IAC5B,OAAO,CAAC,OAAO,CAAC,CAAkB;IAClC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,MAAM,CAAe;IAE7B,OAAO,CAAC,OAAO,CAAsC;IACrD,OAAO,CAAC,qBAAqB,CAA0D;IACvF,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,IAAI,CAA+B;IAE3C;;;;;;;OAOG;IACH,SAAgB,YAAY,EAAE,eAAe,CAAC;gBAElC,MAAM,EAAE,YAAY;IA0C1B,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAwBnC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IA0C3B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAyBb,sBAAsB;YA0CtB,uBAAuB;YA8BvB,gCAAgC;YAmBhC,oBAAoB;YAIpB,oBAAoB;YAOpB,SAAS;YAiBT,gBAAgB;IAsB9B,OAAO,CAAC,0BAA0B;YA+CpB,kBAAkB;YAqBlB,cAAc;YAwEd,eAAe;YAef,oBAAoB;YA+BpB,iBAAiB;YA4BjB,iBAAiB;YA8BjB,YAAY;IAW1B,OAAO,CAAC,UAAU;IAKZ,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAM7D,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK/C,mBAAmB,IAAI,eAAe,EAAE;IAIxC,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI;IAInD,SAAS,IAAI,OAAO;IAIpB,OAAO,IAAI,YAAY,GAAG,aAAa;IAIvC,aAAa,IAAI,OAAO;CAGzB"}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.BaseServer = void 0;
|
|
4
4
|
const EventEmitter_js_1 = require("../abstractions/EventEmitter.js");
|
|
5
|
+
const ExperimentalAPI_js_1 = require("./ExperimentalAPI.js");
|
|
5
6
|
class BaseServer extends EventEmitter_js_1.TypedEventEmitter {
|
|
6
7
|
constructor(config) {
|
|
7
8
|
super();
|
|
@@ -15,6 +16,20 @@ class BaseServer extends EventEmitter_js_1.TypedEventEmitter {
|
|
|
15
16
|
this.storage = config.storage;
|
|
16
17
|
this.roomManager = config.roomManager;
|
|
17
18
|
this.lockManager = config.lockManager;
|
|
19
|
+
// Initialize experimental API namespace
|
|
20
|
+
this.experimental = new ExperimentalAPI_js_1.ExperimentalAPI({
|
|
21
|
+
config: config.experimental || {
|
|
22
|
+
enableBroadcast: false,
|
|
23
|
+
warnOnExperimentalUse: true,
|
|
24
|
+
trackUsageMetrics: false
|
|
25
|
+
},
|
|
26
|
+
logger: console,
|
|
27
|
+
emitMetric: (event) => {
|
|
28
|
+
void this.emit('experimental_usage', event);
|
|
29
|
+
},
|
|
30
|
+
getClients: () => Array.from(this.clients.values()),
|
|
31
|
+
sendToClient: (clientId, message) => this.sendToClient(clientId, message)
|
|
32
|
+
});
|
|
18
33
|
// Determine mode based on configuration
|
|
19
34
|
this.mode = (config.httpServer || config.webSocketServer) ? 'integration' : 'standalone';
|
|
20
35
|
// Connect auth adapter to transport if both exist and transport supports it
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Experimental Broadcast APIs
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ DEVELOPMENT USE ONLY
|
|
5
|
+
*
|
|
6
|
+
* This namespace provides experimental broadcast methods for rapid prototyping
|
|
7
|
+
* and internal tooling. These APIs may change or be removed without major version bumps.
|
|
8
|
+
*
|
|
9
|
+
* For production use, submit a feature request to graduate these APIs to first-class APIs.
|
|
10
|
+
*
|
|
11
|
+
* @see docs/EXPERIMENTAL_BROADCAST.md
|
|
12
|
+
* @see docs/FEATURE_REQUEST_TEMPLATE.md
|
|
13
|
+
*/
|
|
14
|
+
import type { Message } from '../types/index.js';
|
|
15
|
+
import type { ExperimentalFeatureConfig, ClientPredicate, BroadcastResult, BroadcastOptions, ExperimentalUsageEvent } from '../types/experimental.js';
|
|
16
|
+
import type { ConnectedClient } from './BaseServer.js';
|
|
17
|
+
/**
|
|
18
|
+
* Experimental API namespace
|
|
19
|
+
*
|
|
20
|
+
* Provides broadcast methods that bypass normal room isolation.
|
|
21
|
+
* Requires explicit opt-in via configuration.
|
|
22
|
+
*/
|
|
23
|
+
export declare class ExperimentalAPI {
|
|
24
|
+
private readonly config;
|
|
25
|
+
private readonly logger?;
|
|
26
|
+
private readonly emitMetric?;
|
|
27
|
+
private readonly getClients;
|
|
28
|
+
private readonly sendToClient;
|
|
29
|
+
constructor(options: {
|
|
30
|
+
config: ExperimentalFeatureConfig;
|
|
31
|
+
logger?: {
|
|
32
|
+
warn: (message: string, ...args: unknown[]) => void;
|
|
33
|
+
};
|
|
34
|
+
emitMetric?: (event: ExperimentalUsageEvent) => void;
|
|
35
|
+
getClients: () => ConnectedClient[];
|
|
36
|
+
sendToClient: (clientId: string, message: Record<string, unknown>) => Promise<void>;
|
|
37
|
+
});
|
|
38
|
+
/**
|
|
39
|
+
* Broadcasts a message to all connected clients
|
|
40
|
+
*
|
|
41
|
+
* ⚠️ WARNING: This bypasses room isolation and sends to every connected client.
|
|
42
|
+
*
|
|
43
|
+
* @experimental
|
|
44
|
+
* @param message - Message to broadcast
|
|
45
|
+
* @param options - Broadcast options
|
|
46
|
+
* @returns Broadcast result with success/failure counts
|
|
47
|
+
*
|
|
48
|
+
* @throws {ExperimentalFeatureError} If experimental.enableBroadcast is not enabled
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* await server.experimental.broadcast({
|
|
53
|
+
* type: 'system:announcement',
|
|
54
|
+
* data: { message: 'Server maintenance in 5 minutes' }
|
|
55
|
+
* });
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
broadcast(message: Omit<Message, 'id' | 'timestamp'>, options?: BroadcastOptions): Promise<BroadcastResult>;
|
|
59
|
+
/**
|
|
60
|
+
* Broadcasts a message to all authenticated clients
|
|
61
|
+
*
|
|
62
|
+
* Skips unauthenticated connections. Useful for user-specific notifications.
|
|
63
|
+
*
|
|
64
|
+
* @experimental
|
|
65
|
+
* @param message - Message to broadcast
|
|
66
|
+
* @param options - Broadcast options
|
|
67
|
+
* @returns Broadcast result with success/failure counts
|
|
68
|
+
*
|
|
69
|
+
* @throws {ExperimentalFeatureError} If experimental.enableBroadcast is not enabled
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* await server.experimental.broadcastAuthenticated({
|
|
74
|
+
* type: 'presence:user_online',
|
|
75
|
+
* data: { userId: 'alice', status: 'online' }
|
|
76
|
+
* });
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
broadcastAuthenticated(message: Omit<Message, 'id' | 'timestamp'>, options?: BroadcastOptions): Promise<BroadcastResult>;
|
|
80
|
+
/**
|
|
81
|
+
* Broadcasts a message to clients matching a predicate function
|
|
82
|
+
*
|
|
83
|
+
* Allows custom filtering logic for targeted broadcasts.
|
|
84
|
+
*
|
|
85
|
+
* @experimental
|
|
86
|
+
* @param predicate - Function to filter clients
|
|
87
|
+
* @param message - Message to broadcast
|
|
88
|
+
* @param options - Broadcast options
|
|
89
|
+
* @returns Broadcast result with success/failure counts
|
|
90
|
+
*
|
|
91
|
+
* @throws {ExperimentalFeatureError} If experimental.enableBroadcast is not enabled
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```typescript
|
|
95
|
+
* // Broadcast only to admin users
|
|
96
|
+
* await server.experimental.broadcastWhere(
|
|
97
|
+
* (client) => getUserRole(client.userId) === 'admin',
|
|
98
|
+
* {
|
|
99
|
+
* type: 'admin:alert',
|
|
100
|
+
* data: { message: 'Critical system event' }
|
|
101
|
+
* }
|
|
102
|
+
* );
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
broadcastWhere(predicate: ClientPredicate, message: Omit<Message, 'id' | 'timestamp'>, options?: BroadcastOptions): Promise<BroadcastResult>;
|
|
106
|
+
/**
|
|
107
|
+
* Sends a message to specific users by their user IDs
|
|
108
|
+
*
|
|
109
|
+
* Useful for direct notifications and cross-room messaging.
|
|
110
|
+
*
|
|
111
|
+
* @experimental
|
|
112
|
+
* @param userIds - Array of user IDs to send to
|
|
113
|
+
* @param message - Message to send
|
|
114
|
+
* @param options - Broadcast options
|
|
115
|
+
* @returns Broadcast result with success/failure counts
|
|
116
|
+
*
|
|
117
|
+
* @throws {ExperimentalFeatureError} If experimental.enableBroadcast is not enabled
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```typescript
|
|
121
|
+
* await server.experimental.sendToUsers(
|
|
122
|
+
* ['user123', 'user456'],
|
|
123
|
+
* {
|
|
124
|
+
* type: 'notification:mention',
|
|
125
|
+
* data: { mentionedBy: 'alice', context: 'issue-42' }
|
|
126
|
+
* }
|
|
127
|
+
* );
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
sendToUsers(userIds: string[], message: Omit<Message, 'id' | 'timestamp'>, options?: BroadcastOptions): Promise<BroadcastResult>;
|
|
131
|
+
private ensureEnabled;
|
|
132
|
+
private warnIfEnabled;
|
|
133
|
+
private trackUsage;
|
|
134
|
+
private broadcastToClients;
|
|
135
|
+
private generateId;
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=ExperimentalAPI.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExperimentalAPI.d.ts","sourceRoot":"","sources":["../../src/server/ExperimentalAPI.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EACV,yBAAyB,EACzB,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,sBAAsB,EACvB,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD;;;;;GAKG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA4B;IACnD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAA0D;IAClF,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAA0C;IACtE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA0B;IACrD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAwE;gBAEzF,OAAO,EAAE;QACnB,MAAM,EAAE,yBAAyB,CAAC;QAClC,MAAM,CAAC,EAAE;YAAE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;SAAE,CAAC;QACjE,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,IAAI,CAAC;QACrD,UAAU,EAAE,MAAM,eAAe,EAAE,CAAC;QACpC,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KACrF;IAQD;;;;;;;;;;;;;;;;;;;OAmBG;IACG,SAAS,CACb,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,GAAG,WAAW,CAAC,EAC1C,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,eAAe,CAAC;IAa3B;;;;;;;;;;;;;;;;;;;OAmBG;IACG,sBAAsB,CAC1B,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,GAAG,WAAW,CAAC,EAC1C,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,eAAe,CAAC;IAa3B;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACG,cAAc,CAClB,SAAS,EAAE,eAAe,EAC1B,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,GAAG,WAAW,CAAC,EAC1C,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,eAAe,CAAC;IAa3B;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACG,WAAW,CACf,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,GAAG,WAAW,CAAC,EAC1C,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,eAAe,CAAC;IAgB3B,OAAO,CAAC,aAAa;IASrB,OAAO,CAAC,aAAa;IASrB,OAAO,CAAC,UAAU;YAmBJ,kBAAkB;IAsEhC,OAAO,CAAC,UAAU;CAGnB"}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Experimental Broadcast APIs
|
|
4
|
+
*
|
|
5
|
+
* ⚠️ DEVELOPMENT USE ONLY
|
|
6
|
+
*
|
|
7
|
+
* This namespace provides experimental broadcast methods for rapid prototyping
|
|
8
|
+
* and internal tooling. These APIs may change or be removed without major version bumps.
|
|
9
|
+
*
|
|
10
|
+
* For production use, submit a feature request to graduate these APIs to first-class APIs.
|
|
11
|
+
*
|
|
12
|
+
* @see docs/EXPERIMENTAL_BROADCAST.md
|
|
13
|
+
* @see docs/FEATURE_REQUEST_TEMPLATE.md
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.ExperimentalAPI = void 0;
|
|
17
|
+
const experimental_js_1 = require("../types/experimental.js");
|
|
18
|
+
/**
|
|
19
|
+
* Experimental API namespace
|
|
20
|
+
*
|
|
21
|
+
* Provides broadcast methods that bypass normal room isolation.
|
|
22
|
+
* Requires explicit opt-in via configuration.
|
|
23
|
+
*/
|
|
24
|
+
class ExperimentalAPI {
|
|
25
|
+
constructor(options) {
|
|
26
|
+
this.config = options.config;
|
|
27
|
+
this.logger = options.logger;
|
|
28
|
+
this.emitMetric = options.emitMetric;
|
|
29
|
+
this.getClients = options.getClients;
|
|
30
|
+
this.sendToClient = options.sendToClient;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Broadcasts a message to all connected clients
|
|
34
|
+
*
|
|
35
|
+
* ⚠️ WARNING: This bypasses room isolation and sends to every connected client.
|
|
36
|
+
*
|
|
37
|
+
* @experimental
|
|
38
|
+
* @param message - Message to broadcast
|
|
39
|
+
* @param options - Broadcast options
|
|
40
|
+
* @returns Broadcast result with success/failure counts
|
|
41
|
+
*
|
|
42
|
+
* @throws {ExperimentalFeatureError} If experimental.enableBroadcast is not enabled
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* await server.experimental.broadcast({
|
|
47
|
+
* type: 'system:announcement',
|
|
48
|
+
* data: { message: 'Server maintenance in 5 minutes' }
|
|
49
|
+
* });
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
async broadcast(message, options) {
|
|
53
|
+
this.ensureEnabled('broadcast', 'enableBroadcast');
|
|
54
|
+
this.warnIfEnabled('broadcast()');
|
|
55
|
+
const clients = this.getClients();
|
|
56
|
+
const result = await this.broadcastToClients(clients, message, options);
|
|
57
|
+
this.trackUsage('broadcast', message.type, clients.length, message.payload);
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Broadcasts a message to all authenticated clients
|
|
62
|
+
*
|
|
63
|
+
* Skips unauthenticated connections. Useful for user-specific notifications.
|
|
64
|
+
*
|
|
65
|
+
* @experimental
|
|
66
|
+
* @param message - Message to broadcast
|
|
67
|
+
* @param options - Broadcast options
|
|
68
|
+
* @returns Broadcast result with success/failure counts
|
|
69
|
+
*
|
|
70
|
+
* @throws {ExperimentalFeatureError} If experimental.enableBroadcast is not enabled
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* await server.experimental.broadcastAuthenticated({
|
|
75
|
+
* type: 'presence:user_online',
|
|
76
|
+
* data: { userId: 'alice', status: 'online' }
|
|
77
|
+
* });
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
async broadcastAuthenticated(message, options) {
|
|
81
|
+
this.ensureEnabled('broadcastAuthenticated', 'enableBroadcast');
|
|
82
|
+
this.warnIfEnabled('broadcastAuthenticated()');
|
|
83
|
+
const clients = this.getClients().filter(client => client.authenticated);
|
|
84
|
+
const result = await this.broadcastToClients(clients, message, options);
|
|
85
|
+
this.trackUsage('broadcastAuthenticated', message.type, clients.length, message.payload);
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Broadcasts a message to clients matching a predicate function
|
|
90
|
+
*
|
|
91
|
+
* Allows custom filtering logic for targeted broadcasts.
|
|
92
|
+
*
|
|
93
|
+
* @experimental
|
|
94
|
+
* @param predicate - Function to filter clients
|
|
95
|
+
* @param message - Message to broadcast
|
|
96
|
+
* @param options - Broadcast options
|
|
97
|
+
* @returns Broadcast result with success/failure counts
|
|
98
|
+
*
|
|
99
|
+
* @throws {ExperimentalFeatureError} If experimental.enableBroadcast is not enabled
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```typescript
|
|
103
|
+
* // Broadcast only to admin users
|
|
104
|
+
* await server.experimental.broadcastWhere(
|
|
105
|
+
* (client) => getUserRole(client.userId) === 'admin',
|
|
106
|
+
* {
|
|
107
|
+
* type: 'admin:alert',
|
|
108
|
+
* data: { message: 'Critical system event' }
|
|
109
|
+
* }
|
|
110
|
+
* );
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
async broadcastWhere(predicate, message, options) {
|
|
114
|
+
this.ensureEnabled('broadcastWhere', 'enableBroadcast');
|
|
115
|
+
this.warnIfEnabled('broadcastWhere()');
|
|
116
|
+
const clients = this.getClients().filter(predicate);
|
|
117
|
+
const result = await this.broadcastToClients(clients, message, options);
|
|
118
|
+
this.trackUsage('broadcastWhere', message.type, clients.length, message.payload);
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Sends a message to specific users by their user IDs
|
|
123
|
+
*
|
|
124
|
+
* Useful for direct notifications and cross-room messaging.
|
|
125
|
+
*
|
|
126
|
+
* @experimental
|
|
127
|
+
* @param userIds - Array of user IDs to send to
|
|
128
|
+
* @param message - Message to send
|
|
129
|
+
* @param options - Broadcast options
|
|
130
|
+
* @returns Broadcast result with success/failure counts
|
|
131
|
+
*
|
|
132
|
+
* @throws {ExperimentalFeatureError} If experimental.enableBroadcast is not enabled
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```typescript
|
|
136
|
+
* await server.experimental.sendToUsers(
|
|
137
|
+
* ['user123', 'user456'],
|
|
138
|
+
* {
|
|
139
|
+
* type: 'notification:mention',
|
|
140
|
+
* data: { mentionedBy: 'alice', context: 'issue-42' }
|
|
141
|
+
* }
|
|
142
|
+
* );
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
145
|
+
async sendToUsers(userIds, message, options) {
|
|
146
|
+
this.ensureEnabled('sendToUsers', 'enableBroadcast');
|
|
147
|
+
this.warnIfEnabled('sendToUsers()');
|
|
148
|
+
const userIdSet = new Set(userIds);
|
|
149
|
+
const clients = this.getClients().filter(client => userIdSet.has(client.userId));
|
|
150
|
+
const result = await this.broadcastToClients(clients, message, options);
|
|
151
|
+
this.trackUsage('sendToUsers', message.type, clients.length, message.payload);
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
// Private helper methods
|
|
155
|
+
ensureEnabled(feature, configKey) {
|
|
156
|
+
if (!this.config[configKey]) {
|
|
157
|
+
throw new experimental_js_1.ExperimentalFeatureError(feature, configKey);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
warnIfEnabled(method) {
|
|
161
|
+
if (this.config.warnOnExperimentalUse !== false) {
|
|
162
|
+
this.logger?.warn(`⚠️ Experimental API: server.experimental.${method} is for development only. ` +
|
|
163
|
+
`See docs/EXPERIMENTAL_BROADCAST.md for details.`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
trackUsage(method, messageType, recipientCount, payload) {
|
|
167
|
+
if (this.config.trackUsageMetrics && this.emitMetric) {
|
|
168
|
+
const payloadSize = payload ? JSON.stringify(payload).length : undefined;
|
|
169
|
+
this.emitMetric({
|
|
170
|
+
method,
|
|
171
|
+
messageType,
|
|
172
|
+
recipientCount,
|
|
173
|
+
timestamp: Date.now(),
|
|
174
|
+
payloadSize
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async broadcastToClients(clients, message, options) {
|
|
179
|
+
const startTime = Date.now();
|
|
180
|
+
const opts = {
|
|
181
|
+
continueOnError: options?.continueOnError ?? true,
|
|
182
|
+
sendTimeout: options?.sendTimeout ?? 5000,
|
|
183
|
+
includeErrors: options?.includeErrors ?? false
|
|
184
|
+
};
|
|
185
|
+
const fullMessage = {
|
|
186
|
+
id: this.generateId(),
|
|
187
|
+
timestamp: Date.now(),
|
|
188
|
+
type: message.type,
|
|
189
|
+
payload: message.payload
|
|
190
|
+
};
|
|
191
|
+
let sent = 0;
|
|
192
|
+
let failed = 0;
|
|
193
|
+
const errors = [];
|
|
194
|
+
const sendPromises = clients.map(async (client) => {
|
|
195
|
+
try {
|
|
196
|
+
// Create timeout promise
|
|
197
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
198
|
+
setTimeout(() => reject(new Error('Send timeout')), opts.sendTimeout);
|
|
199
|
+
});
|
|
200
|
+
// Convert Message to Record<string, unknown> for sendToClient
|
|
201
|
+
const messageRecord = {
|
|
202
|
+
id: fullMessage.id,
|
|
203
|
+
type: fullMessage.type,
|
|
204
|
+
payload: fullMessage.payload,
|
|
205
|
+
timestamp: fullMessage.timestamp
|
|
206
|
+
};
|
|
207
|
+
// Race between send and timeout
|
|
208
|
+
await Promise.race([
|
|
209
|
+
this.sendToClient(client.id, messageRecord),
|
|
210
|
+
timeoutPromise
|
|
211
|
+
]);
|
|
212
|
+
sent++;
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
failed++;
|
|
216
|
+
if (opts.includeErrors) {
|
|
217
|
+
errors.push({
|
|
218
|
+
clientId: client.id,
|
|
219
|
+
error: error
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
if (!opts.continueOnError) {
|
|
223
|
+
throw error;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
await Promise.all(sendPromises);
|
|
228
|
+
return {
|
|
229
|
+
sent,
|
|
230
|
+
failed,
|
|
231
|
+
errors,
|
|
232
|
+
durationMs: Date.now() - startTime
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
generateId() {
|
|
236
|
+
return `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
exports.ExperimentalAPI = ExperimentalAPI;
|
|
@@ -3,7 +3,7 @@ import type { IAuthAdapter } from '../abstractions/AuthAdapter.js';
|
|
|
3
3
|
import type { IStorageAdapter } from '../abstractions/StorageAdapter.js';
|
|
4
4
|
import type { RoomManager } from '../abstractions/RoomManager.js';
|
|
5
5
|
import type { LockManager } from '../abstractions/LockManager.js';
|
|
6
|
-
import type { RoomConfig } from '../types/index.js';
|
|
6
|
+
import type { RoomConfig, ExperimentalFeatureConfig } from '../types/index.js';
|
|
7
7
|
import { BaseServer } from './BaseServer.js';
|
|
8
8
|
import type { Server as HttpServer } from 'http';
|
|
9
9
|
import type { Server as HttpsServer } from 'https';
|
|
@@ -18,6 +18,7 @@ export declare class ServerBuilder {
|
|
|
18
18
|
private httpServer?;
|
|
19
19
|
private webSocketPath?;
|
|
20
20
|
private webSocketServer?;
|
|
21
|
+
private experimentalFeatures?;
|
|
21
22
|
withTransport(transport: ITransportAdapter): this;
|
|
22
23
|
withAuth(auth: IAuthAdapter): this;
|
|
23
24
|
withStorage(storage: IStorageAdapter): this;
|
|
@@ -27,6 +28,15 @@ export declare class ServerBuilder {
|
|
|
27
28
|
withHttpServer(server: HttpServer | HttpsServer): this;
|
|
28
29
|
withWebSocketPath(path: string): this;
|
|
29
30
|
withWebSocketServer(wss: WebSocketServer): this;
|
|
31
|
+
/**
|
|
32
|
+
* Enable experimental features
|
|
33
|
+
*
|
|
34
|
+
* ⚠️ WARNING: Experimental features are for development only.
|
|
35
|
+
* APIs may change or be removed without major version bumps.
|
|
36
|
+
*
|
|
37
|
+
* @see docs/EXPERIMENTAL_BROADCAST.md
|
|
38
|
+
*/
|
|
39
|
+
withExperimentalFeatures(features: ExperimentalFeatureConfig): this;
|
|
30
40
|
build(): BaseServer;
|
|
31
41
|
}
|
|
32
42
|
//# sourceMappingURL=ServerBuilder.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ServerBuilder.d.ts","sourceRoot":"","sources":["../../src/server/ServerBuilder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;AAC7E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"ServerBuilder.d.ts","sourceRoot":"","sources":["../../src/server/ServerBuilder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;AAC7E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAAE,UAAU,EAAqB,MAAM,iBAAiB,CAAC;AAChE,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,MAAM,CAAC;AACjD,OAAO,KAAK,EAAE,MAAM,IAAI,WAAW,EAAE,MAAM,OAAO,CAAC;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,IAAI,CAAC;AAE1C,qBAAa,aAAa;IACxB,OAAO,CAAC,SAAS,CAAC,CAAoB;IACtC,OAAO,CAAC,IAAI,CAAC,CAAe;IAC5B,OAAO,CAAC,OAAO,CAAC,CAAkB;IAClC,OAAO,CAAC,WAAW,CAAC,CAAc;IAClC,OAAO,CAAC,WAAW,CAAC,CAAc;IAClC,OAAO,CAAC,iBAAiB,CAAC,CAAsB;IAChD,OAAO,CAAC,UAAU,CAAC,CAA2B;IAC9C,OAAO,CAAC,aAAa,CAAC,CAAS;IAC/B,OAAO,CAAC,eAAe,CAAC,CAAkB;IAC1C,OAAO,CAAC,oBAAoB,CAAC,CAA4B;IAEzD,aAAa,CAAC,SAAS,EAAE,iBAAiB,GAAG,IAAI;IAKjD,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IAKlC,WAAW,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI;IAK3C,eAAe,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI;IAK/C,eAAe,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI;IAK/C,qBAAqB,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,IAAI;IAKxD,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,WAAW,GAAG,IAAI;IAKtD,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKrC,mBAAmB,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI;IAK/C;;;;;;;OAOG;IACH,wBAAwB,CAAC,QAAQ,EAAE,yBAAyB,GAAG,IAAI;IAKnE,KAAK,IAAI,UAAU;CAoCpB"}
|
|
@@ -39,6 +39,18 @@ class ServerBuilder {
|
|
|
39
39
|
this.webSocketServer = wss;
|
|
40
40
|
return this;
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Enable experimental features
|
|
44
|
+
*
|
|
45
|
+
* ⚠️ WARNING: Experimental features are for development only.
|
|
46
|
+
* APIs may change or be removed without major version bumps.
|
|
47
|
+
*
|
|
48
|
+
* @see docs/EXPERIMENTAL_BROADCAST.md
|
|
49
|
+
*/
|
|
50
|
+
withExperimentalFeatures(features) {
|
|
51
|
+
this.experimentalFeatures = features;
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
42
54
|
build() {
|
|
43
55
|
if (!this.transport) {
|
|
44
56
|
throw new Error('Transport adapter is required');
|
|
@@ -62,7 +74,12 @@ class ServerBuilder {
|
|
|
62
74
|
},
|
|
63
75
|
httpServer: this.httpServer,
|
|
64
76
|
webSocketPath: this.webSocketPath,
|
|
65
|
-
webSocketServer: this.webSocketServer
|
|
77
|
+
webSocketServer: this.webSocketServer,
|
|
78
|
+
experimental: this.experimentalFeatures || {
|
|
79
|
+
enableBroadcast: false,
|
|
80
|
+
warnOnExperimentalUse: true,
|
|
81
|
+
trackUsageMetrics: false
|
|
82
|
+
}
|
|
66
83
|
};
|
|
67
84
|
return new BaseServer_js_1.BaseServer(config);
|
|
68
85
|
}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACzG,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACzG,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC"}
|
package/dist/server/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ServerBuilder = exports.BaseServer = void 0;
|
|
3
|
+
exports.ExperimentalAPI = exports.ServerBuilder = exports.BaseServer = void 0;
|
|
4
4
|
// Server-side implementations
|
|
5
5
|
var BaseServer_js_1 = require("./BaseServer.js");
|
|
6
6
|
Object.defineProperty(exports, "BaseServer", { enumerable: true, get: function () { return BaseServer_js_1.BaseServer; } });
|
|
7
7
|
var ServerBuilder_js_1 = require("./ServerBuilder.js");
|
|
8
8
|
Object.defineProperty(exports, "ServerBuilder", { enumerable: true, get: function () { return ServerBuilder_js_1.ServerBuilder; } });
|
|
9
|
+
var ExperimentalAPI_js_1 = require("./ExperimentalAPI.js");
|
|
10
|
+
Object.defineProperty(exports, "ExperimentalAPI", { enumerable: true, get: function () { return ExperimentalAPI_js_1.ExperimentalAPI; } });
|