@mulmobridge/client 1.2.0 → 1.3.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/dist/inProcess.d.ts +34 -0
- package/dist/inProcess.js +77 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Attachment, BridgeOptions } from "@mulmobridge/protocol";
|
|
2
|
+
import type { BridgeClient, PushEvent } from "./client.js";
|
|
3
|
+
/** Shape of `chat-service`'s `RelayResult`, restated so this package does not
|
|
4
|
+
* import the server package. Kept structural on purpose: the host passes its
|
|
5
|
+
* own function and TypeScript checks the two agree. */
|
|
6
|
+
export type InProcessRelayResult = {
|
|
7
|
+
kind: "ok";
|
|
8
|
+
reply: string;
|
|
9
|
+
} | {
|
|
10
|
+
kind: "error";
|
|
11
|
+
status: number;
|
|
12
|
+
message: string;
|
|
13
|
+
};
|
|
14
|
+
export type InProcessRelayFn = (params: {
|
|
15
|
+
transportId: string;
|
|
16
|
+
externalChatId: string;
|
|
17
|
+
text: string;
|
|
18
|
+
attachments?: Attachment[] | undefined;
|
|
19
|
+
bridgeOptions?: Readonly<Record<string, string | number | boolean>> | undefined;
|
|
20
|
+
onChunk?: ((text: string) => void) | undefined;
|
|
21
|
+
}) => Promise<InProcessRelayResult>;
|
|
22
|
+
/** Registers this bridge to receive server → bridge pushes. Returns the
|
|
23
|
+
* unregister function, which `close()` calls. */
|
|
24
|
+
export type RegisterInProcessPush = (transportId: string, handler: (event: PushEvent) => void) => () => void;
|
|
25
|
+
export interface InProcessBridgeClientOptions {
|
|
26
|
+
transportId: string;
|
|
27
|
+
relay: InProcessRelayFn;
|
|
28
|
+
registerPush: RegisterInProcessPush;
|
|
29
|
+
/** Forwarded to the host's `startChat` exactly as the handshake bag is on the
|
|
30
|
+
* socket path. Defaults to `{}` — an in-process bridge is configured by the
|
|
31
|
+
* host, so there is no env to scrape on its behalf. */
|
|
32
|
+
options?: BridgeOptions;
|
|
33
|
+
}
|
|
34
|
+
export declare function createInProcessBridgeClient(opts: InProcessBridgeClientOptions): BridgeClient;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// A `BridgeClient` that talks to a chat service living in the SAME process,
|
|
2
|
+
// with no socket, no port and no bearer token (#3080).
|
|
3
|
+
//
|
|
4
|
+
// `packages/chat-service/src/relay.ts` calls itself "the shared core of the
|
|
5
|
+
// bridge chat flow ... HTTP (router) and socket.io transports both call the
|
|
6
|
+
// `RelayFn` this factory returns". This is the third caller of that same core,
|
|
7
|
+
// so nothing new is introduced server-side — only a different way in.
|
|
8
|
+
//
|
|
9
|
+
// The relay and the push registration arrive as callbacks rather than imports:
|
|
10
|
+
// `@mulmobridge/client` sits BELOW the server in the dependency direction and
|
|
11
|
+
// must not import `@mulmoclaude/chat-service`.
|
|
12
|
+
export function createInProcessBridgeClient(opts) {
|
|
13
|
+
const bridgeOptions = opts.options ?? {};
|
|
14
|
+
const pushHandlers = [];
|
|
15
|
+
const chunkHandlers = [];
|
|
16
|
+
let unregisterPush = null;
|
|
17
|
+
let closed = false;
|
|
18
|
+
const deliverPush = (event) => {
|
|
19
|
+
for (const handler of pushHandlers) {
|
|
20
|
+
try {
|
|
21
|
+
handler(event);
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
// Per subscriber, so one bad handler does not stop the ones after it
|
|
25
|
+
// from seeing the push — and does not reach the server's stack.
|
|
26
|
+
console.error(`[${opts.transportId}] push handler threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const send = async (externalChatId, text, attachments) => {
|
|
31
|
+
if (closed)
|
|
32
|
+
return { ok: false, error: "bridge client is closed" };
|
|
33
|
+
const result = await opts.relay({
|
|
34
|
+
transportId: opts.transportId,
|
|
35
|
+
externalChatId,
|
|
36
|
+
text,
|
|
37
|
+
attachments,
|
|
38
|
+
bridgeOptions,
|
|
39
|
+
// Only subscribe the relay to chunks when someone is listening, so the
|
|
40
|
+
// serialiser is not paying for a stream nobody reads.
|
|
41
|
+
onChunk: chunkHandlers.length > 0 ? (chunk) => chunkHandlers.forEach((handler) => handler(chunk)) : undefined,
|
|
42
|
+
});
|
|
43
|
+
return result.kind === "ok" ? { ok: true, reply: result.reply } : { ok: false, error: result.message, status: result.status };
|
|
44
|
+
};
|
|
45
|
+
return {
|
|
46
|
+
send,
|
|
47
|
+
onPush(handler) {
|
|
48
|
+
pushHandlers.push(handler);
|
|
49
|
+
// Registered on the FIRST subscriber so a bridge that never listens costs
|
|
50
|
+
// the host nothing, and only once however many handlers are added.
|
|
51
|
+
unregisterPush ??= opts.registerPush(opts.transportId, deliverPush);
|
|
52
|
+
},
|
|
53
|
+
onTextChunk(handler) {
|
|
54
|
+
chunkHandlers.push(handler);
|
|
55
|
+
},
|
|
56
|
+
// There is no socket, so there is no connection to gain or lose. An
|
|
57
|
+
// in-process bridge is connected from the moment it is constructed.
|
|
58
|
+
onConnect(handler) {
|
|
59
|
+
handler();
|
|
60
|
+
},
|
|
61
|
+
onDisconnect() { },
|
|
62
|
+
close() {
|
|
63
|
+
closed = true;
|
|
64
|
+
unregisterPush?.();
|
|
65
|
+
unregisterPush = null;
|
|
66
|
+
pushHandlers.length = 0;
|
|
67
|
+
chunkHandlers.length = 0;
|
|
68
|
+
},
|
|
69
|
+
get socket() {
|
|
70
|
+
// Deliberately loud rather than null. Measured across all 25 bridges: none
|
|
71
|
+
// reads `.socket`, so the first caller to do so is writing new code against
|
|
72
|
+
// an assumption that does not hold here, and a null would surface as an
|
|
73
|
+
// unrelated TypeError somewhere downstream.
|
|
74
|
+
throw new Error("in-process bridge client has no socket — see packages/client/src/inProcess.ts");
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,3 +8,4 @@ export { asJsonRecord, fetchJsonRecord, type JsonRecord } from "./http.js";
|
|
|
8
8
|
export { formatAckReply } from "./reply.js";
|
|
9
9
|
export { mimeFromExtension, isImageMime, isPdfMime, isSupportedAttachmentMime, isNativeAttachmentMime, parseDataUrl, buildDataUrl, type ParsedDataUrl, } from "./mime.js";
|
|
10
10
|
export { installProcessGuards, SHUTDOWN_GRACE_MS, type ProcessGuardOptions, type ShutdownTask } from "./processGuards.js";
|
|
11
|
+
export { createInProcessBridgeClient, type InProcessBridgeClientOptions, type InProcessRelayFn, type InProcessRelayResult, type RegisterInProcessPush, } from "./inProcess.js";
|
package/dist/index.js
CHANGED
|
@@ -9,3 +9,4 @@ export { asJsonRecord, fetchJsonRecord } from "./http.js";
|
|
|
9
9
|
export { formatAckReply } from "./reply.js";
|
|
10
10
|
export { mimeFromExtension, isImageMime, isPdfMime, isSupportedAttachmentMime, isNativeAttachmentMime, parseDataUrl, buildDataUrl, } from "./mime.js";
|
|
11
11
|
export { installProcessGuards, SHUTDOWN_GRACE_MS } from "./processGuards.js";
|
|
12
|
+
export { createInProcessBridgeClient, } from "./inProcess.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mulmobridge/client",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Socket.io client library for MulmoBridge — shared by all bridge implementations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"socket.io-client": "^4.0.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@types/node": "^26.
|
|
53
|
+
"@types/node": "^26.5.1",
|
|
54
54
|
"typescript": "^6.0.3"
|
|
55
55
|
},
|
|
56
56
|
"homepage": "https://github.com/receptron/mulmoclaude/tree/main/packages/client#readme",
|