Skip to content

Commit 3a866f9

Browse files
authored
[client] Add 'raw' opt/sendOpt to skip JSON serde (#404)
This commit adds a `raw` optional property to the client To allow skipping JSON serde. This will pass the raw input buffer to the underlying fetch
1 parent dc5d77a commit 3a866f9

File tree

4 files changed

+70
-10
lines changed

4 files changed

+70
-10
lines changed

packages/restate-sdk-clients/src/api.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,13 @@ export interface IngresCallOptions {
8888
* Headers to attach to the request.
8989
*/
9090
headers?: Record<string, string>;
91+
92+
/**
93+
* Do not auto serialize the input and output of the handler.
94+
* Setting this to true, would allow you to send binary data to the handler,
95+
* and not assuming that the payload is JSON.
96+
*/
97+
raw?: boolean;
9198
}
9299

93100
export interface IngresSendOptions extends IngresCallOptions {

packages/restate-sdk-clients/src/ingress.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -138,10 +138,14 @@ const doComponentInvocation = async <I, O>(
138138
fragments.push("send");
139139
}
140140
}
141+
const raw = params.opts?.opts.raw ?? false;
141142
//
142143
// request body
143144
//
144-
const { body, contentType } = serializeBodyWithContentType(params.parameter);
145+
const { body, contentType } = serializeBodyWithContentType(
146+
params.parameter,
147+
raw
148+
);
145149
//
146150
// headers
147151
//
@@ -178,7 +182,7 @@ const doComponentInvocation = async <I, O>(
178182
);
179183
}
180184
const responseBuf = await httpResponse.arrayBuffer();
181-
const json = deserializeJson(new Uint8Array(responseBuf)) as O;
185+
const json = deserializeJson(new Uint8Array(responseBuf), raw) as O;
182186
if (!params.send) {
183187
return json;
184188
}
@@ -210,7 +214,7 @@ const doWorkflowHandleCall = async <O>(
210214
});
211215
if (httpResponse.ok) {
212216
const responseBuf = await httpResponse.arrayBuffer();
213-
return deserializeJson(new Uint8Array(responseBuf)) as O;
217+
return deserializeJson(new Uint8Array(responseBuf), false) as O;
214218
}
215219
const body = await httpResponse.text();
216220
throw new HttpCallError(
@@ -361,7 +365,7 @@ class HttpIngress implements Ingress {
361365
payload?: T | undefined
362366
): Promise<void> {
363367
const url = `${this.opts.url}/restate/a/${id}/resolve`;
364-
const { body, contentType } = serializeBodyWithContentType(payload);
368+
const { body, contentType } = serializeBodyWithContentType(payload, false);
365369
const headers = {
366370
...(this.opts.headers ?? {}),
367371
};
@@ -427,7 +431,7 @@ class HttpIngress implements Ingress {
427431
});
428432
if (httpResponse.ok) {
429433
const responseBuf = await httpResponse.arrayBuffer();
430-
return deserializeJson(new Uint8Array(responseBuf)) as T;
434+
return deserializeJson(new Uint8Array(responseBuf), false) as T;
431435
}
432436
const body = await httpResponse.text();
433437
throw new HttpCallError(
@@ -447,21 +451,33 @@ function computeDelayAsIso(opts: SendOpts): string {
447451
}
448452

449453
// eslint-disable-next-line @typescript-eslint/no-explicit-any
450-
function deserializeJson(what: Uint8Array): any {
451-
if (what === undefined || what.length == 0) {
454+
function deserializeJson(what: Uint8Array, raw: boolean): any {
455+
if (what === undefined) {
456+
return undefined;
457+
}
458+
if (raw) {
459+
return what;
460+
}
461+
if (what.length == 0) {
452462
return undefined;
453463
}
454464
const json = new TextDecoder().decode(what);
455465
return JSON.parse(json);
456466
}
457467

458-
function serializeBodyWithContentType(body: unknown): {
468+
function serializeBodyWithContentType(
469+
body: unknown,
470+
raw: boolean
471+
): {
459472
body?: Uint8Array;
460473
contentType?: string;
461474
} {
462475
if (body === undefined) {
463476
return {};
464477
}
478+
if (raw) {
479+
return { body: body as Uint8Array };
480+
}
465481
const json = JSON.stringify(body);
466482
const buffer = new TextEncoder().encode(json);
467483

packages/restate-sdk-examples/src/ingress_client.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,24 @@ const workflow = async (name: string) => {
146146
console.log(await client.workflowAttach());
147147
};
148148

149+
const binaryRawCall = async (name: string) => {
150+
const counter = ingress.objectClient(Counter, name);
151+
152+
const buffer = new TextEncoder().encode("hello!");
153+
154+
const outBuffer = await counter.binary(
155+
buffer,
156+
restate.Opts.from({
157+
raw: true, // <-- tell the client to avoid JSON encoding/decoding
158+
headers: { "content-type": "application/octet-stream" },
159+
})
160+
);
161+
162+
const str = new TextDecoder().decode(outBuffer);
163+
164+
console.log(`We got a buffer for ${name} : ${str}`);
165+
};
166+
149167
// Before running this example, make sure
150168
// to run and register `greeter`, `counter` and `workflow` services.
151169
//
@@ -156,15 +174,16 @@ const workflow = async (name: string) => {
156174
// 3. restate deployment add localhost:9080
157175

158176
Promise.resolve()
159-
.then(() => sendAndCollectResultLater("boby"))
160-
.then(() => simpleCall("bob"))
161177
.then(() => objectCall("bob"))
162178
.then(() => objectCall("mop"))
179+
.then(() => simpleCall("bob"))
180+
.then(() => sendAndCollectResultLater("boby"))
163181
.then(() => workflow("boby"))
164182
.then(() => idempotentCall("joe", "idemp-1"))
165183
.then(() => idempotentCall("joe", "idemp-1"))
166184
.then(() => customHeadersCall("bob"))
167185
.then(() => globalCustomHeaders("bob"))
168186
.then(() => customInterface("bob"))
169187
.then(() => delayedCall("bob"))
188+
.then(() => binaryRawCall("bob"))
170189
.catch((e) => console.error(e));

packages/restate-sdk-examples/src/object.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,24 @@ const counter = restate.object({
3636
return (await ctx.get("count")) ?? 0;
3737
}
3838
),
39+
40+
/**
41+
* Handlers (shared or exclusive) can be configured to bypass JSON serialization,
42+
* by specifying the input (accept) and output (contentType) content types.
43+
*
44+
* to call that handler with binary data, you can use the following curl command:
45+
* curl -X POST -H "Content-Type: application/octet-stream" --data-binary 'hello' ${RESTATE_INGRESS_URL}/counter/mykey/binary
46+
*/
47+
binary: restate.handlers.object.exclusive(
48+
{
49+
accept: "application/octet-stream",
50+
contentType: "application/octet-stream",
51+
},
52+
async (ctx: restate.ObjectContext, data: Uint8Array) => {
53+
// console.log("Received binary data", data);
54+
return data;
55+
}
56+
),
3957
},
4058
});
4159

0 commit comments

Comments
 (0)