Skip to content

Instantly share code, notes, and snippets.

@Lucifier129
Last active December 30, 2025 07:10
Show Gist options
  • Select an option

  • Save Lucifier129/7ad88b98d61e0b58d6cd48783e62fd04 to your computer and use it in GitHub Desktop.

Select an option

Save Lucifier129/7ad88b98d61e0b58d6cd48783e62fd04 to your computer and use it in GitHub Desktop.
// =============================================================================
// PART 1: PRIMITIVES (Result & Accessor)
// =============================================================================
type Result<T> = { ok: true; value: T } | { ok: false; error: string }
const Ok = <T>(value: T): Result<T> => ({ ok: true, value })
const Err = (error: string): Result<any> => ({ ok: false, error })
type Getter<Local, Root> = (root: Root) => Result<Local>
type Setter<Local, Root> = (value: Local, root: Root) => Result<Root>
type RawAccessor<Local, Root = any> = {
get: Getter<Local, Root>
set: Setter<Local, Root>
}
class Accessor<Local, Root = any> {
readonly get: Getter<Local, Root>
readonly set: Setter<Local, Root>
constructor(get: Getter<Local, Root>, set: Setter<Local, Root>) {
this.get = get
this.set = set
}
static id<State>(): Accessor<State, State> {
return new Accessor(Ok, (newValue, _oldValue) => Ok(newValue))
}
map<Next>(get: Getter<Next, Local>, set: Setter<Next, Local>): Accessor<Next, Root> {
return new Accessor(
(root) => {
const localResult = this.get(root)
return localResult.ok ? get(localResult.value) : localResult
},
(nextValue, root) => {
const localResult = this.get(root)
if (!localResult.ok) return localResult
const newLocalResult = set(nextValue, localResult.value)
return newLocalResult.ok ? this.set(newLocalResult.value, root) : newLocalResult
},
)
}
compose<Next>(next: Accessor<Next, Local>): Accessor<Next, Root> {
return this.map(next.get, next.set)
}
field<Key extends keyof Local>(key: Key): Accessor<Local[Key], Root> {
return this.map(
(local) => Ok(local[key]),
(newValue, local) => Ok({ ...local, [key]: newValue }),
)
}
index(targetIndex: number): Accessor<Local extends Array<infer Item> ? Item : never, Root> {
return this.map(
(local) => {
const array = local as any[]
return targetIndex >= 0 && targetIndex < array.length
? Ok(array[targetIndex])
: Err(`Index[${targetIndex}] out of bounds`)
},
(newValue, local) => {
const array = [...(local as any[])]
if (targetIndex >= 0 && targetIndex < array.length) {
array[targetIndex] = newValue
return Ok(array as any)
}
return Err(`Index[${targetIndex}] out of bounds`)
},
)
}
match<Matched extends Local>(predicate: (local: Local) => local is Matched): Accessor<Matched, Root> {
return this.map(
(local) => (predicate(local) ? Ok(local) : Err('Match predicate failed')),
(newValue, _local) => Ok(newValue as Local),
)
}
find<Item = Local extends Array<infer ArrayItem> ? ArrayItem : never>(
predicate: (item: Item, index: number) => boolean,
): Accessor<Item, Root> {
return this.map(
(local) => {
const array = local as any as Item[]
const foundIndex = array.findIndex(predicate)
return foundIndex !== -1 ? Ok(array[foundIndex]) : Err('Item not found')
},
(newValue, local) => {
const array = [...(local as any as any[])]
const foundIndex = array.findIndex(predicate)
if (foundIndex !== -1) {
array[foundIndex] = newValue
return Ok(array as any)
}
return Err('Item not found on set')
},
)
}
}
// =============================================================================
// PART 2: STREAM (Observable-like Abstraction)
// =============================================================================
type Observer<T> = {
next: (value: T) => void
error?: (err: any) => void
complete?: () => void
}
type Subscription = {
unsubscribe: () => void
}
type StreamSubscriber<T> = (observer: Observer<T>) => Subscription | (() => void) | void
class Stream<T> {
private _subscribe: StreamSubscriber<T>
constructor(subscribe: StreamSubscriber<T>) {
this._subscribe = subscribe
}
subscribe(observerOrNext: Observer<T> | ((value: T) => void)): Subscription {
const observer: Observer<T> = typeof observerOrNext === 'function' ? { next: observerOrNext } : observerOrNext
const result = this._subscribe(observer)
if (!result) {
return { unsubscribe: () => {} }
}
if (typeof result === 'function') {
return { unsubscribe: result }
}
return result
}
// --- Static Constructors ---
static of<T>(...values: T[]): Stream<T> {
return new Stream((observer) => {
for (const value of values) {
observer.next(value)
}
observer.complete?.()
})
}
static from<T>(iterable: Iterable<T>): Stream<T> {
return new Stream((observer) => {
for (const value of iterable) {
observer.next(value)
}
observer.complete?.()
})
}
static fromPromise<T>(promise: Promise<T>): Stream<T> {
return new Stream((observer) => {
let cancelled = false
promise
.then((value) => {
if (!cancelled) {
observer.next(value)
observer.complete?.()
}
})
.catch((err) => {
if (!cancelled) {
observer.error?.(err)
}
})
return () => {
cancelled = true
}
})
}
static interval(ms: number): Stream<number> {
return new Stream((observer) => {
let count = 0
const id = setInterval(() => observer.next(count++), ms)
return () => clearInterval(id)
})
}
static merge<T>(...streams: Stream<T>[]): Stream<T> {
return new Stream((observer) => {
let completedCount = 0
const subscriptions: Subscription[] = []
for (const stream of streams) {
const subscription = stream.subscribe({
next: (value) => observer.next(value),
error: (error) => observer.error?.(error),
complete: () => {
completedCount++
if (completedCount === streams.length) {
observer.complete?.()
}
},
})
subscriptions.push(subscription)
}
return () => subscriptions.forEach((subscription) => subscription.unsubscribe())
})
}
static combine<T extends any[]>(...streams: { [K in keyof T]: Stream<T[K]> }): Stream<T> {
return new Stream((observer) => {
const latestValues: any[] = new Array(streams.length)
const hasEmittedValue: boolean[] = new Array(streams.length).fill(false)
let completedCount = 0
const subscriptions: Subscription[] = []
streams.forEach((stream, streamIndex) => {
const subscription = stream.subscribe({
next: (value) => {
latestValues[streamIndex] = value
hasEmittedValue[streamIndex] = true
if (hasEmittedValue.every(Boolean)) {
observer.next([...latestValues] as T)
}
},
error: (error) => observer.error?.(error),
complete: () => {
completedCount++
if (completedCount === streams.length) {
observer.complete?.()
}
},
})
subscriptions.push(subscription)
})
return () => subscriptions.forEach((subscription) => subscription.unsubscribe())
})
}
static combineLatest<T extends any[]>(...streams: { [K in keyof T]: Stream<T[K]> }): Stream<T> {
return Stream.combine<T>(...(streams as any))
}
static empty<T = never>(): Stream<T> {
return new Stream((observer) => {
observer.complete?.()
})
}
static never<T = never>(): Stream<T> {
return new Stream(() => {})
}
// --- Operators ---
map<Result>(mapper: (value: T) => Result): Stream<Result> {
return new Stream((observer) => {
return this.subscribe({
next: (value) => observer.next(mapper(value)),
error: (error) => observer.error?.(error),
complete: () => observer.complete?.(),
})
})
}
filter(predicate: (value: T) => boolean): Stream<T> {
return new Stream((observer) => {
return this.subscribe({
next: (value) => {
if (predicate(value)) observer.next(value)
},
error: (error) => observer.error?.(error),
complete: () => observer.complete?.(),
})
})
}
take(count: number): Stream<T> {
return new Stream((observer) => {
let taken = 0
const subscription = this.subscribe({
next: (value) => {
if (taken < count) {
taken++
observer.next(value)
if (taken >= count) {
observer.complete?.()
subscription.unsubscribe()
}
}
},
error: (error) => observer.error?.(error),
complete: () => observer.complete?.(),
})
return subscription
})
}
takeUntil(notifier: Stream<any>): Stream<T> {
return new Stream((observer) => {
const sourceSubscription = this.subscribe({
next: (value) => observer.next(value),
error: (error) => observer.error?.(error),
complete: () => observer.complete?.(),
})
const notifierSubscription = notifier.subscribe({
next: () => {
observer.complete?.()
sourceSubscription.unsubscribe()
notifierSubscription.unsubscribe()
},
})
return () => {
sourceSubscription.unsubscribe()
notifierSubscription.unsubscribe()
}
})
}
takeWhile(predicate: (value: T) => boolean): Stream<T> {
return new Stream((observer) => {
const subscription = this.subscribe({
next: (value) => {
if (predicate(value)) {
observer.next(value)
} else {
observer.complete?.()
subscription.unsubscribe()
}
},
error: (error) => observer.error?.(error),
complete: () => observer.complete?.(),
})
return subscription
})
}
skip(count: number): Stream<T> {
return new Stream((observer) => {
let skippedCount = 0
return this.subscribe({
next: (value) => {
if (skippedCount >= count) {
observer.next(value)
} else {
skippedCount++
}
},
error: (error) => observer.error?.(error),
complete: () => observer.complete?.(),
})
})
}
distinct(comparator?: (prev: T, curr: T) => boolean): Stream<T> {
return new Stream((observer) => {
let hasPreviousValue = false
let previousValue: T
const compareValues = comparator ?? ((prev, curr) => prev === curr)
return this.subscribe({
next: (value) => {
if (!hasPreviousValue || !compareValues(previousValue, value)) {
hasPreviousValue = true
previousValue = value
observer.next(value)
}
},
error: (error) => observer.error?.(error),
complete: () => observer.complete?.(),
})
})
}
switchMap<Result>(mapper: (value: T) => Stream<Result>): Stream<Result> {
return new Stream((observer) => {
let innerSubscription: Subscription | null = null
let outerCompleted = false
let innerCompleted = false
const checkComplete = () => {
if (outerCompleted && innerCompleted) {
observer.complete?.()
}
}
const outerSubscription = this.subscribe({
next: (value) => {
innerSubscription?.unsubscribe()
innerCompleted = false
const innerStream = mapper(value)
innerSubscription = innerStream.subscribe({
next: (innerValue) => observer.next(innerValue),
error: (error) => observer.error?.(error),
complete: () => {
innerCompleted = true
checkComplete()
},
})
},
error: (error) => observer.error?.(error),
complete: () => {
outerCompleted = true
checkComplete()
},
})
return () => {
outerSubscription.unsubscribe()
innerSubscription?.unsubscribe()
}
})
}
flatMap<Result>(mapper: (value: T) => Stream<Result>): Stream<Result> {
return new Stream((observer) => {
const innerSubscriptions: Subscription[] = []
let outerCompleted = false
let activeInnerCount = 0
const checkComplete = () => {
if (outerCompleted && activeInnerCount === 0) {
observer.complete?.()
}
}
const outerSubscription = this.subscribe({
next: (value) => {
activeInnerCount++
const innerStream = mapper(value)
const innerSubscription = innerStream.subscribe({
next: (innerValue) => observer.next(innerValue),
error: (error) => observer.error?.(error),
complete: () => {
activeInnerCount--
checkComplete()
},
})
innerSubscriptions.push(innerSubscription)
},
error: (error) => observer.error?.(error),
complete: () => {
outerCompleted = true
checkComplete()
},
})
return () => {
outerSubscription.unsubscribe()
innerSubscriptions.forEach((subscription) => subscription.unsubscribe())
}
})
}
debounce(delayMs: number): Stream<T> {
return new Stream((observer) => {
let timeoutId: ReturnType<typeof setTimeout> | null = null
const subscription = this.subscribe({
next: (value) => {
if (timeoutId) clearTimeout(timeoutId)
timeoutId = setTimeout(() => observer.next(value), delayMs)
},
error: (error) => observer.error?.(error),
complete: () => {
if (timeoutId) clearTimeout(timeoutId)
observer.complete?.()
},
})
return () => {
if (timeoutId) clearTimeout(timeoutId)
subscription.unsubscribe()
}
})
}
throttle(intervalMs: number): Stream<T> {
return new Stream((observer) => {
let lastEmitTime = 0
return this.subscribe({
next: (value) => {
const now = Date.now()
if (now - lastEmitTime >= intervalMs) {
lastEmitTime = now
observer.next(value)
}
},
error: (error) => observer.error?.(error),
complete: () => observer.complete?.(),
})
})
}
scan<Accumulator>(
reducer: (accumulator: Accumulator, value: T) => Accumulator,
initialValue: Accumulator,
): Stream<Accumulator> {
return new Stream((observer) => {
let accumulator = initialValue
return this.subscribe({
next: (value) => {
accumulator = reducer(accumulator, value)
observer.next(accumulator)
},
error: (error) => observer.error?.(error),
complete: () => observer.complete?.(),
})
})
}
startWith(...initialValues: T[]): Stream<T> {
return new Stream((observer) => {
for (const value of initialValues) {
observer.next(value)
}
return this.subscribe(observer)
})
}
tap(sideEffect: (value: T) => void): Stream<T> {
return new Stream((observer) => {
return this.subscribe({
next: (value) => {
sideEffect(value)
observer.next(value)
},
error: (error) => observer.error?.(error),
complete: () => observer.complete?.(),
})
})
}
catchError(errorHandler: (error: any) => Stream<T>): Stream<T> {
return new Stream((observer) => {
return this.subscribe({
next: (value) => observer.next(value),
error: (error) => {
const recoveryStream = errorHandler(error)
recoveryStream.subscribe(observer)
},
complete: () => observer.complete?.(),
})
})
}
finalize(cleanupFn: () => void): Stream<T> {
return new Stream((observer) => {
const subscription = this.subscribe({
next: (value) => observer.next(value),
error: (error) => {
cleanupFn()
observer.error?.(error)
},
complete: () => {
cleanupFn()
observer.complete?.()
},
})
return () => {
cleanupFn()
subscription.unsubscribe()
}
})
}
share(): Stream<T> {
const sharedObservers: Observer<T>[] = []
let sharedSubscription: Subscription | null = null
let hasCompleted = false
return new Stream((observer) => {
if (hasCompleted) {
observer.complete?.()
return
}
sharedObservers.push(observer)
if (sharedObservers.length === 1) {
sharedSubscription = this.subscribe({
next: (value) => sharedObservers.forEach((obs) => obs.next(value)),
error: (error) => sharedObservers.forEach((obs) => obs.error?.(error)),
complete: () => {
hasCompleted = true
sharedObservers.forEach((obs) => obs.complete?.())
},
})
}
return () => {
const index = sharedObservers.indexOf(observer)
if (index !== -1) sharedObservers.splice(index, 1)
if (sharedObservers.length === 0 && sharedSubscription) {
sharedSubscription.unsubscribe()
sharedSubscription = null
}
}
})
}
toPromise(): Promise<T | undefined> {
return new Promise((resolve, reject) => {
let lastValue: T | undefined
this.subscribe({
next: (value) => {
lastValue = value
},
error: reject,
complete: () => resolve(lastValue),
})
})
}
}
// Subject: Both Observable and Observer
class Subject<T> extends Stream<T> {
private observers: Set<Observer<T>> = new Set()
private hasCompleted = false
private errorValue: any = null
constructor() {
super((observer) => {
if (this.hasCompleted) {
observer.complete?.()
return
}
if (this.errorValue !== null) {
observer.error?.(this.errorValue)
return
}
this.observers.add(observer)
return () => this.observers.delete(observer)
})
}
next(value: T): void {
if (this.hasCompleted) return
this.observers.forEach((observer) => observer.next(value))
}
error(errorValue: any): void {
if (this.hasCompleted) return
this.errorValue = errorValue
this.observers.forEach((observer) => observer.error?.(errorValue))
}
complete(): void {
if (this.hasCompleted) return
this.hasCompleted = true
this.observers.forEach((observer) => observer.complete?.())
}
asStream(): Stream<T> {
return new Stream((observer) => this.subscribe(observer))
}
}
// BehaviorSubject: Subject with current value
class BehaviorSubject<T> extends Stream<T> {
private observers: Set<Observer<T>> = new Set()
private currentValue: T
private hasCompleted = false
constructor(initialValue: T) {
super((observer) => {
if (this.hasCompleted) {
observer.complete?.()
return
}
observer.next(this.currentValue)
this.observers.add(observer)
return () => this.observers.delete(observer)
})
this.currentValue = initialValue
}
get value(): T {
return this.currentValue
}
next(value: T): void {
if (this.hasCompleted) return
this.currentValue = value
this.observers.forEach((observer) => observer.next(value))
}
complete(): void {
if (this.hasCompleted) return
this.hasCompleted = true
this.observers.forEach((observer) => observer.complete?.())
}
}
// =============================================================================
// PART 3: STATE LAYER (Store & Domain)
// =============================================================================
class Store<Root> {
private listeners: Set<(state: Root) => void> = new Set()
state: Root
constructor(initialState: Root) {
this.state = initialState
}
subscribe(listener: (state: Root) => void): () => void {
this.listeners.add(listener)
return () => this.listeners.delete(listener)
}
commit(newState: Root): void {
if (this.state !== newState) {
this.state = newState
this.listeners.forEach((listener) => listener(this.state))
}
}
}
// Effect management storage
const effectMethodsStorage = new WeakMap<object, Map<string, () => Stream<void>>>()
const effectSubscriptionsStorage = new WeakMap<Domain<any, any>, Map<string, Subscription>>()
// Domain lifts Accessor methods and binds them to the Store
class Domain<Local, Root = any> {
readonly store: Store<Root>
readonly accessor: Accessor<Local, Root>
private subscriptionCount = 0
constructor(store: Store<Root>, accessor: Accessor<Local, Root>) {
this.store = store
this.accessor = accessor
}
get(): Result<Local> {
return this.accessor.get(this.store.state)
}
set(newValue: Local): void {
const result = this.accessor.set(newValue, this.store.state)
if (result.ok) this.store.commit(result.value)
}
update(updater: (currentValue: Local) => Local): void {
const currentResult = this.get()
if (currentResult.ok) this.set(updater(currentResult.value))
}
field<Key extends keyof Local>(key: Key): Domain<Local[Key], Root> {
return new Domain(this.store, this.accessor.field(key))
}
index(targetIndex: number): Domain<Local extends Array<infer Item> ? Item : never, Root> {
return new Domain(this.store, this.accessor.index(targetIndex))
}
match<Matched extends Local>(predicate: (local: Local) => local is Matched): Domain<Matched, Root> {
return new Domain(this.store, this.accessor.match(predicate))
}
find<Item = Local extends Array<infer ArrayItem> ? ArrayItem : never>(
predicate: (item: Item, index: number) => boolean,
): Domain<Item, Root> {
return new Domain(this.store, this.accessor.find(predicate))
}
as<DomainType extends Domain<Local, Root>>(
DomainCtor: new (store: Store<Root>, accessor: Accessor<Local, Root>) => DomainType,
): DomainType {
return new DomainCtor(this.store, this.accessor)
}
// Stream property: emits state changes, completes on accessor error
stream = new Stream<Local>((observer) => {
let lastValue: Local | undefined
let hasEmitted = false
// Emit current value immediately if ok
const currentResult = this.get()
if (currentResult.ok) {
lastValue = currentResult.value
hasEmitted = true
observer.next(currentResult.value)
} else {
// If currently in error state, complete immediately
observer.complete?.()
return
}
const unsubscribe = this.store.subscribe(() => {
const result = this.get()
if (result.ok) {
// Only emit if value changed
if (!hasEmitted || lastValue !== result.value) {
lastValue = result.value
hasEmitted = true
observer.next(result.value)
}
} else {
// Accessor returns error - this domain path is no longer valid
observer.complete?.()
}
})
return unsubscribe
})
// Subscribe with effect lifecycle management
subscribe(onNext: (state: Local) => void, onComplete?: () => void): () => void {
this.subscriptionCount++
// First subscription: start effects
if (this.subscriptionCount === 1) {
this.startEffects()
}
// Subscribe to stream
const subscription = this.stream.subscribe({
next: onNext,
complete: onComplete,
})
return () => {
subscription.unsubscribe()
this.subscriptionCount--
// Last unsubscribe: stop effects
if (this.subscriptionCount === 0) {
this.stopEffects()
}
}
}
private startEffects(): void {
const effectMethods = effectMethodsStorage.get(this)
if (!effectMethods) return
let effectSubscriptions = effectSubscriptionsStorage.get(this)
if (!effectSubscriptions) {
effectSubscriptions = new Map()
effectSubscriptionsStorage.set(this, effectSubscriptions)
}
effectMethods.forEach((effectFn, methodName) => {
const effectStream = effectFn.call(this)
const subscription = effectStream.subscribe({
next: () => {},
error: (error) => console.error(`Effect ${methodName} error:`, error),
})
effectSubscriptions!.set(methodName, subscription)
})
}
private stopEffects(): void {
const effectSubscriptions = effectSubscriptionsStorage.get(this)
if (!effectSubscriptions) return
effectSubscriptions.forEach((subscription) => subscription.unsubscribe())
effectSubscriptions.clear()
}
}
// =============================================================================
// PART 4: EFFECT DECORATOR
// =============================================================================
type EffectMethod = () => Stream<void>
// Effect decorator: marks methods as effects that run during domain subscription lifecycle
function effect() {
return function <This, Value extends EffectMethod>(
target: Value,
context: ClassMethodDecoratorContext<This, Value> & {
static: false
},
): Value {
const methodName = String(context.name)
context.addInitializer(function (this: This) {
let methods = effectMethodsStorage.get(this as object)
if (!methods) {
methods = new Map()
effectMethodsStorage.set(this as object, methods)
}
methods.set(methodName, (target as Function).bind(this) as EffectMethod)
})
return target
}
}
// =============================================================================
// PART 5: FRAMEWORK (Component )
// =============================================================================
type ComponentCtorStatic = Omit<typeof Component, 'prototype'>
interface ComponentCtor<Input, Out, Context = any> extends ComponentCtorStatic {
new (input: Input, context: Context): Component<Input, Out, Context>
}
abstract class Component<Input, Out, Context = any> {
protected readonly context: Context
protected readonly input: Input
constructor(input: Input, context: Context) {
this.context = context
this.input = input
}
static run<Input, Out, Context>(this: ComponentCtor<Input, Out, Context>, input: Input, context: Context): Out {
const Ctor = this
const instance = new Ctor(input, context)
try {
return instance.impl()
} catch (error) {
return instance.catch(error instanceof Error ? error : new Error(String(error), { cause: error }))
}
}
use<SubInput, SubOut>(Child: ComponentCtor<SubInput, SubOut, Context>, input: SubInput): SubOut {
return Child.run(input, this.context)
}
abstract impl(): Out
abstract catch(error: Error): Out
}
// =============================================================================
// PART 6: HTML VIEW
// =============================================================================
class EventRegistry {
private handlers: Map<string, Function> = new Map()
private counter = 0
register(fn: Function): string {
const id = `e${++this.counter}`
this.handlers.set(id, fn)
return id
}
trigger(id: string, payload?: any): void {
const fn = this.handlers.get(id)
if (fn) fn(payload)
else console.warn(`[Event] Unknown handler: ${id}`)
}
reset(): void {
this.handlers.clear()
this.counter = 0
}
}
const eventRegistry = new EventRegistry()
// 3. Setup Global Handlers
const globalHandlers = {
trigger: (id: string) => eventRegistry.trigger(id),
}
if (typeof globalThis !== 'undefined') (globalThis as any).globalHandlers = globalHandlers
abstract class HtmlView<Input, Context> extends Component<Input, string, Context> {
protected handler<E = any>(fn: (e: E) => void): string {
const id = eventRegistry.register(fn)
return `globalHandlers.trigger('${id}')`
}
catch(error: Error): string {
return `<div style="color:red; border:1px solid red; padding:8px;">
<strong>Component Error:</strong> ${error.message}
<pre style="font-size:10px">${JSON.stringify(this.input, null, 2)}</pre>
</div>`
}
}
// =============================================================================
// PART 7: USER LAND - TODO APP WITH STREAM & EFFECTS
// =============================================================================
// --- Models ---
type Todo = { id: number; text: string; done: boolean }
// --- Domains (Logic) with Effects ---
class TodoDomain extends Domain<Todo> {
toggle(): void {
this.update((todo) => ({ ...todo, done: !todo.done }))
}
remove(): void {
console.log('Remove not implemented (needs parent list access)')
}
}
class ListDomain extends Domain<Todo[]> {
add(text: string): void {
this.update((todos) => [...todos, { id: Date.now(), text, done: false }])
}
clear(): void {
this.update((todos) => todos.filter((todo) => !todo.done))
}
todo(id: number) {
return this.find((todo) => todo.id === id).as(TodoDomain)
}
}
class LogsDomain extends Domain<string[]> {
addLog(message: string): void {
this.update((logs) => [...logs, `[${new Date().toISOString()}] ${message}`])
}
}
type AppState = { user: string; todos: Todo[]; filter: 'all' | 'active'; logs: string[] }
class AppDomain extends Domain<AppState> {
todos$ = this.field('todos').as(ListDomain)
logs$ = this.field('logs').as(LogsDomain)
toggleFilter(): void {
this.update((state) => ({ ...state, filter: state.filter === 'all' ? 'active' : 'all' }))
}
// Effect: Log todo changes
@effect()
logTodoChanges(): Stream<void> {
return this.todos$.stream.skip(1).map((todoList) => {
const totalCount = todoList.length
const completedCount = todoList.filter((todo) => todo.done).length
this.logs$.addLog(`Todos updated: ${totalCount} total, ${completedCount} done`)
})
}
// Effect: Auto-save simulation
@effect()
autoSave(): Stream<void> {
return this.stream
.skip(1)
.debounce(1000)
.map((appState) => {
console.log('🔄 Auto-saving state...', appState)
this.logs$.addLog('State auto-saved')
})
}
}
// --- Views ---
type AppContext = { theme: string }
type TodoItemProps = { domain: TodoDomain }
class TodoItem extends HtmlView<TodoItemProps, AppContext> {
impl(): string {
const { domain } = this.input
const result = domain.get()
if (!result.ok) return `<!-- Error reading todo -->`
const todo = result.value
const onClick = this.handler(() => domain.toggle())
const style = todo.done ? 'text-decoration: line-through; color: #aaa;' : 'font-weight: bold;'
return `<li style="${style}" onclick="${onClick}">
${todo.text} ${todo.done ? '✅' : ''}
</li>`
}
}
type TodoListProps = { domain: ListDomain; filter: string }
class TodoList extends HtmlView<TodoListProps, AppContext> {
impl(): string {
const { domain, filter } = this.input
const result = domain.get()
if (!result.ok) return '<div>Loading Error</div>'
const todos = result.value
const items = todos
.filter((todo) => filter === 'all' || !todo.done)
.map((todo) => this.use(TodoItem, { domain: domain.todo(todo.id) }))
.join('')
const onAdd = this.handler(() => domain.add(`Task ${Math.floor(Math.random() * 100)}`))
const onClear = this.handler(() => domain.clear())
return `
<ul>${items}</ul>
<button onclick="${onAdd}">Add Task</button>
<button onclick="${onClear}">Clear Done</button>
`
}
}
type LogsPanelProps = { domain: LogsDomain }
class LogsPanel extends HtmlView<LogsPanelProps, AppContext> {
impl(): string {
const { domain } = this.input
const result = domain.get()
if (!result.ok) return '<div>Error loading logs</div>'
const logs = result.value
const logItems = logs
.slice(-5)
.map((logEntry) => `<li style="font-size: 12px; color: #666;">${logEntry}</li>`)
.join('')
return `
<div style="margin-top: 20px; padding: 10px; background: #f5f5f5; border-radius: 4px;">
<h3 style="margin: 0 0 10px;">Activity Log (last 5)</h3>
<ul style="margin: 0; padding-left: 20px;">${logItems || '<li>No logs yet</li>'}</ul>
</div>
`
}
}
class App extends HtmlView<AppDomain, AppContext> {
impl(): string {
const domain = this.input
const stateResult = domain.get()
if (!stateResult.ok) return '<div>App State Error</div>'
const state = stateResult.value
const onFilter = this.handler(() => domain.toggleFilter())
return `
<div id="app-container" style="font-family: sans-serif; padding: 20px;">
<h1>${state.user}'s Todos</h1>
<div style="margin-bottom: 10px;">
Status: <b>${state.filter}</b>
<button onclick="${onFilter}">Toggle Filter</button>
</div>
${this.use(TodoList, {
domain: domain.todos$,
filter: state.filter,
})}
${this.use(LogsPanel, {
domain: domain.logs$,
})}
</div>
`
}
}
// =============================================================================
// PART 8: STREAM DEMO
// =============================================================================
function streamDemo(): void {
console.log('\n=== Stream Demo ===\n')
// Basic Stream operations
const numbers = Stream.of(1, 2, 3, 4, 5)
console.log('Stream.of + map + filter:')
numbers
.map((num) => num * 2)
.filter((num) => num > 4)
.subscribe((num) => console.log(` Value: ${num}`))
// Combine streams
console.log('\nStream.combine:')
const numberSubject$ = new BehaviorSubject(1)
const letterSubject$ = new BehaviorSubject('A')
Stream.combine(numberSubject$, letterSubject$).subscribe(([num, letter]) =>
console.log(` Combined: ${num}, ${letter}`),
)
numberSubject$.next(2)
letterSubject$.next('B')
// switchMap example
console.log('\nswitchMap:')
const outerSubject$ = new Subject<number>()
outerSubject$
.switchMap((num) => Stream.of(num * 10, num * 100))
.subscribe((result) => console.log(` switchMap result: ${result}`))
outerSubject$.next(1)
outerSubject$.next(2)
// takeUntil example
console.log('\ntakeUntil:')
const stopSignal$ = new Subject<void>()
const countingStream$ = Stream.of(1, 2, 3, 4, 5).takeUntil(stopSignal$)
countingStream$.subscribe({
next: (count) => console.log(` Count: ${count}`),
complete: () => console.log(' Counting stopped'),
})
}
// =============================================================================
// PART 9: BOOTSTRAP (DOM & Node.js Compat)
// =============================================================================
function bootstrap() {
const context: AppContext = { theme: 'light' }
// 2. Setup Store with logs
const store = new Store<AppState>({
user: 'Mendler',
todos: [
{ id: 1, text: 'Learn Architecture', done: true },
{ id: 2, text: 'Implement DOM Render', done: false },
],
filter: 'all',
logs: [],
})
// 5. Construct Root Domain
const rootDomain = new Domain(store, Accessor.id<AppState>()).as(AppDomain)
// 6. Subscribe to domain (this starts effects!)
const unsubscribe = rootDomain.subscribe((state) => {
console.log('📊 State updated:', state.user, '- Todos:', state.todos.length)
})
// 7. Render Logic with DOM Mutation
const render = () => {
eventRegistry.reset()
const html = App.run(rootDomain, context)
if (typeof document !== 'undefined') {
let root = document.getElementById('root')
if (!root) {
root = document.createElement('div')
root.id = 'root'
document.body.appendChild(root)
}
root.innerHTML = html
} else {
console.log('\n--- [VIRTUAL DOM] ---')
console.log(html)
}
}
// 8. Subscribe store for re-rendering
store.subscribe(() => {
console.log('⚡ Update Detected, Re-rendering...')
render()
})
// 9. Initial Render
render()
return { store, rootDomain, unsubscribe }
}
// --- RUN ---
streamDemo()
bootstrap()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment