orchestrator-client 5.7.1 → 5.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +85 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +80 -2
- package/dist/index.d.ts +80 -2
- package/dist/index.js +85 -5
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.d.cts
CHANGED
|
@@ -511,6 +511,15 @@ interface OrchestratorClientOptions {
|
|
|
511
511
|
timeoutMs?: number;
|
|
512
512
|
/** Max retry attempts on transient failures (default: 3). */
|
|
513
513
|
maxRetries?: number;
|
|
514
|
+
/**
|
|
515
|
+
* Locale tag sent as `X-Locale` on every request (e.g. `"hu-hu"`, `"en-us"`).
|
|
516
|
+
*
|
|
517
|
+
* When set the API returns translated content where available.
|
|
518
|
+
* When not set the API returns its default (English) content.
|
|
519
|
+
* Per-call locale overrides (on `listTasks`, `getTaskStatus`, etc.)
|
|
520
|
+
* take precedence over this global setting.
|
|
521
|
+
*/
|
|
522
|
+
locale?: string;
|
|
514
523
|
/**
|
|
515
524
|
* Custom fetch implementation override.
|
|
516
525
|
*
|
|
@@ -538,6 +547,7 @@ declare class OrchestratorAsync {
|
|
|
538
547
|
protected _getToken: (() => string | Promise<string>) | undefined;
|
|
539
548
|
protected _timeoutMs: number;
|
|
540
549
|
protected _maxRetries: number;
|
|
550
|
+
protected _locale: string | undefined;
|
|
541
551
|
protected _fetch: typeof globalThis.fetch;
|
|
542
552
|
protected _abortController: AbortController | null;
|
|
543
553
|
constructor(opts?: OrchestratorClientOptions);
|
|
@@ -687,7 +697,7 @@ declare class OrchestratorAsync {
|
|
|
687
697
|
orderDirection?: string;
|
|
688
698
|
}): Promise<ErrorEventListResult>;
|
|
689
699
|
getErrorDetail(errorId: string): Promise<ErrorEventDetail>;
|
|
690
|
-
getErrorStats(since?: string): Promise<ErrorStatsResult>;
|
|
700
|
+
getErrorStats(since?: string, topN?: number): Promise<ErrorStatsResult>;
|
|
691
701
|
countErrors(since?: string): Promise<ErrorCountResult>;
|
|
692
702
|
purgeErrors(): Promise<ErrorPurgeResult>;
|
|
693
703
|
health(): Promise<HealthStatus>;
|
|
@@ -926,6 +936,19 @@ interface RealtimeClientOptions {
|
|
|
926
936
|
getToken?: (() => string | Promise<string>) | string;
|
|
927
937
|
/** Whether to auto-connect on construction (default: false). */
|
|
928
938
|
autoConnect?: boolean;
|
|
939
|
+
/**
|
|
940
|
+
* Client identifier sent as the `client_id` query parameter at connection
|
|
941
|
+
* time. The server uses this for logging/diagnostics.
|
|
942
|
+
* Defaults to `"orchestrator-client"`.
|
|
943
|
+
*/
|
|
944
|
+
clientId?: string;
|
|
945
|
+
/**
|
|
946
|
+
* Locale tag sent as the `locale` query parameter at connection time
|
|
947
|
+
* (e.g. `"hu-hu"`, `"en-us"`). When set the server automatically joins
|
|
948
|
+
* the client into the matching `locale:<tag>` room so translation-ready
|
|
949
|
+
* events are delivered without a separate `subscribeLocale()` call.
|
|
950
|
+
*/
|
|
951
|
+
locale?: string;
|
|
929
952
|
}
|
|
930
953
|
/**
|
|
931
954
|
* Socket.IO-based realtime client for receiving orchestrator events.
|
|
@@ -933,20 +956,40 @@ interface RealtimeClientOptions {
|
|
|
933
956
|
* The server wraps all domain events in a `message` Socket.IO event with an
|
|
934
957
|
* inner `event_type` field. This client unwraps the envelope and dispatches
|
|
935
958
|
* to registered handlers by event_type.
|
|
959
|
+
*
|
|
960
|
+
* ## Two subscription APIs — choose one or mix them:
|
|
961
|
+
*
|
|
962
|
+
* ### 1. Event-type handlers via `on(event, handler)`
|
|
963
|
+
* Registers a callback for a specific event type string (e.g.
|
|
964
|
+
* `"task_status_changed"`). You still need to join the relevant rooms
|
|
965
|
+
* manually via `subscribeTask()`, `subscribeEvents()`, etc.
|
|
966
|
+
*
|
|
967
|
+
* ### 2. Room-scoped subscribers via `subscribe(handler, rooms)`
|
|
968
|
+
* Mirrors the WebSocketProvider pattern used in the webui. Each call
|
|
969
|
+
* returns an opaque subscription id. Calling `unsubscribe(id)` removes it.
|
|
970
|
+
* Room membership is kept in sync automatically: the client joins the union
|
|
971
|
+
* of all subscribers' rooms and leaves rooms that are no longer needed.
|
|
972
|
+
* The handler receives the unwrapped event dict for every event from those
|
|
973
|
+
* rooms, regardless of type.
|
|
936
974
|
*/
|
|
937
975
|
declare class RealtimeClient {
|
|
938
976
|
private _baseUrl;
|
|
939
977
|
private _socketOptions;
|
|
940
978
|
private _getToken?;
|
|
979
|
+
private _clientId;
|
|
980
|
+
private _locale?;
|
|
941
981
|
private _socket;
|
|
942
982
|
private _handlers;
|
|
943
983
|
private _connected;
|
|
984
|
+
private _subscriptions;
|
|
985
|
+
private _currentRooms;
|
|
986
|
+
private _subIdCounter;
|
|
944
987
|
constructor(baseUrl: string, opts: RealtimeClientOptions);
|
|
945
988
|
get connected(): boolean;
|
|
946
989
|
connect(): Promise<void>;
|
|
947
990
|
disconnect(): Promise<void>;
|
|
948
991
|
/**
|
|
949
|
-
* Dispatch a message envelope to registered handlers.
|
|
992
|
+
* Dispatch a message envelope to registered handlers and subscribers.
|
|
950
993
|
* The server sends: socket.emit("message", {type: "message", event: {..., event_type: "...", ...}})
|
|
951
994
|
*/
|
|
952
995
|
private _dispatch;
|
|
@@ -989,6 +1032,41 @@ declare class RealtimeClient {
|
|
|
989
1032
|
* Leave arbitrary rooms by name.
|
|
990
1033
|
*/
|
|
991
1034
|
leaveRooms(rooms: string[]): void;
|
|
1035
|
+
/**
|
|
1036
|
+
* Register a subscriber that receives all events from the given rooms.
|
|
1037
|
+
*
|
|
1038
|
+
* Room membership is managed automatically: the client joins the union
|
|
1039
|
+
* of all active subscribers' rooms and leaves rooms that are no longer
|
|
1040
|
+
* needed when the last subscriber referencing them is removed.
|
|
1041
|
+
*
|
|
1042
|
+
* The `handler` receives the unwrapped event dict (same object that
|
|
1043
|
+
* `on()` handlers receive).
|
|
1044
|
+
*
|
|
1045
|
+
* Returns an opaque subscription id that must be passed to
|
|
1046
|
+
* `unsubscribe()` to remove the subscription.
|
|
1047
|
+
*
|
|
1048
|
+
* Example — mirror the webui's per-component subscription pattern:
|
|
1049
|
+
*
|
|
1050
|
+
* const id = rt.subscribe((event) => {
|
|
1051
|
+
* if (event.event_type === "task_status_changed") { ... }
|
|
1052
|
+
* }, [`task:${taskId}`]);
|
|
1053
|
+
*
|
|
1054
|
+
* // later, on cleanup:
|
|
1055
|
+
* rt.unsubscribe(id);
|
|
1056
|
+
*/
|
|
1057
|
+
subscribe(handler: EventHandler, rooms: string[]): string;
|
|
1058
|
+
/**
|
|
1059
|
+
* Remove a subscription registered via `subscribe()`.
|
|
1060
|
+
*
|
|
1061
|
+
* Rooms that are no longer referenced by any remaining subscriber are
|
|
1062
|
+
* left automatically.
|
|
1063
|
+
*/
|
|
1064
|
+
unsubscribe(id: string): void;
|
|
1065
|
+
/**
|
|
1066
|
+
* Diff the union of all subscribers' rooms against the currently joined
|
|
1067
|
+
* rooms and emit `join`/`leave` for the delta. No-ops when not connected.
|
|
1068
|
+
*/
|
|
1069
|
+
private _syncRooms;
|
|
992
1070
|
/**
|
|
993
1071
|
* Send a ping to the server.
|
|
994
1072
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -511,6 +511,15 @@ interface OrchestratorClientOptions {
|
|
|
511
511
|
timeoutMs?: number;
|
|
512
512
|
/** Max retry attempts on transient failures (default: 3). */
|
|
513
513
|
maxRetries?: number;
|
|
514
|
+
/**
|
|
515
|
+
* Locale tag sent as `X-Locale` on every request (e.g. `"hu-hu"`, `"en-us"`).
|
|
516
|
+
*
|
|
517
|
+
* When set the API returns translated content where available.
|
|
518
|
+
* When not set the API returns its default (English) content.
|
|
519
|
+
* Per-call locale overrides (on `listTasks`, `getTaskStatus`, etc.)
|
|
520
|
+
* take precedence over this global setting.
|
|
521
|
+
*/
|
|
522
|
+
locale?: string;
|
|
514
523
|
/**
|
|
515
524
|
* Custom fetch implementation override.
|
|
516
525
|
*
|
|
@@ -538,6 +547,7 @@ declare class OrchestratorAsync {
|
|
|
538
547
|
protected _getToken: (() => string | Promise<string>) | undefined;
|
|
539
548
|
protected _timeoutMs: number;
|
|
540
549
|
protected _maxRetries: number;
|
|
550
|
+
protected _locale: string | undefined;
|
|
541
551
|
protected _fetch: typeof globalThis.fetch;
|
|
542
552
|
protected _abortController: AbortController | null;
|
|
543
553
|
constructor(opts?: OrchestratorClientOptions);
|
|
@@ -687,7 +697,7 @@ declare class OrchestratorAsync {
|
|
|
687
697
|
orderDirection?: string;
|
|
688
698
|
}): Promise<ErrorEventListResult>;
|
|
689
699
|
getErrorDetail(errorId: string): Promise<ErrorEventDetail>;
|
|
690
|
-
getErrorStats(since?: string): Promise<ErrorStatsResult>;
|
|
700
|
+
getErrorStats(since?: string, topN?: number): Promise<ErrorStatsResult>;
|
|
691
701
|
countErrors(since?: string): Promise<ErrorCountResult>;
|
|
692
702
|
purgeErrors(): Promise<ErrorPurgeResult>;
|
|
693
703
|
health(): Promise<HealthStatus>;
|
|
@@ -926,6 +936,19 @@ interface RealtimeClientOptions {
|
|
|
926
936
|
getToken?: (() => string | Promise<string>) | string;
|
|
927
937
|
/** Whether to auto-connect on construction (default: false). */
|
|
928
938
|
autoConnect?: boolean;
|
|
939
|
+
/**
|
|
940
|
+
* Client identifier sent as the `client_id` query parameter at connection
|
|
941
|
+
* time. The server uses this for logging/diagnostics.
|
|
942
|
+
* Defaults to `"orchestrator-client"`.
|
|
943
|
+
*/
|
|
944
|
+
clientId?: string;
|
|
945
|
+
/**
|
|
946
|
+
* Locale tag sent as the `locale` query parameter at connection time
|
|
947
|
+
* (e.g. `"hu-hu"`, `"en-us"`). When set the server automatically joins
|
|
948
|
+
* the client into the matching `locale:<tag>` room so translation-ready
|
|
949
|
+
* events are delivered without a separate `subscribeLocale()` call.
|
|
950
|
+
*/
|
|
951
|
+
locale?: string;
|
|
929
952
|
}
|
|
930
953
|
/**
|
|
931
954
|
* Socket.IO-based realtime client for receiving orchestrator events.
|
|
@@ -933,20 +956,40 @@ interface RealtimeClientOptions {
|
|
|
933
956
|
* The server wraps all domain events in a `message` Socket.IO event with an
|
|
934
957
|
* inner `event_type` field. This client unwraps the envelope and dispatches
|
|
935
958
|
* to registered handlers by event_type.
|
|
959
|
+
*
|
|
960
|
+
* ## Two subscription APIs — choose one or mix them:
|
|
961
|
+
*
|
|
962
|
+
* ### 1. Event-type handlers via `on(event, handler)`
|
|
963
|
+
* Registers a callback for a specific event type string (e.g.
|
|
964
|
+
* `"task_status_changed"`). You still need to join the relevant rooms
|
|
965
|
+
* manually via `subscribeTask()`, `subscribeEvents()`, etc.
|
|
966
|
+
*
|
|
967
|
+
* ### 2. Room-scoped subscribers via `subscribe(handler, rooms)`
|
|
968
|
+
* Mirrors the WebSocketProvider pattern used in the webui. Each call
|
|
969
|
+
* returns an opaque subscription id. Calling `unsubscribe(id)` removes it.
|
|
970
|
+
* Room membership is kept in sync automatically: the client joins the union
|
|
971
|
+
* of all subscribers' rooms and leaves rooms that are no longer needed.
|
|
972
|
+
* The handler receives the unwrapped event dict for every event from those
|
|
973
|
+
* rooms, regardless of type.
|
|
936
974
|
*/
|
|
937
975
|
declare class RealtimeClient {
|
|
938
976
|
private _baseUrl;
|
|
939
977
|
private _socketOptions;
|
|
940
978
|
private _getToken?;
|
|
979
|
+
private _clientId;
|
|
980
|
+
private _locale?;
|
|
941
981
|
private _socket;
|
|
942
982
|
private _handlers;
|
|
943
983
|
private _connected;
|
|
984
|
+
private _subscriptions;
|
|
985
|
+
private _currentRooms;
|
|
986
|
+
private _subIdCounter;
|
|
944
987
|
constructor(baseUrl: string, opts: RealtimeClientOptions);
|
|
945
988
|
get connected(): boolean;
|
|
946
989
|
connect(): Promise<void>;
|
|
947
990
|
disconnect(): Promise<void>;
|
|
948
991
|
/**
|
|
949
|
-
* Dispatch a message envelope to registered handlers.
|
|
992
|
+
* Dispatch a message envelope to registered handlers and subscribers.
|
|
950
993
|
* The server sends: socket.emit("message", {type: "message", event: {..., event_type: "...", ...}})
|
|
951
994
|
*/
|
|
952
995
|
private _dispatch;
|
|
@@ -989,6 +1032,41 @@ declare class RealtimeClient {
|
|
|
989
1032
|
* Leave arbitrary rooms by name.
|
|
990
1033
|
*/
|
|
991
1034
|
leaveRooms(rooms: string[]): void;
|
|
1035
|
+
/**
|
|
1036
|
+
* Register a subscriber that receives all events from the given rooms.
|
|
1037
|
+
*
|
|
1038
|
+
* Room membership is managed automatically: the client joins the union
|
|
1039
|
+
* of all active subscribers' rooms and leaves rooms that are no longer
|
|
1040
|
+
* needed when the last subscriber referencing them is removed.
|
|
1041
|
+
*
|
|
1042
|
+
* The `handler` receives the unwrapped event dict (same object that
|
|
1043
|
+
* `on()` handlers receive).
|
|
1044
|
+
*
|
|
1045
|
+
* Returns an opaque subscription id that must be passed to
|
|
1046
|
+
* `unsubscribe()` to remove the subscription.
|
|
1047
|
+
*
|
|
1048
|
+
* Example — mirror the webui's per-component subscription pattern:
|
|
1049
|
+
*
|
|
1050
|
+
* const id = rt.subscribe((event) => {
|
|
1051
|
+
* if (event.event_type === "task_status_changed") { ... }
|
|
1052
|
+
* }, [`task:${taskId}`]);
|
|
1053
|
+
*
|
|
1054
|
+
* // later, on cleanup:
|
|
1055
|
+
* rt.unsubscribe(id);
|
|
1056
|
+
*/
|
|
1057
|
+
subscribe(handler: EventHandler, rooms: string[]): string;
|
|
1058
|
+
/**
|
|
1059
|
+
* Remove a subscription registered via `subscribe()`.
|
|
1060
|
+
*
|
|
1061
|
+
* Rooms that are no longer referenced by any remaining subscriber are
|
|
1062
|
+
* left automatically.
|
|
1063
|
+
*/
|
|
1064
|
+
unsubscribe(id: string): void;
|
|
1065
|
+
/**
|
|
1066
|
+
* Diff the union of all subscribers' rooms against the currently joined
|
|
1067
|
+
* rooms and emit `join`/`leave` for the delta. No-ops when not connected.
|
|
1068
|
+
*/
|
|
1069
|
+
private _syncRooms;
|
|
992
1070
|
/**
|
|
993
1071
|
* Send a ping to the server.
|
|
994
1072
|
*/
|
package/dist/index.js
CHANGED
|
@@ -90,6 +90,7 @@ var OrchestratorAsync = class {
|
|
|
90
90
|
this._getToken = opts.getToken;
|
|
91
91
|
this._timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
92
92
|
this._maxRetries = opts.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
93
|
+
this._locale = opts.locale;
|
|
93
94
|
this._fetch = opts.fetch ?? globalThis.fetch;
|
|
94
95
|
if (opts.insecure && !opts.fetch && typeof process !== "undefined" && process.versions?.node) {
|
|
95
96
|
this._insecure = true;
|
|
@@ -114,6 +115,9 @@ var OrchestratorAsync = class {
|
|
|
114
115
|
headers.Authorization = `Bearer ${token}`;
|
|
115
116
|
}
|
|
116
117
|
}
|
|
118
|
+
if (this._locale) {
|
|
119
|
+
headers["X-Locale"] = this._locale;
|
|
120
|
+
}
|
|
117
121
|
return headers;
|
|
118
122
|
}
|
|
119
123
|
async _request(method, path, opts) {
|
|
@@ -344,10 +348,9 @@ var OrchestratorAsync = class {
|
|
|
344
348
|
);
|
|
345
349
|
}
|
|
346
350
|
async getTaskCompactions(taskId) {
|
|
347
|
-
|
|
351
|
+
return this._get("/task/compactions", {
|
|
348
352
|
task_id: taskId
|
|
349
353
|
});
|
|
350
|
-
return data.compactions ?? [];
|
|
351
354
|
}
|
|
352
355
|
async getTaskJournal(taskId) {
|
|
353
356
|
const data = await this._get("/task/journal", {
|
|
@@ -924,8 +927,10 @@ var OrchestratorAsync = class {
|
|
|
924
927
|
async getErrorDetail(errorId) {
|
|
925
928
|
return this._get(`/errors/${errorId}`);
|
|
926
929
|
}
|
|
927
|
-
async getErrorStats(since) {
|
|
928
|
-
const params = {
|
|
930
|
+
async getErrorStats(since, topN = 10) {
|
|
931
|
+
const params = {
|
|
932
|
+
top_n: topN
|
|
933
|
+
};
|
|
929
934
|
if (since) params.since = since;
|
|
930
935
|
return this._get("/errors/stats", params);
|
|
931
936
|
}
|
|
@@ -1635,9 +1640,15 @@ var RealtimeClient = class {
|
|
|
1635
1640
|
this._socket = null;
|
|
1636
1641
|
this._handlers = /* @__PURE__ */ new Map();
|
|
1637
1642
|
this._connected = false;
|
|
1643
|
+
// Multi-subscriber room-diffing state (mirrors WebSocketProvider)
|
|
1644
|
+
this._subscriptions = /* @__PURE__ */ new Map();
|
|
1645
|
+
this._currentRooms = /* @__PURE__ */ new Set();
|
|
1646
|
+
this._subIdCounter = 0;
|
|
1638
1647
|
this._baseUrl = baseUrl.replace(/\/+$/, "");
|
|
1639
1648
|
this._socketOptions = opts.socketOptions ?? {};
|
|
1640
1649
|
this._getToken = opts.getToken;
|
|
1650
|
+
this._clientId = opts.clientId ?? "orchestrator-client";
|
|
1651
|
+
this._locale = opts.locale;
|
|
1641
1652
|
if (opts.autoConnect) {
|
|
1642
1653
|
this.connect();
|
|
1643
1654
|
}
|
|
@@ -1650,18 +1661,26 @@ var RealtimeClient = class {
|
|
|
1650
1661
|
if (this._getToken) {
|
|
1651
1662
|
auth.token = typeof this._getToken === "function" ? await this._getToken() : this._getToken;
|
|
1652
1663
|
}
|
|
1664
|
+
const query = { client_id: this._clientId };
|
|
1665
|
+
if (this._locale) {
|
|
1666
|
+
query.locale = this._locale;
|
|
1667
|
+
}
|
|
1653
1668
|
const { io } = await import("socket.io-client");
|
|
1654
1669
|
this._socket = io(this._baseUrl, {
|
|
1655
1670
|
path: "/socket.io",
|
|
1656
1671
|
auth,
|
|
1672
|
+
query,
|
|
1657
1673
|
transports: ["websocket", "polling"],
|
|
1658
1674
|
...this._socketOptions
|
|
1659
1675
|
});
|
|
1660
1676
|
this._socket.on("connect", () => {
|
|
1661
1677
|
this._connected = true;
|
|
1678
|
+
this._currentRooms = /* @__PURE__ */ new Set();
|
|
1679
|
+
this._syncRooms();
|
|
1662
1680
|
});
|
|
1663
1681
|
this._socket.on("disconnect", () => {
|
|
1664
1682
|
this._connected = false;
|
|
1683
|
+
this._currentRooms = /* @__PURE__ */ new Set();
|
|
1665
1684
|
});
|
|
1666
1685
|
this._socket.on("message", (payload) => this._dispatch(payload));
|
|
1667
1686
|
return new Promise((resolve, reject) => {
|
|
@@ -1684,7 +1703,7 @@ var RealtimeClient = class {
|
|
|
1684
1703
|
this._connected = false;
|
|
1685
1704
|
}
|
|
1686
1705
|
/**
|
|
1687
|
-
* Dispatch a message envelope to registered handlers.
|
|
1706
|
+
* Dispatch a message envelope to registered handlers and subscribers.
|
|
1688
1707
|
* The server sends: socket.emit("message", {type: "message", event: {..., event_type: "...", ...}})
|
|
1689
1708
|
*/
|
|
1690
1709
|
_dispatch(payload) {
|
|
@@ -1698,6 +1717,9 @@ var RealtimeClient = class {
|
|
|
1698
1717
|
h(event);
|
|
1699
1718
|
}
|
|
1700
1719
|
}
|
|
1720
|
+
for (const sub of this._subscriptions.values()) {
|
|
1721
|
+
sub.handler(event);
|
|
1722
|
+
}
|
|
1701
1723
|
}
|
|
1702
1724
|
/**
|
|
1703
1725
|
* Subscribe to realtime events for a specific task.
|
|
@@ -1767,6 +1789,64 @@ var RealtimeClient = class {
|
|
|
1767
1789
|
if (!this._socket) throw new Error("RealtimeClient not connected");
|
|
1768
1790
|
this._socket.emit("leave", { rooms });
|
|
1769
1791
|
}
|
|
1792
|
+
// ------------------------------------------------------------------
|
|
1793
|
+
// Multi-subscriber API (mirrors WebSocketProvider room-diffing)
|
|
1794
|
+
// ------------------------------------------------------------------
|
|
1795
|
+
/**
|
|
1796
|
+
* Register a subscriber that receives all events from the given rooms.
|
|
1797
|
+
*
|
|
1798
|
+
* Room membership is managed automatically: the client joins the union
|
|
1799
|
+
* of all active subscribers' rooms and leaves rooms that are no longer
|
|
1800
|
+
* needed when the last subscriber referencing them is removed.
|
|
1801
|
+
*
|
|
1802
|
+
* The `handler` receives the unwrapped event dict (same object that
|
|
1803
|
+
* `on()` handlers receive).
|
|
1804
|
+
*
|
|
1805
|
+
* Returns an opaque subscription id that must be passed to
|
|
1806
|
+
* `unsubscribe()` to remove the subscription.
|
|
1807
|
+
*
|
|
1808
|
+
* Example — mirror the webui's per-component subscription pattern:
|
|
1809
|
+
*
|
|
1810
|
+
* const id = rt.subscribe((event) => {
|
|
1811
|
+
* if (event.event_type === "task_status_changed") { ... }
|
|
1812
|
+
* }, [`task:${taskId}`]);
|
|
1813
|
+
*
|
|
1814
|
+
* // later, on cleanup:
|
|
1815
|
+
* rt.unsubscribe(id);
|
|
1816
|
+
*/
|
|
1817
|
+
subscribe(handler, rooms) {
|
|
1818
|
+
const id = `sub_${++this._subIdCounter}`;
|
|
1819
|
+
this._subscriptions.set(id, { handler, rooms });
|
|
1820
|
+
this._syncRooms();
|
|
1821
|
+
return id;
|
|
1822
|
+
}
|
|
1823
|
+
/**
|
|
1824
|
+
* Remove a subscription registered via `subscribe()`.
|
|
1825
|
+
*
|
|
1826
|
+
* Rooms that are no longer referenced by any remaining subscriber are
|
|
1827
|
+
* left automatically.
|
|
1828
|
+
*/
|
|
1829
|
+
unsubscribe(id) {
|
|
1830
|
+
this._subscriptions.delete(id);
|
|
1831
|
+
this._syncRooms();
|
|
1832
|
+
}
|
|
1833
|
+
/**
|
|
1834
|
+
* Diff the union of all subscribers' rooms against the currently joined
|
|
1835
|
+
* rooms and emit `join`/`leave` for the delta. No-ops when not connected.
|
|
1836
|
+
*/
|
|
1837
|
+
_syncRooms() {
|
|
1838
|
+
const socket = this._socket;
|
|
1839
|
+
if (!socket?.connected) return;
|
|
1840
|
+
const needed = /* @__PURE__ */ new Set();
|
|
1841
|
+
for (const sub of this._subscriptions.values()) {
|
|
1842
|
+
for (const r of sub.rooms) needed.add(r);
|
|
1843
|
+
}
|
|
1844
|
+
const toJoin = [...needed].filter((r) => !this._currentRooms.has(r));
|
|
1845
|
+
const toLeave = [...this._currentRooms].filter((r) => !needed.has(r));
|
|
1846
|
+
if (toJoin.length) socket.emit("join", { rooms: toJoin });
|
|
1847
|
+
if (toLeave.length) socket.emit("leave", { rooms: toLeave });
|
|
1848
|
+
this._currentRooms = needed;
|
|
1849
|
+
}
|
|
1770
1850
|
/**
|
|
1771
1851
|
* Send a ping to the server.
|
|
1772
1852
|
*/
|