Skip to content

Commit 267a23b

Browse files
authored
Enforce type checks when specifying serde (#501)
1 parent f717281 commit 267a23b

File tree

4 files changed

+61
-25
lines changed

4 files changed

+61
-25
lines changed

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

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

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

32+
export type ArgType<T> = T extends (ctx: any) => any
33+
? void
34+
: T extends (ctx: any, input: infer I) => any
35+
? I
36+
: never;
37+
38+
export type HandlerReturnType<T> = T extends (
39+
ctx: any,
40+
input: any
41+
) => Promise<infer R>
42+
? R
43+
: never;
44+
3245
export type ServiceHandler<F, C = RestateContext> = F extends (
3346
ctx: C
3447
) => Promise<any>

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ export type {
2929
RestateWorkflowSharedContext,
3030
Workflow,
3131
WorkflowDefinitionFrom,
32+
ArgType,
33+
HandlerReturnType,
3234
} from "./core.js";
3335

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

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ export namespace serde {
7171
return JSON.parse(new TextDecoder().decode(data)) as T;
7272
}
7373

74-
schema<U>(schema: object): JsonSerde<U> {
75-
return new JsonSerde<U>(schema);
74+
schema<U>(schema: object): Serde<U> {
75+
return new JsonSerde<U>(schema) as Serde<U>;
7676
}
7777
}
7878

packages/restate-sdk/src/types/rpc.ts

Lines changed: 44 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import {
3434
type WorkflowDefinition,
3535
type WorkflowSharedHandler,
3636
type Serde,
37+
type ArgType,
38+
type HandlerReturnType,
3739
serde,
3840
} from "@restatedev/restate-sdk-core";
3941
import { TerminalError } from "./errors.js";
@@ -245,7 +247,7 @@ export enum HandlerKind {
245247
WORKFLOW,
246248
}
247249

248-
export type ServiceHandlerOpts = {
250+
export type ServiceHandlerOpts<I, O> = {
249251
/**
250252
* Define the acceptable content-type. Wildcards can be used, e.g. `application/*` or `* / *`.
251253
* If not provided, the `input.contentType` will be used instead.
@@ -263,7 +265,7 @@ export type ServiceHandlerOpts = {
263265
* restate.serde.binary the skip serialization/deserialization altogether.
264266
* in that case, the input parameter is a Uint8Array.
265267
*/
266-
input?: Serde<unknown>;
268+
input?: Serde<I>;
267269

268270
/**
269271
* The Serde to use for serializing the output.
@@ -273,7 +275,7 @@ export type ServiceHandlerOpts = {
273275
* restate.serde.binary the skip serialization/deserialization altogether.
274276
* in that case, the output parameter is a Uint8Array.
275277
*/
276-
output?: Serde<unknown>;
278+
output?: Serde<O>;
277279

278280
/**
279281
* An additional description for the handler, for documentation purposes.
@@ -286,7 +288,7 @@ export type ServiceHandlerOpts = {
286288
metadata?: Record<string, string>;
287289
};
288290

289-
export type ObjectHandlerOpts = {
291+
export type ObjectHandlerOpts<I, O> = {
290292
/**
291293
* Define the acceptable content-type. Wildcards can be used, e.g. `application/*` or `* / *`.
292294
* If not provided, the `input.contentType` will be used instead.
@@ -304,7 +306,7 @@ export type ObjectHandlerOpts = {
304306
* restate.serde.binary the skip serialization/deserialization altogether.
305307
* in that case, the input parameter is a Uint8Array.
306308
*/
307-
input?: Serde<unknown>;
309+
input?: Serde<I>;
308310

309311
/**
310312
* The Serde to use for serializing the output.
@@ -314,7 +316,7 @@ export type ObjectHandlerOpts = {
314316
* restate.serde.binary the skip serialization/deserialization altogether.
315317
* in that case, the output parameter is a Uint8Array.
316318
*/
317-
output?: Serde<unknown>;
319+
output?: Serde<O>;
318320

319321
/**
320322
* An additional description for the handler, for documentation purposes.
@@ -327,7 +329,7 @@ export type ObjectHandlerOpts = {
327329
metadata?: Record<string, string>;
328330
};
329331

330-
export type WorkflowHandlerOpts = {
332+
export type WorkflowHandlerOpts<I, O> = {
331333
/**
332334
* Define the acceptable content-type. Wildcards can be used, e.g. `application/*` or `* / *`.
333335
* If not provided, the `input.contentType` will be used instead.
@@ -345,7 +347,7 @@ export type WorkflowHandlerOpts = {
345347
* restate.serde.binary the skip serialization/deserialization altogether.
346348
* in that case, the input parameter is a Uint8Array.
347349
*/
348-
input?: Serde<unknown>;
350+
input?: Serde<I>;
349351

350352
/**
351353
* The Serde to use for serializing the output.
@@ -355,7 +357,7 @@ export type WorkflowHandlerOpts = {
355357
* restate.serde.binary the skip serialization/deserialization altogether.
356358
* in that case, the output parameter is a Uint8Array.
357359
*/
358-
output?: Serde<unknown>;
360+
output?: Serde<O>;
359361

360362
/**
361363
* An additional description for the handler, for documentation purposes.
@@ -374,7 +376,10 @@ export class HandlerWrapper {
374376
public static from(
375377
kind: HandlerKind,
376378
handler: Function,
377-
opts?: ServiceHandlerOpts | ObjectHandlerOpts | WorkflowHandlerOpts
379+
opts?:
380+
| ServiceHandlerOpts<unknown, unknown>
381+
| ObjectHandlerOpts<unknown, unknown>
382+
| WorkflowHandlerOpts<unknown, unknown>
378383
): HandlerWrapper {
379384
const inputSerde: Serde<unknown> = opts?.input ?? defaultSerde();
380385
const outputSerde: Serde<unknown> = opts?.output ?? defaultSerde();
@@ -463,15 +468,15 @@ export namespace handlers {
463468
* @param fn the actual handler code to execute
464469
*/
465470
export function handler<F>(
466-
opts: ServiceHandlerOpts,
471+
opts: ServiceHandlerOpts<ArgType<F>, HandlerReturnType<F>>,
467472
fn: ServiceHandler<F, Context>
468473
): F {
469474
return HandlerWrapper.from(HandlerKind.SERVICE, fn, opts).transpose();
470475
}
471476

472477
export namespace workflow {
473478
export function workflow<F>(
474-
opts: WorkflowHandlerOpts,
479+
opts: WorkflowHandlerOpts<ArgType<F>, HandlerReturnType<F>>,
475480
fn: WorkflowHandler<F, WorkflowContext<any>>
476481
): F;
477482

@@ -480,13 +485,18 @@ export namespace handlers {
480485
): F;
481486

482487
export function workflow<F>(
483-
optsOrFn: WorkflowHandlerOpts | WorkflowHandler<F, WorkflowContext<any>>,
488+
optsOrFn:
489+
| WorkflowHandlerOpts<ArgType<F>, HandlerReturnType<F>>
490+
| WorkflowHandler<F, WorkflowContext<any>>,
484491
fn?: WorkflowHandler<F, WorkflowContext<any>>
485492
): F {
486493
if (typeof optsOrFn === "function") {
487494
return HandlerWrapper.from(HandlerKind.WORKFLOW, optsOrFn).transpose();
488495
}
489-
const opts = optsOrFn satisfies WorkflowHandlerOpts;
496+
const opts = optsOrFn satisfies WorkflowHandlerOpts<
497+
ArgType<F>,
498+
HandlerReturnType<F>
499+
>;
490500
if (typeof fn !== "function") {
491501
throw new TypeError("The second argument must be a function");
492502
}
@@ -503,7 +513,7 @@ export namespace handlers {
503513
* @param fn the handler to execute
504514
*/
505515
export function shared<F>(
506-
opts: WorkflowHandlerOpts,
516+
opts: WorkflowHandlerOpts<ArgType<F>, HandlerReturnType<F>>,
507517
fn: WorkflowSharedHandler<F, WorkflowSharedContext<any>>
508518
): F;
509519

@@ -531,14 +541,17 @@ export namespace handlers {
531541
*/
532542
export function shared<F>(
533543
optsOrFn:
534-
| WorkflowHandlerOpts
544+
| WorkflowHandlerOpts<ArgType<F>, HandlerReturnType<F>>
535545
| WorkflowSharedHandler<F, WorkflowSharedContext<any>>,
536546
fn?: WorkflowSharedHandler<F, WorkflowSharedContext<any>>
537547
): F {
538548
if (typeof optsOrFn === "function") {
539549
return HandlerWrapper.from(HandlerKind.SHARED, optsOrFn).transpose();
540550
}
541-
const opts = optsOrFn satisfies ObjectHandlerOpts;
551+
const opts = optsOrFn satisfies ObjectHandlerOpts<
552+
ArgType<F>,
553+
HandlerReturnType<F>
554+
>;
542555
if (typeof fn !== "function") {
543556
throw new TypeError("The second argument must be a function");
544557
}
@@ -556,7 +569,7 @@ export namespace handlers {
556569
* @param fn the handler to execute
557570
*/
558571
export function exclusive<F>(
559-
opts: ObjectHandlerOpts,
572+
opts: ObjectHandlerOpts<ArgType<F>, HandlerReturnType<F>>,
560573
fn: ObjectHandler<F, ObjectContext<any>>
561574
): F;
562575

@@ -588,13 +601,18 @@ export namespace handlers {
588601
* @param fn the handler to execute
589602
*/
590603
export function exclusive<F>(
591-
optsOrFn: ObjectHandlerOpts | ObjectHandler<F, ObjectContext<any>>,
604+
optsOrFn:
605+
| ObjectHandlerOpts<ArgType<F>, HandlerReturnType<F>>
606+
| ObjectHandler<F, ObjectContext<any>>,
592607
fn?: ObjectHandler<F, ObjectContext<any>>
593608
): F {
594609
if (typeof optsOrFn === "function") {
595610
return HandlerWrapper.from(HandlerKind.EXCLUSIVE, optsOrFn).transpose();
596611
}
597-
const opts = optsOrFn satisfies ObjectHandlerOpts;
612+
const opts = optsOrFn satisfies ObjectHandlerOpts<
613+
ArgType<F>,
614+
HandlerReturnType<F>
615+
>;
598616
if (typeof fn !== "function") {
599617
throw new TypeError("The second argument must be a function");
600618
}
@@ -613,7 +631,7 @@ export namespace handlers {
613631
* @param fn the handler to execute
614632
*/
615633
export function shared<F>(
616-
opts: ObjectHandlerOpts,
634+
opts: ObjectHandlerOpts<ArgType<F>, HandlerReturnType<F>>,
617635
fn: ObjectSharedHandler<F, ObjectSharedContext<any>>
618636
): F;
619637

@@ -645,14 +663,17 @@ export namespace handlers {
645663
*/
646664
export function shared<F>(
647665
optsOrFn:
648-
| ObjectHandlerOpts
666+
| ObjectHandlerOpts<ArgType<F>, HandlerReturnType<F>>
649667
| ObjectSharedHandler<F, ObjectSharedContext<any>>,
650668
fn?: ObjectSharedHandler<F, ObjectSharedContext<any>>
651669
): F {
652670
if (typeof optsOrFn === "function") {
653671
return HandlerWrapper.from(HandlerKind.SHARED, optsOrFn).transpose();
654672
}
655-
const opts = optsOrFn satisfies ObjectHandlerOpts;
673+
const opts = optsOrFn satisfies ObjectHandlerOpts<
674+
ArgType<F>,
675+
HandlerReturnType<F>
676+
>;
656677
if (typeof fn !== "function") {
657678
throw new TypeError("The second argument must be a function");
658679
}

0 commit comments

Comments
 (0)