@synnaxlabs/freighter 0.55.0 → 0.56.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/.turbo/turbo-build.log +7 -10
- package/dist/freighter.cjs +7 -7
- package/dist/freighter.js +516 -444
- package/dist/src/alamos.d.ts.map +1 -1
- package/dist/src/http.d.ts +1 -1
- package/dist/src/http.d.ts.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/middleware.d.ts +19 -15
- package/dist/src/middleware.d.ts.map +1 -1
- package/dist/src/stream.d.ts +27 -29
- package/dist/src/stream.d.ts.map +1 -1
- package/dist/src/unary.d.ts +5 -4
- package/dist/src/unary.d.ts.map +1 -1
- package/dist/src/websocket.d.ts.map +1 -1
- package/package.json +11 -11
- package/src/alamos.ts +23 -12
- package/src/http.spec.ts +10 -36
- package/src/http.ts +16 -20
- package/src/index.ts +1 -1
- package/src/middleware.ts +20 -19
- package/src/stream.ts +27 -29
- package/src/unary.ts +15 -23
- package/src/websocket.spec.ts +20 -33
- package/src/websocket.ts +20 -28
package/src/stream.ts
CHANGED
|
@@ -16,15 +16,14 @@ import { type Transport } from "@/transport";
|
|
|
16
16
|
*/
|
|
17
17
|
export interface StreamReceiver<RS extends z.ZodType> {
|
|
18
18
|
/**
|
|
19
|
-
* Receives a response from the stream. It's not safe to call receive
|
|
20
|
-
* concurrently.
|
|
19
|
+
* Receives a response from the stream. It's not safe to call receive concurrently.
|
|
21
20
|
*
|
|
22
|
-
* @returns
|
|
23
|
-
* @
|
|
24
|
-
*
|
|
25
|
-
*
|
|
21
|
+
* @returns the next response from the stream.
|
|
22
|
+
* @throws freighter.EOF: if the server closed the stream nominally.
|
|
23
|
+
* @throws Error: if the server closed the stream abnormally, throws the error the
|
|
24
|
+
* server returned, or a transport error if the transport itself failed.
|
|
26
25
|
*/
|
|
27
|
-
receive: () => Promise<
|
|
26
|
+
receive: () => Promise<z.infer<RS>>;
|
|
28
27
|
|
|
29
28
|
/**
|
|
30
29
|
* @returns true if the stream has received a response
|
|
@@ -37,17 +36,16 @@ export interface StreamReceiver<RS extends z.ZodType> {
|
|
|
37
36
|
*/
|
|
38
37
|
export interface StreamSender<RQ extends z.ZodType> {
|
|
39
38
|
/**
|
|
40
|
-
* Sends a request to the stream. It is not safe to call send concurrently
|
|
41
|
-
*
|
|
39
|
+
* Sends a request to the stream. It is not safe to call send concurrently with
|
|
40
|
+
* closeSend or send.
|
|
42
41
|
|
|
43
42
|
* @param req - the request to send.
|
|
44
|
-
* @
|
|
45
|
-
*
|
|
46
|
-
* @
|
|
47
|
-
* @
|
|
48
|
-
* @raises Error: if the transport fails.
|
|
43
|
+
* @throws freighter.EOF: if the server closed the stream. The caller can discover the
|
|
44
|
+
* error returned by the server by calling receive().
|
|
45
|
+
* @throws freighter.StreamClosed: if the client called closeSend().
|
|
46
|
+
* @throws Error: if the transport fails.
|
|
49
47
|
*/
|
|
50
|
-
send: (req: z.input<RQ> | z.infer<RQ>) =>
|
|
48
|
+
send: (req: z.input<RQ> | z.infer<RQ>) => void;
|
|
51
49
|
}
|
|
52
50
|
|
|
53
51
|
/**
|
|
@@ -56,13 +54,13 @@ export interface StreamSender<RQ extends z.ZodType> {
|
|
|
56
54
|
*/
|
|
57
55
|
export interface StreamSenderCloser<RQ extends z.ZodType> extends StreamSender<RQ> {
|
|
58
56
|
/**
|
|
59
|
-
* Lets the server know no more messages will be sent. If the client attempts
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
57
|
+
* Lets the server know no more messages will be sent. If the client attempts to call
|
|
58
|
+
* send() after calling closeSend(), a freighter.StreamClosed exception will be raised.
|
|
59
|
+
* close_send is idempotent. If the server has already closed the stream, close_send
|
|
60
|
+
* will do nothing.
|
|
63
61
|
|
|
64
|
-
* After calling close_send, the client is responsible for calling receive()
|
|
65
|
-
*
|
|
62
|
+
* After calling close_send, the client is responsible for calling receive() to
|
|
63
|
+
* successfully receive the server's acknowledgement.
|
|
66
64
|
*/
|
|
67
65
|
closeSend: () => void;
|
|
68
66
|
}
|
|
@@ -78,15 +76,15 @@ export interface Stream<RQ extends z.ZodType, RS extends z.ZodType = RQ>
|
|
|
78
76
|
*/
|
|
79
77
|
export interface StreamClient extends Transport {
|
|
80
78
|
/**
|
|
81
|
-
* Dials the target and returns a stream that can be used to issue requests
|
|
82
|
-
*
|
|
79
|
+
* Dials the target and returns a stream that can be used to issue requests and
|
|
80
|
+
* receive responses
|
|
83
81
|
*
|
|
84
|
-
* @param target - The target to dial. In some implementations, this may be
|
|
85
|
-
*
|
|
86
|
-
* @param reqSchema - The schema for the request type. This is used to
|
|
87
|
-
*
|
|
88
|
-
* @param resSchema - The schema for the response type. This is used to
|
|
89
|
-
*
|
|
82
|
+
* @param target - The target to dial. In some implementations, this may be an
|
|
83
|
+
* endpoint path, or in others, a complete hostname or URL.
|
|
84
|
+
* @param reqSchema - The schema for the request type. This is used to validate the
|
|
85
|
+
* request before sending it.
|
|
86
|
+
* @param resSchema - The schema for the response type. This is used to validate the
|
|
87
|
+
* response before returning it.
|
|
90
88
|
*/
|
|
91
89
|
stream: <RQ extends z.ZodType, RS extends z.ZodType = RQ>(
|
|
92
90
|
target: string,
|
package/src/unary.ts
CHANGED
|
@@ -15,8 +15,8 @@ import { type Middleware } from "@/middleware";
|
|
|
15
15
|
import { type Transport } from "@/transport";
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* An interface for an entity that implements a simple request-response
|
|
19
|
-
*
|
|
18
|
+
* An interface for an entity that implements a simple request-response transport
|
|
19
|
+
* between two entities.
|
|
20
20
|
*/
|
|
21
21
|
export interface UnaryClient extends Transport {
|
|
22
22
|
/**
|
|
@@ -24,13 +24,15 @@ export interface UnaryClient extends Transport {
|
|
|
24
24
|
* @param target - The target server to send the request to.
|
|
25
25
|
* @param req - The request to send.
|
|
26
26
|
* @param resSchema - The schema to validate the response against.
|
|
27
|
+
* @returns the decoded response.
|
|
28
|
+
* @throws Error: if the server returns an error or the transport fails.
|
|
27
29
|
*/
|
|
28
30
|
send: <RQ extends z.ZodType, RS extends z.ZodType = RQ>(
|
|
29
31
|
target: string,
|
|
30
32
|
req: z.input<RQ> | z.infer<RQ>,
|
|
31
33
|
reqSchema: RQ,
|
|
32
34
|
resSchema: RS,
|
|
33
|
-
) => Promise<
|
|
35
|
+
) => Promise<z.infer<RS>>;
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
export const unaryWithBreaker = (
|
|
@@ -53,28 +55,18 @@ export const unaryWithBreaker = (
|
|
|
53
55
|
req: z.input<RQ> | z.infer<RQ>,
|
|
54
56
|
reqSchema: RQ,
|
|
55
57
|
resSchema: RS,
|
|
56
|
-
): Promise<
|
|
58
|
+
): Promise<z.infer<RS>> {
|
|
57
59
|
const brk = new breaker.Breaker(cfg);
|
|
58
|
-
do
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
do
|
|
61
|
+
try {
|
|
62
|
+
return await this.wrapped.send(target, req, reqSchema, resSchema);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
if (!Unreachable.matches(err)) throw err;
|
|
65
|
+
console.warn(`[freighter] ${brk.retryMessage}`, err);
|
|
66
|
+
if (!(await brk.wait())) throw err;
|
|
67
|
+
}
|
|
68
|
+
while (true);
|
|
65
69
|
}
|
|
66
70
|
}
|
|
67
71
|
return new WithBreaker(base);
|
|
68
72
|
};
|
|
69
|
-
|
|
70
|
-
export const sendRequired = async <RQ extends z.ZodType, RS extends z.ZodType = RQ>(
|
|
71
|
-
client: UnaryClient,
|
|
72
|
-
target: string,
|
|
73
|
-
req: z.input<RQ> | z.infer<RQ>,
|
|
74
|
-
reqSchema: RQ,
|
|
75
|
-
resSchema: RS,
|
|
76
|
-
): Promise<z.infer<RS>> => {
|
|
77
|
-
const [res, err] = await client.send(target, req, reqSchema, resSchema);
|
|
78
|
-
if (err != null) throw err;
|
|
79
|
-
return res;
|
|
80
|
-
};
|
package/src/websocket.spec.ts
CHANGED
|
@@ -15,12 +15,9 @@ import { EOF } from "@/errors";
|
|
|
15
15
|
import { type Context } from "@/middleware";
|
|
16
16
|
import { WebSocketClient } from "@/websocket";
|
|
17
17
|
|
|
18
|
-
const url = new URL({
|
|
19
|
-
host: "127.0.0.1",
|
|
20
|
-
port: 8080,
|
|
21
|
-
});
|
|
18
|
+
const url = new URL({ host: "127.0.0.1", port: 8080 });
|
|
22
19
|
|
|
23
|
-
const
|
|
20
|
+
const messageSchema = z.object({
|
|
24
21
|
id: z.number().optional(),
|
|
25
22
|
message: z.string().optional(),
|
|
26
23
|
});
|
|
@@ -47,73 +44,63 @@ const decodeTestError = (encoded: errors.Payload): errors.Typed | null => {
|
|
|
47
44
|
return new MyCustomError(message, parseInt(code, 10));
|
|
48
45
|
};
|
|
49
46
|
|
|
50
|
-
errors.register({
|
|
51
|
-
encode: encodeTestError,
|
|
52
|
-
decode: decodeTestError,
|
|
53
|
-
});
|
|
47
|
+
errors.register({ encode: encodeTestError, decode: decodeTestError });
|
|
54
48
|
|
|
55
49
|
describe("websocket", () => {
|
|
56
50
|
test("basic exchange", async () => {
|
|
57
|
-
const stream = await client.stream("stream/echo",
|
|
51
|
+
const stream = await client.stream("stream/echo", messageSchema, messageSchema);
|
|
58
52
|
for (let i = 0; i < 10; i++) {
|
|
59
53
|
stream.send({ id: i, message: "hello" });
|
|
60
|
-
const
|
|
61
|
-
expect(
|
|
62
|
-
expect(response
|
|
63
|
-
expect(response?.message).toEqual("hello");
|
|
54
|
+
const response = await stream.receive();
|
|
55
|
+
expect(response.id).toEqual(i + 1);
|
|
56
|
+
expect(response.message).toEqual("hello");
|
|
64
57
|
}
|
|
65
58
|
stream.closeSend();
|
|
66
|
-
|
|
67
|
-
expect(EOF.matches(error)).toBeTruthy();
|
|
68
|
-
expect(response).toBeNull();
|
|
59
|
+
await expect(stream.receive()).rejects.toThrow(EOF);
|
|
69
60
|
});
|
|
70
61
|
|
|
71
62
|
test("receive message after close", async () => {
|
|
72
63
|
const stream = await client.stream(
|
|
73
64
|
"stream/sendMessageAfterClientClose",
|
|
74
|
-
|
|
75
|
-
|
|
65
|
+
messageSchema,
|
|
66
|
+
messageSchema,
|
|
76
67
|
);
|
|
77
68
|
stream.closeSend();
|
|
78
|
-
const
|
|
79
|
-
expect(
|
|
80
|
-
expect(response
|
|
81
|
-
expect(
|
|
82
|
-
const [, recvError] = await stream.receive();
|
|
83
|
-
expect(EOF.matches(recvError)).toBeTruthy();
|
|
69
|
+
const response = await stream.receive();
|
|
70
|
+
expect(response.id).toEqual(0);
|
|
71
|
+
expect(response.message).toEqual("Close Acknowledged");
|
|
72
|
+
await expect(stream.receive()).rejects.toThrow(EOF);
|
|
84
73
|
});
|
|
85
74
|
|
|
86
75
|
test("receive error", async () => {
|
|
87
76
|
const stream = await client.stream(
|
|
88
77
|
"stream/receiveAndExitWithErr",
|
|
89
|
-
|
|
90
|
-
|
|
78
|
+
messageSchema,
|
|
79
|
+
messageSchema,
|
|
91
80
|
);
|
|
92
81
|
stream.send({ id: 0, message: "hello" });
|
|
93
|
-
|
|
94
|
-
expect(MyCustomError.matches(error)).toBeTruthy();
|
|
95
|
-
expect(response).toBeNull();
|
|
82
|
+
await expect(stream.receive()).rejects.toThrow(MyCustomError);
|
|
96
83
|
});
|
|
97
84
|
|
|
98
85
|
describe("middleware", () => {
|
|
99
86
|
test("receive middleware", async () => {
|
|
100
87
|
const myClient = new WebSocketClient(url, new binary.JSONCodec());
|
|
101
88
|
let c = 0;
|
|
102
|
-
myClient.use(async (md, next): Promise<
|
|
89
|
+
myClient.use(async (md, next): Promise<Context> => {
|
|
103
90
|
if (md.params !== undefined) {
|
|
104
91
|
c++;
|
|
105
92
|
md.params.Test = "test";
|
|
106
93
|
}
|
|
107
94
|
return await next(md);
|
|
108
95
|
});
|
|
109
|
-
await myClient.stream("stream/middlewareCheck",
|
|
96
|
+
await myClient.stream("stream/middlewareCheck", messageSchema, messageSchema);
|
|
110
97
|
expect(c).toEqual(1);
|
|
111
98
|
});
|
|
112
99
|
|
|
113
100
|
test("middleware error on server", async () => {
|
|
114
101
|
const myClient = new WebSocketClient(url, new binary.JSONCodec());
|
|
115
102
|
await expect(
|
|
116
|
-
myClient.stream("stream/middlewareCheck",
|
|
103
|
+
myClient.stream("stream/middlewareCheck", messageSchema, messageSchema),
|
|
117
104
|
).rejects.toThrow("test param not found");
|
|
118
105
|
});
|
|
119
106
|
});
|
package/src/websocket.ts
CHANGED
|
@@ -56,34 +56,32 @@ class WebSocketStream<
|
|
|
56
56
|
this.listenForMessages();
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
async receiveOpenAck(): Promise<
|
|
59
|
+
async receiveOpenAck(): Promise<void> {
|
|
60
60
|
const msg = await this.receiveMsg();
|
|
61
|
-
if (msg.type
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
return null;
|
|
61
|
+
if (msg.type === "open") return;
|
|
62
|
+
if (msg.error == null) throw new Error("Message error must be defined");
|
|
63
|
+
const err = errors.decode(msg.error);
|
|
64
|
+
throw err ?? new Error(`Unexpected open-ack message type: ${msg.type}`);
|
|
66
65
|
}
|
|
67
66
|
|
|
68
67
|
/** Implements the Stream protocol */
|
|
69
|
-
send(req: z.input<RQ> | z.infer<RQ>):
|
|
70
|
-
if (this.serverClosed != null)
|
|
68
|
+
send(req: z.input<RQ> | z.infer<RQ>): void {
|
|
69
|
+
if (this.serverClosed != null) throw new EOF();
|
|
71
70
|
if (this.sendClosed) throw new StreamClosed();
|
|
72
71
|
this.ws.send(this.codec.encode({ type: "data", payload: req }));
|
|
73
|
-
return null;
|
|
74
72
|
}
|
|
75
73
|
|
|
76
74
|
/** Implements the Stream protocol */
|
|
77
|
-
async receive(): Promise<
|
|
78
|
-
if (this.serverClosed != null)
|
|
75
|
+
async receive(): Promise<z.infer<RS>> {
|
|
76
|
+
if (this.serverClosed != null) throw this.serverClosed;
|
|
79
77
|
const msg = await this.receiveMsg();
|
|
80
78
|
if (msg.type === "close") {
|
|
81
79
|
if (msg.error == null) throw new Error("Message error must be defined");
|
|
82
80
|
this.serverClosed = errors.decode(msg.error);
|
|
83
81
|
if (this.serverClosed == null) throw new Error("Message error must be defined");
|
|
84
|
-
|
|
82
|
+
throw this.serverClosed;
|
|
85
83
|
}
|
|
86
|
-
return
|
|
84
|
+
return this.resSchema.parse(msg.payload);
|
|
87
85
|
}
|
|
88
86
|
|
|
89
87
|
/** Implements the Stream protocol */
|
|
@@ -177,19 +175,16 @@ export class WebSocketClient extends MiddlewareCollector implements StreamClient
|
|
|
177
175
|
resSchema: RS,
|
|
178
176
|
): Promise<Stream<RQ, RS>> {
|
|
179
177
|
let stream: Stream<RQ, RS> | undefined;
|
|
180
|
-
|
|
178
|
+
await this.executeMiddleware(
|
|
181
179
|
{ target, protocol: "websocket", params: {}, role: "client" },
|
|
182
|
-
async (ctx: Context): Promise<
|
|
180
|
+
async (ctx: Context): Promise<Context> => {
|
|
183
181
|
const ws = new WebSocket(this.buildURL(target, ctx));
|
|
184
182
|
const outCtx: Context = { ...ctx, params: {} };
|
|
185
183
|
ws.binaryType = WebSocketClient.MESSAGE_TYPE;
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
stream = streamOrErr;
|
|
189
|
-
return [outCtx, null];
|
|
184
|
+
stream = await this.wrapSocket(ws, reqSchema, resSchema);
|
|
185
|
+
return outCtx;
|
|
190
186
|
},
|
|
191
187
|
);
|
|
192
|
-
if (error != null) throw error;
|
|
193
188
|
return stream as Stream<RQ, RS>;
|
|
194
189
|
}
|
|
195
190
|
|
|
@@ -208,21 +203,18 @@ export class WebSocketClient extends MiddlewareCollector implements StreamClient
|
|
|
208
203
|
ws: WebSocket,
|
|
209
204
|
reqSchema: RQ,
|
|
210
205
|
resSchema: RS,
|
|
211
|
-
): Promise<WebSocketStream<RQ, RS
|
|
212
|
-
return await new Promise((resolve) => {
|
|
206
|
+
): Promise<WebSocketStream<RQ, RS>> {
|
|
207
|
+
return await new Promise((resolve, reject) => {
|
|
213
208
|
ws.onopen = () => {
|
|
214
209
|
const oWs = new WebSocketStream<RQ, RS>(ws, this.encoder, reqSchema, resSchema);
|
|
215
210
|
oWs
|
|
216
211
|
.receiveOpenAck()
|
|
217
|
-
.then((
|
|
218
|
-
|
|
219
|
-
else resolve(oWs);
|
|
220
|
-
})
|
|
221
|
-
.catch((err: Error) => resolve(err));
|
|
212
|
+
.then(() => resolve(oWs))
|
|
213
|
+
.catch((err: Error) => reject(err));
|
|
222
214
|
};
|
|
223
215
|
ws.onerror = (ev: Event) => {
|
|
224
216
|
const ev_ = ev as ErrorEvent;
|
|
225
|
-
|
|
217
|
+
reject(new Error(ev_.message));
|
|
226
218
|
};
|
|
227
219
|
});
|
|
228
220
|
}
|