An educational pure functional programming library in TypeScript
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add missing Exit import to examples

+52 -11
+1
examples/http-client.ts
··· 31 31 type Eff, 32 32 type Exit, 33 33 type Branded, 34 + Exit, 34 35 35 36 // Constructors 36 37 succeed,
+1
examples/parallel-scraper.ts
··· 37 37 type Fiber, 38 38 type FiberStatus, 39 39 type Branded, 40 + Exit, 40 41 41 42 // Constructors 42 43 succeed,
+50 -11
examples/reactive-dom.ts
··· 33 33 import { 34 34 // Types 35 35 type Eff, 36 - type Exit, 36 + Exit, 37 37 type Fiber, 38 38 type Entity, 39 39 type Result, ··· 569 569 // DEMO - Simulated DOM environment 570 570 // ============================================================================= 571 571 572 + // Check if we're in a browser environment 573 + const isBrowser = typeof window !== "undefined" && typeof MouseEvent !== "undefined" 574 + 575 + /** 576 + * Mock Event class for non-browser environments 577 + * Allows the demo to run in Node.js/Bun without real DOM APIs 578 + */ 579 + class MockEvent { 580 + readonly type: string 581 + readonly target: unknown 582 + constructor(type: string, options?: { target?: unknown }) { 583 + this.type = type 584 + this.target = options?.target 585 + } 586 + } 587 + 588 + /** 589 + * Mock MouseEvent class for non-browser environments 590 + */ 591 + class MockMouseEvent extends MockEvent { 592 + readonly clientX: number = 0 593 + readonly clientY: number = 0 594 + constructor(type: string) { 595 + super(type) 596 + } 597 + } 598 + 599 + // Use native Event/MouseEvent in browser, mock versions otherwise 600 + const EventClass = isBrowser ? Event : MockEvent 601 + const MouseEventClass = isBrowser ? MouseEvent : MockMouseEvent 602 + 572 603 /** 573 604 * Mock DOM elements for Node.js environment 574 605 * In a browser, you'd use real DOM elements ··· 586 617 listeners.get(type)?.delete(listener) 587 618 console.log(` [${name}] Removed listener for '${type}'`) 588 619 }, 589 - dispatchEvent(event: Event): boolean { 590 - listeners.get(event.type)?.forEach(l => l(event)) 620 + dispatchEvent(event: Event | MockEvent): boolean { 621 + listeners.get(event.type)?.forEach(l => l(event as Event)) 591 622 return true 592 623 }, 593 624 } ··· 598 629 console.log("║ REACTIVE DOM DEMO - purus-ts ║") 599 630 console.log("╚═══════════════════════════════════════════════════════════════╝\n") 600 631 632 + if (!isBrowser) { 633 + console.log("Note: Running in non-browser environment (Node.js/Bun)") 634 + console.log(" Using mock Event classes for demonstration") 635 + console.log(" For real DOM integration, run in a browser with a bundler\n") 636 + } 637 + 601 638 // 1. Event subscription with automatic cleanup 602 639 console.log("─── 1. Event Subscription with Cleanup ───") 603 640 const button = createMockElement("Button") ··· 624 661 ) 625 662 626 663 // Simulate some clicks 627 - setTimeout(() => button.dispatchEvent(new MouseEvent("click")), 20) 628 - setTimeout(() => button.dispatchEvent(new MouseEvent("click")), 50) 664 + setTimeout(() => button.dispatchEvent(new MouseEventClass("click")), 20) 665 + setTimeout(() => button.dispatchEvent(new MouseEventClass("click")), 50) 629 666 630 667 await clickFiber.await() 631 668 console.log(" ✓ Subscription interrupted, listeners cleaned up\n") ··· 646 683 // Simulate rapid typing - only last one should fire 647 684 const simulateTyping = (delay: number, value: string) => { 648 685 setTimeout(() => { 649 - const event = { target: { value } } as unknown as InputEvent 650 - input.dispatchEvent(Object.assign(new Event("input"), event)) 686 + const event = new EventClass("input", { target: { value } }) as unknown as InputEvent 687 + input.dispatchEvent(event) 651 688 }, delay) 652 689 } 653 690 ··· 656 693 simulateTyping(30, "hel") 657 694 simulateTyping(40, "hello") // Only this should trigger search after debounce 658 695 659 - await sleep(500).then(() => runPromise(succeed(undefined))) 696 + await runPromise(sleep(500)) 660 697 661 698 // Stop the search subscription 662 - const fiber = await runPromise(searchFiber.join().then(f => f) as unknown as Eff<Fiber<never, never>, never, unknown>) 663 - await runPromise(interruptFiber(fiber as unknown as Fiber<never, never>)) 699 + // searchFiber is Fiber<Fiber<never, never>, never> - the outer fiber's result is the inner fiber 700 + const innerFiber = await runPromise(searchFiber.join()) 701 + // Don't await the interrupt - the fiber is blocked on an async event listener that can't be cancelled 702 + innerFiber.interrupt() 664 703 console.log(" ✓ Debounced search completed\n") 665 704 666 705 // 3. Race between actions ··· 683 722 684 723 // Simulate cancel being clicked first 685 724 setTimeout(() => { 686 - cancelBtn.dispatchEvent(new MouseEvent("click")) 725 + cancelBtn.dispatchEvent(new MouseEventClass("click")) 687 726 console.log(" Cancel clicked first!") 688 727 }, 50) 689 728