@statelyai/sdk 0.14.2 → 0.16.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/README.md +81 -11
- package/dist/auth.d.mts +1 -1
- package/dist/bin.d.mts +1 -0
- package/dist/bin.mjs +35 -0
- package/dist/embed.d.mts +1 -1
- package/dist/embed.mjs +1 -1
- package/dist/{graph-3oSqiY4N.d.mts → graph-DuOeLGvh.d.mts} +1 -1
- package/dist/graph.d.mts +1 -1
- package/dist/{index-CcgKzQ-C.d.mts → index-ag6K2iAS.d.mts} +1 -1
- package/dist/index.d.mts +7 -7
- package/dist/index.mjs +6 -6
- package/dist/{inspect-DWDTNMEc.mjs → inspect-BwVdM4R8.mjs} +109 -72
- package/dist/{inspect-DQ_7I7eH.d.mts → inspect-CduY_2Fa.d.mts} +54 -59
- package/dist/inspect.d.mts +1 -1
- package/dist/inspect.mjs +1 -1
- package/dist/node-relay.d.mts +22 -0
- package/dist/node-relay.mjs +84 -0
- package/dist/patchTypes.d.mts +1 -1
- package/dist/protocol.d.mts +102 -115
- package/dist/protocol.mjs +33 -2
- package/dist/relay.d.mts +10 -11
- package/dist/relay.mjs +83 -62
- package/dist/{dist-CO36Ks-i.mjs → statelyPragma-Th0gdXfx.mjs} +112 -4432
- package/dist/studio-Bk_u41gu.d.mts +303 -0
- package/dist/studio-txdyJHD7.mjs +4725 -0
- package/dist/studio.d.mts +2 -125
- package/dist/studio.mjs +2 -171
- package/dist/sync.d.mts +2 -2
- package/dist/sync.mjs +13 -24
- package/dist/{transport-D2Ud05yg.mjs → transport-B_ig9Znn.mjs} +56 -198
- package/package.json +15 -3
- package/dist/dist-Ci5I7Rjq.mjs +0 -9401
- /package/dist/{auth-D9-iLi-S.d.mts → auth-2Thaw42j.d.mts} +0 -0
package/dist/relay.mjs
CHANGED
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
import { INSPECTION_PROTOCOL_VERSION, PREFIX } from "./protocol.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/relay.ts
|
|
4
|
+
const REPLAYABLE_MESSAGE_TYPES = new Set([
|
|
5
|
+
"@statelyai.init",
|
|
6
|
+
"@statelyai.inspectSnapshot",
|
|
7
|
+
"@statelyai.system.init",
|
|
8
|
+
"@statelyai.system.actorRegistered",
|
|
9
|
+
"@statelyai.system.actorEvent",
|
|
10
|
+
"@statelyai.system.actorSnapshot",
|
|
11
|
+
"@statelyai.system.actorStopped"
|
|
12
|
+
]);
|
|
13
|
+
const FORWARD_ONLY_MESSAGE_TYPES = new Set([
|
|
14
|
+
"@statelyai.system.selectActor",
|
|
15
|
+
"@statelyai.retrieve",
|
|
16
|
+
"@statelyai.retrieved",
|
|
17
|
+
"@statelyai.error",
|
|
18
|
+
"@statelyai.ready",
|
|
19
|
+
"@statelyai.loaded",
|
|
20
|
+
"@statelyai.capabilityDenied",
|
|
21
|
+
"@statelyai.register",
|
|
22
|
+
"@statelyai.registered"
|
|
23
|
+
]);
|
|
24
|
+
function isSupportedMessageType(type) {
|
|
25
|
+
return REPLAYABLE_MESSAGE_TYPES.has(type) || FORWARD_ONLY_MESSAGE_TYPES.has(type);
|
|
26
|
+
}
|
|
4
27
|
function parseMessage(raw) {
|
|
5
28
|
if (raw instanceof ArrayBuffer) raw = new TextDecoder().decode(raw);
|
|
6
29
|
if (typeof raw === "string") try {
|
|
@@ -12,6 +35,22 @@ function parseMessage(raw) {
|
|
|
12
35
|
const message = raw;
|
|
13
36
|
return typeof message.type === "string" && message.type.startsWith(PREFIX) ? message : null;
|
|
14
37
|
}
|
|
38
|
+
function validationError(message) {
|
|
39
|
+
if (!isSupportedMessageType(message.type)) return `Unsupported inspection message type ${String(message.type)}`;
|
|
40
|
+
const hasString = (key) => typeof message[key] === "string" && message[key] !== "";
|
|
41
|
+
switch (message.type) {
|
|
42
|
+
case "@statelyai.system.init": return message.protocolVersion === INSPECTION_PROTOCOL_VERSION && Array.isArray(message.actors) ? null : "system.init requires the current protocolVersion and actors array";
|
|
43
|
+
case "@statelyai.system.selectActor":
|
|
44
|
+
case "@statelyai.system.actorStopped": return hasString("sessionId") ? null : `${message.type} requires sessionId`;
|
|
45
|
+
case "@statelyai.system.actorRegistered": return hasString("sessionId") && hasString("actorId") ? null : "actorRegistered requires sessionId and actorId";
|
|
46
|
+
case "@statelyai.system.actorEvent": return hasString("sessionId") && "event" in message ? null : "actorEvent requires sessionId and event";
|
|
47
|
+
case "@statelyai.system.actorSnapshot": return hasString("sessionId") && "snapshot" in message ? null : "actorSnapshot requires sessionId and snapshot";
|
|
48
|
+
case "@statelyai.retrieve":
|
|
49
|
+
case "@statelyai.retrieved": return hasString("requestId") ? null : `${message.type} requires requestId`;
|
|
50
|
+
case "@statelyai.error": return hasString("code") && hasString("message") ? null : "error requires code and message";
|
|
51
|
+
default: return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
15
54
|
function errorMessage(code, message) {
|
|
16
55
|
return {
|
|
17
56
|
type: "@statelyai.error",
|
|
@@ -30,10 +69,10 @@ function compactReplay(replay, message, max) {
|
|
|
30
69
|
const index = replay.findIndex((entry) => entry.type === "@statelyai.init");
|
|
31
70
|
if (index >= 0) replay.splice(index, 1);
|
|
32
71
|
} else if (type === "@statelyai.system.actorSnapshot" || type === "@statelyai.inspectSnapshot") {
|
|
33
|
-
const
|
|
72
|
+
const sessionId = record.sessionId;
|
|
34
73
|
const index = replay.findIndex((entry) => {
|
|
35
74
|
const candidate = entry;
|
|
36
|
-
return candidate.type === type && candidate.
|
|
75
|
+
return candidate.type === type && candidate.sessionId === sessionId;
|
|
37
76
|
});
|
|
38
77
|
if (index >= 0) replay.splice(index, 1);
|
|
39
78
|
}
|
|
@@ -41,110 +80,92 @@ function compactReplay(replay, message, max) {
|
|
|
41
80
|
if (replay.length > max) replay.splice(0, replay.length - max);
|
|
42
81
|
}
|
|
43
82
|
/**
|
|
44
|
-
* Runtime-neutral
|
|
83
|
+
* Runtime-neutral implementation of one inspection room.
|
|
45
84
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
85
|
+
* The hosting adapter selects the room from the WebSocket URL. This module
|
|
86
|
+
* owns protocol registration, multi-producer/viewer routing, and bounded
|
|
87
|
+
* per-producer replay.
|
|
49
88
|
*/
|
|
50
89
|
function createInspectionRelay(options = {}) {
|
|
51
90
|
const maxReplayMessages = options.maxReplayMessages ?? 200;
|
|
52
|
-
const
|
|
91
|
+
const maxReplayProducers = Math.max(1, options.maxReplayProducers ?? 100);
|
|
53
92
|
const peers = /* @__PURE__ */ new Map();
|
|
54
|
-
const
|
|
93
|
+
const producers = new Map(Object.entries(options.snapshot?.producers ?? {}).slice(-maxReplayProducers).map(([id, replay]) => [id, [...replay]]));
|
|
55
94
|
const send = (peerId, message) => ({
|
|
56
95
|
type: "send",
|
|
57
96
|
peerId,
|
|
58
97
|
message
|
|
59
98
|
});
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
sessions.delete(sessionId);
|
|
68
|
-
sessions.set(sessionId, replay);
|
|
69
|
-
while (sessions.size > maxReplaySessions) {
|
|
70
|
-
const oldestSessionId = sessions.keys().next().value;
|
|
71
|
-
if (oldestSessionId === void 0) break;
|
|
72
|
-
sessions.delete(oldestSessionId);
|
|
99
|
+
function retainReplay(producerId, replay) {
|
|
100
|
+
producers.delete(producerId);
|
|
101
|
+
producers.set(producerId, replay);
|
|
102
|
+
while (producers.size > maxReplayProducers) {
|
|
103
|
+
const oldestProducerId = producers.keys().next().value;
|
|
104
|
+
if (oldestProducerId === void 0) break;
|
|
105
|
+
producers.delete(oldestProducerId);
|
|
73
106
|
}
|
|
74
107
|
}
|
|
75
108
|
return {
|
|
76
109
|
connect(peer) {
|
|
77
|
-
peers.set(peer.id, {
|
|
78
|
-
...peer,
|
|
79
|
-
sessionIds: Array.from(new Set([peer.primarySessionId, ...peer.sessionIds]))
|
|
80
|
-
});
|
|
110
|
+
peers.set(peer.id, { ...peer });
|
|
81
111
|
},
|
|
82
112
|
disconnect(peerId) {
|
|
83
113
|
peers.delete(peerId);
|
|
84
114
|
},
|
|
85
115
|
clear() {
|
|
86
116
|
peers.clear();
|
|
87
|
-
|
|
117
|
+
producers.clear();
|
|
88
118
|
},
|
|
89
119
|
peer(peerId) {
|
|
90
120
|
const peer = peers.get(peerId);
|
|
91
|
-
return peer ? {
|
|
92
|
-
...peer,
|
|
93
|
-
sessionIds: [...peer.sessionIds]
|
|
94
|
-
} : void 0;
|
|
121
|
+
return peer ? { ...peer } : void 0;
|
|
95
122
|
},
|
|
96
123
|
receive(peerId, raw) {
|
|
97
124
|
const message = parseMessage(raw);
|
|
98
|
-
if (!message) return [];
|
|
125
|
+
if (!message) return [send(peerId, errorMessage("invalid_message", "Expected a JSON protocol message"))];
|
|
126
|
+
const invalid = validationError(message);
|
|
127
|
+
if (invalid) return [send(peerId, errorMessage("invalid_message", invalid))];
|
|
99
128
|
if (message.type === "@statelyai.register") {
|
|
100
129
|
const role = message.role;
|
|
101
|
-
const sessionId = message.sessionId;
|
|
102
130
|
const protocolVersion = message.protocolVersion;
|
|
103
|
-
|
|
104
|
-
if (
|
|
131
|
+
const producerId = message.producerId;
|
|
132
|
+
if (role !== "producer" && role !== "viewer" || role === "producer" && (typeof producerId !== "string" || producerId.length === 0)) return [send(peerId, errorMessage("invalid_registration", "Invalid relay registration"))];
|
|
133
|
+
if (protocolVersion !== INSPECTION_PROTOCOL_VERSION) return [send(peerId, errorMessage("unsupported_protocol", `Protocol ${String(protocolVersion)} is not supported; expected ${INSPECTION_PROTOCOL_VERSION}`))];
|
|
105
134
|
const peer = {
|
|
106
135
|
id: peerId,
|
|
107
136
|
role,
|
|
108
|
-
|
|
109
|
-
sessionIds: [sessionId]
|
|
137
|
+
...role === "producer" && { producerId }
|
|
110
138
|
};
|
|
111
139
|
peers.set(peerId, peer);
|
|
112
140
|
const effects = [send(peerId, {
|
|
113
141
|
type: "@statelyai.registered",
|
|
114
142
|
protocolVersion: INSPECTION_PROTOCOL_VERSION,
|
|
115
|
-
|
|
143
|
+
...peer.producerId && { producerId: peer.producerId }
|
|
116
144
|
})];
|
|
117
|
-
if (role === "
|
|
145
|
+
if (role === "viewer") for (const replay of producers.values()) effects.push(...replay.map((entry) => send(peerId, entry)));
|
|
118
146
|
return effects;
|
|
119
147
|
}
|
|
120
148
|
const sender = peers.get(peerId);
|
|
121
149
|
if (!sender) return [send(peerId, errorMessage("not_registered", "Register before sending messages"))];
|
|
122
|
-
if (
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (!sender.sessionIds.includes(sessionId)) return [send(peerId, errorMessage("unknown_session", "Peer is not registered for session"))];
|
|
135
|
-
const stamped = {
|
|
136
|
-
...message,
|
|
137
|
-
sessionId
|
|
138
|
-
};
|
|
139
|
-
if (sender.role === "client") {
|
|
140
|
-
const replay = sessions.get(sessionId) ?? [];
|
|
141
|
-
compactReplay(replay, stamped, maxReplayMessages);
|
|
142
|
-
retainReplay(sessionId, replay);
|
|
150
|
+
if (sender.role === "producer") {
|
|
151
|
+
const producerId = sender.producerId;
|
|
152
|
+
const stamped = {
|
|
153
|
+
...message,
|
|
154
|
+
producerId
|
|
155
|
+
};
|
|
156
|
+
if (REPLAYABLE_MESSAGE_TYPES.has(message.type)) {
|
|
157
|
+
const replay = producers.get(producerId) ?? [];
|
|
158
|
+
compactReplay(replay, stamped, maxReplayMessages);
|
|
159
|
+
retainReplay(producerId, replay);
|
|
160
|
+
}
|
|
161
|
+
return Array.from(peers.values()).filter((peer) => peer.role === "viewer").map((peer) => send(peer.id, stamped));
|
|
143
162
|
}
|
|
144
|
-
|
|
163
|
+
const targetProducerId = typeof message.producerId === "string" ? message.producerId : void 0;
|
|
164
|
+
return Array.from(peers.values()).filter((peer) => peer.role === "producer" && (!targetProducerId || peer.producerId === targetProducerId)).map((peer) => send(peer.id, message));
|
|
145
165
|
},
|
|
146
|
-
snapshot() {
|
|
147
|
-
|
|
166
|
+
snapshot(producerId) {
|
|
167
|
+
const entries = producerId ? producers.has(producerId) ? [[producerId, producers.get(producerId)]] : [] : Array.from(producers.entries());
|
|
168
|
+
return { producers: Object.fromEntries(entries.map(([producerId, replay]) => [producerId, [...replay]])) };
|
|
148
169
|
}
|
|
149
170
|
};
|
|
150
171
|
}
|