@@ -95,7 +95,7 @@ function buildTaskContext<S>(
9595
9696 const scheduleIn = ( delay : Duration . Input ) : Effect . Effect < void , TaskError > =>
9797 Effect . gen ( function * ( ) {
98- const ms = Duration . toMillis ( delay )
98+ const ms = Duration . toMillis ( Duration . fromInputUnsafe ( delay ) )
9999 const ts = Date . now ( ) + ms
100100 yield * alarm . set ( ts ) . pipe ( Effect . mapError ( mapAlarmError ) )
101101 yield * storage . set ( ALARM_KEY , ts ) . pipe ( Effect . mapError ( mapStorageError ) )
@@ -140,18 +140,53 @@ function cleanup(
140140 } ) . pipe ( Effect . mapError ( ( e ) => new TaskExecutionError ( { cause : e } ) ) )
141141}
142142
143+ // ---------------------------------------------------------------------------
144+ // Internal: type guard for PurgeSignal
145+ // ---------------------------------------------------------------------------
146+
147+ function isPurgeSignal ( error : unknown ) : error is PurgeSignal {
148+ return error instanceof PurgeSignal
149+ }
150+
151+ // ---------------------------------------------------------------------------
152+ // Internal: handle errors from a handler effect, routing PurgeSignal to
153+ // cleanup and other errors to onError (if provided).
154+ // ---------------------------------------------------------------------------
155+
156+ function handleHandlerError < S , HErr , OErr > (
157+ ctx : TaskContext < S > ,
158+ error : HErr ,
159+ onError : ( ( ctx : TaskContext < S > , error : HErr ) => Effect . Effect < void , OErr , never > ) | undefined ,
160+ storage : Storage [ "Service" ] ,
161+ alarm : Alarm [ "Service" ] ,
162+ ) : Effect . Effect < void , TaskExecutionError > {
163+ if ( isPurgeSignal ( error ) ) {
164+ return cleanup ( storage , alarm )
165+ }
166+
167+ if ( ! onError ) {
168+ return Effect . fail ( new TaskExecutionError ( { cause : error } ) )
169+ }
170+
171+ return onError ( ctx , error ) . pipe (
172+ Effect . catch ( ( oErr ) =>
173+ isPurgeSignal ( oErr )
174+ ? cleanup ( storage , alarm )
175+ : Effect . fail ( new TaskExecutionError ( { cause : oErr } ) ) ,
176+ ) ,
177+ Effect . mapError ( ( e ) => new TaskExecutionError ( { cause : e } ) ) ,
178+ )
179+ }
180+
143181// ---------------------------------------------------------------------------
144182// Internal: build RegisteredTask from a fully-resolved definition (R = never).
145183//
146184// All handler effects in the definition have R = never, so no layer provision
147- // is needed. S, E, Err are captured in the closures via the generic params.
148- //
149- // The error widening trick (mapError to Err | PurgeSignal) lets catchTag
150- // find the PurgeSignal tag even when Err is a generic type parameter.
185+ // is needed. S, E, EErr, AErr are captured in closures via generic params.
151186// ---------------------------------------------------------------------------
152187
153- function buildRegisteredTask < S , E , Err > (
154- definition : TaskDefinition < S , E , Err , never > ,
188+ function buildRegisteredTask < S , E , EErr , AErr , OErr , GErr > (
189+ definition : TaskDefinition < S , E , EErr , AErr , never , OErr , GErr > ,
155190) : RegisteredTask {
156191 const decodeEvent = Schema . decodeUnknownEffect ( definition . event )
157192 const decodeState = Schema . decodeUnknownEffect ( definition . state )
@@ -171,26 +206,9 @@ function buildRegisteredTask<S, E, Err>(
171206
172207 const ctx = buildTaskContext ( storage , alarm , id , name , decodeState , encodeState )
173208
174- // Widen error to include PurgeSignal so catchTag can resolve the tag
175- const withPurge = definition . onEvent ( ctx , event ) . pipe (
176- Effect . mapError ( ( e ) : Err | PurgeSignal => e ) ,
177- Effect . catchTag ( "PurgeSignal" , ( ) => cleanup ( storage , alarm ) ) ,
178- )
179-
180- if ( ! definition . onError ) {
181- yield * withPurge . pipe (
182- Effect . mapError ( ( e ) => new TaskExecutionError ( { cause : e } ) ) ,
183- )
184- return
185- }
186-
187- yield * withPurge . pipe (
209+ yield * definition . onEvent ( ctx , event ) . pipe (
188210 Effect . catch ( ( error ) =>
189- definition . onError ! ( ctx , error ) . pipe (
190- Effect . mapError ( ( e ) : Err | PurgeSignal => e ) ,
191- Effect . catchTag ( "PurgeSignal" , ( ) => cleanup ( storage , alarm ) ) ,
192- Effect . mapError ( ( e ) => new TaskExecutionError ( { cause : e } ) ) ,
193- )
211+ handleHandlerError ( ctx , error , definition . onError , storage , alarm ) ,
194212 ) ,
195213 )
196214 } )
@@ -202,27 +220,16 @@ function buildRegisteredTask<S, E, Err>(
202220 name : string ,
203221 ) : Effect . Effect < void , TaskExecutionError > =>
204222 Effect . gen ( function * ( ) {
205- const ctx = buildTaskContext ( storage , alarm , id , name , decodeState , encodeState )
206-
207- const withPurge = definition . onAlarm ( ctx ) . pipe (
208- Effect . mapError ( ( e ) : Err | PurgeSignal => e ) ,
209- Effect . catchTag ( "PurgeSignal" , ( ) => cleanup ( storage , alarm ) ) ,
223+ // Clear the alarm bookmark — the alarm has fired, so it's no longer pending
224+ yield * storage . delete ( ALARM_KEY ) . pipe (
225+ Effect . mapError ( ( e ) => new TaskExecutionError ( { cause : e } ) ) ,
210226 )
211227
212- if ( ! definition . onError ) {
213- yield * withPurge . pipe (
214- Effect . mapError ( ( e ) => new TaskExecutionError ( { cause : e } ) ) ,
215- )
216- return
217- }
228+ const ctx = buildTaskContext ( storage , alarm , id , name , decodeState , encodeState )
218229
219- yield * withPurge . pipe (
230+ yield * definition . onAlarm ( ctx ) . pipe (
220231 Effect . catch ( ( error ) =>
221- definition . onError ! ( ctx , error ) . pipe (
222- Effect . mapError ( ( e ) : Err | PurgeSignal => e ) ,
223- Effect . catchTag ( "PurgeSignal" , ( ) => cleanup ( storage , alarm ) ) ,
224- Effect . mapError ( ( e ) => new TaskExecutionError ( { cause : e } ) ) ,
225- )
232+ handleHandlerError ( ctx , error , definition . onError , storage , alarm ) ,
226233 ) ,
227234 )
228235 } )
@@ -258,8 +265,8 @@ function buildRegisteredTask<S, E, Err>(
258265// registerTask — for definitions with no service requirements (R = never)
259266// ---------------------------------------------------------------------------
260267
261- export function registerTask < S , E , Err > (
262- definition : TaskDefinition < S , E , Err , never > ,
268+ export function registerTask < S , E , EErr , AErr , OErr , GErr > (
269+ definition : TaskDefinition < S , E , EErr , AErr , never , OErr , GErr > ,
263270) : RegisteredTask {
264271 return buildRegisteredTask ( definition )
265272}
@@ -272,11 +279,11 @@ export function registerTask<S, E, Err>(
272279// shared buildRegisteredTask.
273280// ---------------------------------------------------------------------------
274281
275- export function registerTaskWithLayer < S , E , Err , R > (
276- definition : TaskDefinition < S , E , Err , R > ,
282+ export function registerTaskWithLayer < S , E , EErr , AErr , R , OErr , GErr > (
283+ definition : TaskDefinition < S , E , EErr , AErr , R , OErr , GErr > ,
277284 layer : Layer . Layer < R > ,
278285) : RegisteredTask {
279- const resolved : TaskDefinition < S , E , Err , never > = {
286+ const resolved : TaskDefinition < S , E , EErr , AErr , never , OErr , GErr > = {
280287 _tag : "TaskDefinition" ,
281288 state : definition . state ,
282289 event : definition . event ,
0 commit comments