Skip to content

Commit c4acc26

Browse files
New API to define handlers/shared handlers without wrapped functions. This supports proper contextual typing, and allows to evolve the Typed State API as well.
1 parent 8f15000 commit c4acc26

File tree

13 files changed

+467
-208
lines changed

13 files changed

+467
-208
lines changed

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

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type {
77
WorkflowDefinitionFrom,
88
Serde,
99
Duration,
10+
FlattenHandlersDefinition,
1011
JournalValueCodec,
1112
} from "@restatedev/restate-sdk-core";
1213
import { millisOrDurationToMillis } from "@restatedev/restate-sdk-core";
@@ -26,7 +27,9 @@ export interface Ingress {
2627
/**
2728
* Create a client from a {@link ServiceDefinition}.
2829
*/
29-
serviceClient<D>(opts: ServiceDefinitionFrom<D>): IngressClient<Service<D>>;
30+
serviceClient<D>(
31+
opts: ServiceDefinitionFrom<D>
32+
): IngressClient<FlattenHandlersDefinition<Service<D>>>;
3033

3134
/**
3235
* Create a client from a {@link WorkflowDefinition}.
@@ -36,7 +39,7 @@ export interface Ingress {
3639
workflowClient<D>(
3740
opts: WorkflowDefinitionFrom<D>,
3841
key: string
39-
): IngressWorkflowClient<Workflow<D>>;
42+
): IngressWorkflowClient<FlattenHandlersDefinition<Workflow<D>>>;
4043

4144
/**
4245
* Create a client from a {@link VirtualObjectDefinition}.
@@ -45,22 +48,22 @@ export interface Ingress {
4548
objectClient<D>(
4649
opts: VirtualObjectDefinitionFrom<D>,
4750
key: string
48-
): IngressClient<VirtualObject<D>>;
51+
): IngressClient<FlattenHandlersDefinition<VirtualObject<D>>>;
4952

5053
/**
5154
* Create a client from a {@link ServiceDefinition}.
5255
*/
5356
serviceSendClient<D>(
5457
opts: ServiceDefinitionFrom<D>
55-
): IngressSendClient<Service<D>>;
58+
): IngressSendClient<FlattenHandlersDefinition<Service<D>>>;
5659

5760
/**
5861
* Create a client from a {@link VirtualObjectDefinition}.
5962
*/
6063
objectSendClient<D>(
6164
opts: VirtualObjectDefinitionFrom<D>,
6265
key: string
63-
): IngressSendClient<VirtualObject<D>>;
66+
): IngressSendClient<FlattenHandlersDefinition<VirtualObject<D>>>;
6467

6568
/**
6669
* Resolve an awakeable from the ingress client.

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

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
type Serde,
2020
serde,
2121
type JournalValueCodec,
22+
type FlattenHandlersDefinition,
2223
} from "@restatedev/restate-sdk-core";
2324
import type {
2425
ConnectionOpts,
@@ -296,21 +297,27 @@ class HttpIngress implements Ingress {
296297
);
297298
}
298299

299-
serviceClient<D>(opts: ServiceDefinitionFrom<D>): IngressClient<Service<D>> {
300-
return this.proxy(opts.name) as IngressClient<Service<D>>;
300+
serviceClient<D>(
301+
opts: ServiceDefinitionFrom<D>
302+
): IngressClient<FlattenHandlersDefinition<Service<D>>> {
303+
return this.proxy(opts.name) as IngressClient<
304+
FlattenHandlersDefinition<Service<D>>
305+
>;
301306
}
302307

303308
objectClient<D>(
304309
opts: VirtualObjectDefinitionFrom<D>,
305310
key: string
306-
): IngressClient<VirtualObject<D>> {
307-
return this.proxy(opts.name, key) as IngressClient<VirtualObject<D>>;
311+
): IngressClient<FlattenHandlersDefinition<VirtualObject<D>>> {
312+
return this.proxy(opts.name, key) as IngressClient<
313+
FlattenHandlersDefinition<VirtualObject<D>>
314+
>;
308315
}
309316

310317
workflowClient<D>(
311318
opts: WorkflowDefinitionFrom<D>,
312319
key: string
313-
): IngressWorkflowClient<Workflow<D>> {
320+
): IngressWorkflowClient<FlattenHandlersDefinition<Workflow<D>>> {
314321
const component = opts.name;
315322
const conn = this.opts;
316323

@@ -392,23 +399,23 @@ class HttpIngress implements Ingress {
392399
};
393400
},
394401
}
395-
) as IngressWorkflowClient<Workflow<D>>;
402+
) as IngressWorkflowClient<FlattenHandlersDefinition<Workflow<D>>>;
396403
}
397404

398405
objectSendClient<D>(
399406
opts: VirtualObjectDefinitionFrom<D>,
400407
key: string
401-
): IngressSendClient<VirtualObject<D>> {
408+
): IngressSendClient<FlattenHandlersDefinition<VirtualObject<D>>> {
402409
return this.proxy(opts.name, key, true) as IngressSendClient<
403-
VirtualObject<D>
410+
FlattenHandlersDefinition<VirtualObject<D>>
404411
>;
405412
}
406413

407414
serviceSendClient<D>(
408415
opts: ServiceDefinitionFrom<D>
409-
): IngressSendClient<Service<D>> {
416+
): IngressSendClient<FlattenHandlersDefinition<Service<D>>> {
410417
return this.proxy(opts.name, undefined, true) as IngressSendClient<
411-
Service<D>
418+
FlattenHandlersDefinition<Service<D>>
412419
>;
413420
}
414421

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export type {
2222
VirtualObject,
2323
Duration,
2424
JournalValueCodec,
25+
FlattenHandlersDefinition,
2526
} from "@restatedev/restate-sdk-core";
2627

2728
export { serde } from "@restatedev/restate-sdk-core";

packages/restate-sdk-core/src/core.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,28 @@ export interface RestateWorkflowContext
2929

3030
// ----------- service -------------------------------------------------------
3131

32+
/**
33+
* @deprecated
34+
*/
3235
export type ArgType<T> = T extends (ctx: any) => any
3336
? void
3437
: T extends (ctx: any, input: infer I) => any
3538
? I
3639
: never;
3740

41+
/**
42+
* @deprecated
43+
*/
3844
export type HandlerReturnType<T> = T extends (
3945
ctx: any,
4046
input: any
4147
) => Promise<infer R>
4248
? R
4349
: never;
4450

51+
/**
52+
* @deprecated
53+
*/
4554
export type ServiceHandler<F, C = RestateContext> = F extends (
4655
ctx: C
4756
) => Promise<any>
@@ -65,6 +74,9 @@ export type ServiceDefinitionFrom<M> = M extends ServiceDefinition<
6574

6675
// ----------- object -------------------------------------------------------
6776

77+
/**
78+
* @deprecated
79+
*/
6880
export type ObjectSharedHandler<
6981
F,
7082
SC = RestateObjectSharedContext
@@ -74,6 +86,9 @@ export type ObjectSharedHandler<
7486
? F
7587
: (ctx: SC, param?: any) => Promise<any>;
7688

89+
/**
90+
* @deprecated
91+
*/
7792
export type ObjectHandler<F, C = RestateObjectContext> = F extends (
7893
ctx: C,
7994
param: any
@@ -104,6 +119,9 @@ export type VirtualObjectDefinitionFrom<M> = M extends VirtualObjectDefinition<
104119

105120
// ----------- workflow -------------------------------------------------------
106121

122+
/**
123+
* @deprecated
124+
*/
107125
export type WorkflowSharedHandler<
108126
F,
109127
SC = RestateWorkflowSharedContext
@@ -113,6 +131,9 @@ export type WorkflowSharedHandler<
113131
? F
114132
: (ctx: SC, param?: any) => Promise<any>;
115133

134+
/**
135+
* @deprecated
136+
*/
116137
export type WorkflowHandler<F, C = RestateWorkflowContext> = F extends (
117138
ctx: C,
118139
param: any
@@ -135,3 +156,28 @@ export type WorkflowDefinitionFrom<M> = M extends WorkflowDefinition<
135156
>
136157
? M
137158
: WorkflowDefinition<string, M>;
159+
160+
// -------- Type manipulation for clients
161+
162+
export type FlattenHandlersDefinition<M> = {
163+
[K in keyof M]: M[K] extends {
164+
handler:
165+
| ((ctx: any, param: any) => Promise<any>)
166+
| ((ctx: any) => Promise<any>)
167+
| ((ctx: any, param?: any) => Promise<any>);
168+
}
169+
? M[K]["handler"]
170+
: M[K] extends {
171+
sharedHandler:
172+
| ((ctx: any, param: any) => Promise<any>)
173+
| ((ctx: any) => Promise<any>)
174+
| ((ctx: any, param?: any) => Promise<any>);
175+
}
176+
? M[K]["sharedHandler"]
177+
: M[K] extends
178+
| ((ctx: any, param: any) => Promise<any>)
179+
| ((ctx: any) => Promise<any>)
180+
| ((ctx: any, param?: any) => Promise<any>)
181+
? M[K]
182+
: never;
183+
};

packages/restate-sdk-core/src/public_api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export type {
3131
WorkflowDefinitionFrom,
3232
ArgType,
3333
HandlerReturnType,
34+
FlattenHandlersDefinition,
3435
} from "./core.js";
3536

3637
export type { Serde } from "./serde_api.js";

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@ import * as restate from "@restatedev/restate-sdk-clients";
1616
import type { Greeter } from "./greeter.js";
1717
import type { PaymentWorkflow } from "./workflow.js";
1818
import type { Counter } from "./object.js";
19+
import type { RawService } from "./raw.js";
1920

2021
const Greeter: Greeter = { name: "greeter" };
2122
const Counter: Counter = { name: "counter" };
23+
const RawService: RawService = { name: "raw" };
2224
const Workflow: PaymentWorkflow = { name: "payment" };
2325

2426
const ingress = restate.connect({ url: "http://localhost:8080" });
@@ -157,12 +159,12 @@ const workflow = async (name: string) => {
157159
console.log(await client.workflowAttach());
158160
};
159161

160-
const binaryRawCall = async (name: string) => {
161-
const counter = ingress.objectClient(Counter, name);
162+
const binaryRawCall = async () => {
163+
const rawService = ingress.serviceClient(RawService);
162164

163165
const buffer = new TextEncoder().encode("hello!");
164166

165-
const outBuffer = await counter.binary(
167+
const outBuffer = await rawService.binary(
166168
buffer,
167169
restate.rpc.opts({
168170
input: restate.serde.binary,
@@ -173,7 +175,7 @@ const binaryRawCall = async (name: string) => {
173175

174176
const str = new TextDecoder().decode(outBuffer);
175177

176-
console.log(`We got a buffer for ${name} : ${str}`);
178+
console.log(`We got a buffer : ${str}`);
177179
};
178180

179181
// Before running this example, make sure
@@ -198,5 +200,5 @@ Promise.resolve()
198200
.then(() => globalCustomHeaders("bob"))
199201
.then(() => customInterface("bob"))
200202
.then(() => delayedCall("bob"))
201-
.then(() => binaryRawCall("bob"))
203+
.then(() => binaryRawCall())
202204
.catch((e) => console.error(e));

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

Lines changed: 12 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,59 +9,35 @@
99
* https://github.com/restatedev/sdk-typescript/blob/main/LICENSE
1010
*/
1111

12-
import {
13-
createObjectHandler,
14-
createObjectSharedHandler,
15-
object,
16-
type ObjectContext,
17-
type ObjectSharedContext,
18-
serde,
19-
serve,
20-
} from "@restatedev/restate-sdk";
12+
import { object, serve } from "@restatedev/restate-sdk";
2113

2214
export const counter = object({
2315
name: "counter",
2416
handlers: {
25-
/**
26-
* Add amount to the currently stored count
27-
*/
28-
add: async (ctx: ObjectContext, amount: number) => {
17+
add: async (ctx, amount: number) => {
2918
const current = await ctx.get<number>("count");
3019
const updated = (current ?? 0) + amount;
3120
ctx.set("count", updated);
3221
return updated;
3322
},
34-
3523
/**
3624
* Get the current amount.
3725
*
3826
* Notice that VirtualObjects can have "shared" handlers.
3927
* These handlers can be executed concurrently to the exclusive handlers (i.e. add)
4028
* But they can not modify the state (set is missing from the ctx).
4129
*/
42-
current: createObjectSharedHandler(
43-
async (ctx: ObjectSharedContext): Promise<number> => {
44-
return (await ctx.get("count")) ?? 0;
45-
}
46-
),
47-
48-
/**
49-
* Handlers (shared or exclusive) can be configured to bypass JSON serialization,
50-
* by specifying the input (accept) and output (contentType) content types.
51-
*
52-
* to call that handler with binary data, you can use the following curl command:
53-
* curl -X POST -H "Content-Type: application/octet-stream" --data-binary 'hello' ${RESTATE_INGRESS_URL}/counter/mykey/binary
54-
*/
55-
binary: createObjectHandler(
56-
{
57-
input: serde.binary,
58-
output: serde.binary,
30+
current: {
31+
sharedHandler: async (ctx) => {
32+
return (await ctx.get<number>("count")) ?? 0;
5933
},
60-
async (ctx: ObjectContext, data: Uint8Array) => {
61-
// console.log("Received binary data", data);
62-
return data;
63-
}
64-
),
34+
},
35+
clear: {
36+
handler: async (ctx) => {
37+
ctx.clearAll();
38+
},
39+
enableLazyState: true,
40+
},
6541
},
6642
});
6743

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright (c) 2023-2024 - Restate Software, Inc., Restate GmbH
3+
*
4+
* This file is part of the Restate SDK for Node.js/TypeScript,
5+
* which is released under the MIT license.
6+
*
7+
* You can find a copy of the license in file LICENSE in the root
8+
* directory of this repository or package, or at
9+
* https://github.com/restatedev/sdk-typescript/blob/main/LICENSE
10+
*/
11+
12+
import { service, serve } from "@restatedev/restate-sdk";
13+
import { serde } from "@restatedev/restate-sdk-core";
14+
15+
const rawService = service({
16+
name: "raw",
17+
handlers: {
18+
binary: {
19+
/**
20+
* Handlers can be configured to bypass JSON serialization,
21+
* by specifying the input (accept) and output (contentType) content types.
22+
*
23+
* To call this handler with binary data, you can use the following curl command:
24+
* curl -X POST -H "Content-Type: application/octet-stream" --data-binary 'hello' ${RESTATE_INGRESS_URL}/raw/binary
25+
*/
26+
input: serde.binary,
27+
output: serde.binary,
28+
handler: async (ctx, data: Uint8Array) => {
29+
// console.log("Received binary data", data);
30+
return data;
31+
},
32+
},
33+
},
34+
});
35+
36+
export type RawService = typeof rawService;
37+
38+
serve({ services: [rawService] });

0 commit comments

Comments
 (0)