diff --git a/src/__tests__/definition.test.ts b/src/__tests__/definition.test.ts index 786b4f2..10fbeb8 100644 --- a/src/__tests__/definition.test.ts +++ b/src/__tests__/definition.test.ts @@ -13,12 +13,14 @@ import * as Exit from "effect/Exit"; import * as Fiber from "effect/Fiber"; import * as Option from "effect/Option"; import * as Result from "effect/Result"; +import * as Schema from "effect/Schema"; import { TestClock } from "effect/testing"; import * as WorkflowEngine from "effect/unstable/workflow/WorkflowEngine"; import { describe, expect, expectTypeOf, it } from "vitest"; import { handle, implementActivities, type ActivityRunner } from "../activities.js"; import { continueAsNew, + defineActivity, executeChild, sleepUntil, version, @@ -80,6 +82,38 @@ const _types = () => { // @ts-expect-error wrong payload shape Charge({ orderId: 1 }); + // Omitted schemas retain their runtime defaults in the type channels. + const Defaults = defineActivity("defaults", { payload: { id: Schema.String } }); + const defaults = Defaults({ id: "x" }); + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + + // An error schema can still be inferred while success defaults to void. + const ErrorOnly = defineActivity("errorOnly", { + payload: { id: Schema.String }, + error: CardDeclined, + }); + const errorOnly = ErrorOnly({ id: "x" }); + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + + defineActivity<"missingSuccess", { id: typeof Schema.String }, typeof Schema.String>( + "missingSuccess", + // @ts-expect-error a non-default success generic requires its runtime schema + { payload: { id: Schema.String } }, + ); + + defineActivity< + "missingError", + { id: typeof Schema.String }, + Schema.Void, + typeof CardDeclined + >( + "missingError", + // @ts-expect-error a non-default error generic requires its runtime schema + { payload: { id: Schema.String } }, + ); + // Messages: deferred success, mailbox payload, update request typing. expectTypeOf>().toEqualTypeOf(); expectTypeOf>().toEqualTypeOf<{ diff --git a/src/definition.ts b/src/definition.ts index 61b2fa9..bc2c6c9 100644 --- a/src/definition.ts +++ b/src/definition.ts @@ -133,6 +133,22 @@ export type SuccessOf = export type ErrorOf = A extends TypedActivity ? E["Type"] : never; +type IsExactly = [A] extends [B] ? ([B] extends [A] ? true : false) : false; + +type ActivityDeclaration< + Payload extends Schema.Struct.Fields | Schema.Top, + Success extends Schema.Top, + Error extends Schema.Top, +> = { + readonly payload: Payload; + readonly options?: TypedActivityOptions; +} & (IsExactly extends true + ? { readonly success?: Success } + : { readonly success: Success }) + & (IsExactly extends true + ? { readonly error?: Error } + : { readonly error: Error }); + /** Build the serializable projection of an activity declaration. */ const makeTypedActivity = < const Name extends string, @@ -141,12 +157,7 @@ const makeTypedActivity = < Error extends Schema.Top = Schema.Never, >( name: Name, - definition: { - readonly payload: Payload; - readonly success?: Success; - readonly error?: Error; - readonly options?: TypedActivityOptions; - }, + definition: ActivityDeclaration, ): TypedActivity< Name, Payload extends Schema.Struct.Fields ? Schema.Struct : Payload, @@ -319,12 +330,7 @@ export const defineActivity = < Error extends Schema.Top = Schema.Never, >( name: Name, - decl: { - readonly payload: Payload; - readonly success?: Success; - readonly error?: Error; - readonly options?: TypedActivityOptions; - }, + decl: ActivityDeclaration, ): DefinedActivity< Name, Payload extends Schema.Struct.Fields ? Schema.Struct : Payload,