@@ -44,7 +44,7 @@ describe("Bus (Effect-native)", () => {
4444 const done = yield * Deferred . make < void > ( )
4545 const ready = yield * Latch . make ( )
4646
47- yield * Stream . runForEach ( bus . subscribe ( TestEvent . Ping ) , ( evt ) =>
47+ yield * Stream . runForEach ( yield * bus . subscribe ( TestEvent . Ping ) , ( evt ) =>
4848 Effect . gen ( function * ( ) {
4949 if ( evt . properties . value < 0 ) {
5050 yield * ready . open
@@ -71,7 +71,7 @@ describe("Bus (Effect-native)", () => {
7171 const done = yield * Deferred . make < void > ( )
7272 const ready = yield * Latch . make ( )
7373
74- yield * Stream . runForEach ( bus . subscribe ( TestEvent . Ping ) , ( evt ) =>
74+ yield * Stream . runForEach ( yield * bus . subscribe ( TestEvent . Ping ) , ( evt ) =>
7575 Effect . gen ( function * ( ) {
7676 if ( evt . properties . value < 0 ) {
7777 yield * ready . open
@@ -98,7 +98,7 @@ describe("Bus (Effect-native)", () => {
9898 const done = yield * Deferred . make < void > ( )
9999 const ready = yield * Latch . make ( )
100100
101- yield * Stream . runForEach ( bus . subscribeAll ( ) , ( evt ) =>
101+ yield * Stream . runForEach ( yield * bus . subscribeAll ( ) , ( evt ) =>
102102 Effect . gen ( function * ( ) {
103103 if ( evt . type === TestEvent . Warmup . type ) {
104104 yield * ready . open
@@ -129,7 +129,7 @@ describe("Bus (Effect-native)", () => {
129129 const readyA = yield * Latch . make ( )
130130 const readyB = yield * Latch . make ( )
131131
132- yield * Stream . runForEach ( bus . subscribe ( TestEvent . Ping ) , ( evt ) =>
132+ yield * Stream . runForEach ( yield * bus . subscribe ( TestEvent . Ping ) , ( evt ) =>
133133 Effect . gen ( function * ( ) {
134134 if ( evt . properties . value < 0 ) {
135135 yield * readyA . open
@@ -140,7 +140,7 @@ describe("Bus (Effect-native)", () => {
140140 } ) ,
141141 ) . pipe ( Effect . forkScoped )
142142
143- yield * Stream . runForEach ( bus . subscribe ( TestEvent . Ping ) , ( evt ) =>
143+ yield * Stream . runForEach ( yield * bus . subscribe ( TestEvent . Ping ) , ( evt ) =>
144144 Effect . gen ( function * ( ) {
145145 if ( evt . properties . value < 0 ) {
146146 yield * readyB . open
@@ -162,6 +162,92 @@ describe("Bus (Effect-native)", () => {
162162 } ) ,
163163 )
164164
165+ // RACE 1: eager subscription means publishing immediately after yield*
166+ // bus.subscribe is delivered. Regression for the old lazy `Stream.unwrap`
167+ // shape where PubSub.subscribe ran on first pull and missed any publish
168+ // in the hand-off window.
169+ it . instance ( "eager subscribe: publish after yield* is delivered without consumer-activation race" , ( ) =>
170+ Effect . gen ( function * ( ) {
171+ const bus = yield * Bus . Service
172+ const stream = yield * bus . subscribe ( TestEvent . Ping )
173+
174+ // Hand-off window: subscription is alive (we yielded). Publish goes
175+ // straight into the subscription queue, even with no consumer running.
176+ yield * bus . publish ( TestEvent . Ping , { value : 99 } )
177+
178+ const collected = yield * stream . pipe (
179+ Stream . take ( 1 ) ,
180+ Stream . runCollect ,
181+ Effect . timeout ( "400 millis" ) ,
182+ Effect . option ,
183+ )
184+
185+ expect ( collected . _tag ) . toBe ( "Some" )
186+ if ( collected . _tag === "Some" ) {
187+ const arr = Array . from ( collected . value )
188+ expect ( arr [ 0 ] . properties . value ) . toBe ( 99 )
189+ }
190+ } ) ,
191+ )
192+
193+ // RACE 2: same property for subscribeAll.
194+ it . instance ( "eager subscribeAll: publish after yield* is delivered" , ( ) =>
195+ Effect . gen ( function * ( ) {
196+ const bus = yield * Bus . Service
197+ const stream = yield * bus . subscribeAll ( )
198+
199+ yield * bus . publish ( TestEvent . Ping , { value : 42 } )
200+
201+ const collected = yield * stream . pipe (
202+ Stream . take ( 1 ) ,
203+ Stream . runCollect ,
204+ Effect . timeout ( "400 millis" ) ,
205+ Effect . option ,
206+ )
207+
208+ expect ( collected . _tag ) . toBe ( "Some" )
209+ if ( collected . _tag === "Some" ) {
210+ const arr = Array . from ( collected . value )
211+ expect ( arr [ 0 ] . type ) . toBe ( TestEvent . Ping . type )
212+ }
213+ } ) ,
214+ )
215+
216+ // RACE 3: the /event-handler shape exactly. With eager subscription, the
217+ // bus subscription is alive before Stream.concat ever starts. Publishes
218+ // during the prefix consumption window are queued and delivered.
219+ it . instance ( "eager subscribe: Stream.concat(initial, subscribe) delivers publish during prefix" , ( ) =>
220+ Effect . gen ( function * ( ) {
221+ const bus = yield * Bus . Service
222+ const sawInitial = yield * Deferred . make < void > ( )
223+ const sawPublish = yield * Deferred . make < number > ( )
224+
225+ type Frame = { marker ?: "initial" ; value ?: number }
226+ const subscriptionStream = yield * bus . subscribe ( TestEvent . Ping )
227+ const handlerStream : Stream . Stream < Frame > = Stream . make ( { marker : "initial" } as Frame ) . pipe (
228+ Stream . concat ( subscriptionStream . pipe ( Stream . map ( ( evt ) : Frame => ( { value : evt . properties . value } ) ) ) ) ,
229+ )
230+
231+ yield * Stream . runForEach ( handlerStream , ( frame ) =>
232+ Effect . gen ( function * ( ) {
233+ if ( frame . marker === "initial" ) {
234+ Deferred . doneUnsafe ( sawInitial , Effect . void )
235+ return
236+ }
237+ if ( frame . value !== undefined ) Deferred . doneUnsafe ( sawPublish , Effect . succeed ( frame . value ) )
238+ } ) ,
239+ ) . pipe ( Effect . forkScoped )
240+
241+ yield * Deferred . await ( sawInitial ) . pipe ( Effect . timeout ( "1 second" ) )
242+
243+ yield * bus . publish ( TestEvent . Ping , { value : 7 } )
244+
245+ const got = yield * Deferred . await ( sawPublish ) . pipe ( Effect . timeout ( "1 second" ) , Effect . option )
246+ expect ( got . _tag ) . toBe ( "Some" )
247+ if ( got . _tag === "Some" ) expect ( got . value ) . toBe ( 7 )
248+ } ) ,
249+ )
250+
165251 it . live ( "subscribeAll stream sees InstanceDisposed on disposal" , ( ) =>
166252 Effect . gen ( function * ( ) {
167253 const dir = yield * tmpdirScoped ( )
@@ -174,7 +260,7 @@ describe("Bus (Effect-native)", () => {
174260 yield * Effect . gen ( function * ( ) {
175261 const bus = yield * Bus . Service
176262
177- yield * Stream . runForEach ( bus . subscribeAll ( ) , ( evt ) =>
263+ yield * Stream . runForEach ( yield * bus . subscribeAll ( ) , ( evt ) =>
178264 Effect . gen ( function * ( ) {
179265 if ( evt . type === TestEvent . Warmup . type ) {
180266 yield * ready . open
0 commit comments