Skip to content

feat: structured concurrency for streaming SSR (Zag integration) #42

@justrach

Description

@justrach

Context

Zag is a Zig fork exploring structured concurrency with fibers and work-stealing — no function coloring, no `async fn`.

How This Applies to merjs

Current streaming SSR uses raw threads for parallel fetching:

const results = mer.fetchAll(alloc, &.{ req1, req2, req3 });
// Spawns 3 threads, joins all, copies results

With Zag's structured concurrency, this becomes:

scope |s| {
    const weather = s.spawn(mer.fetch, .{ alloc, weather_req });
    const users = s.spawn(mer.fetch, .{ alloc, users_req });
    
    stream.placeholder("weather", skeleton);
    stream.placeholder("users", skeleton);
    
    // Resolve as each completes — no join barrier
    stream.resolve("weather", renderWeather(weather.await()));
    stream.resolve("users", renderUsers(users.await()));
}
// All tasks guaranteed done when scope exits

Benefits

  • No thread-per-fetch overhead — fibers are ~4KB vs ~8MB thread stacks
  • Scope-based lifetime — tasks can't outlive the request
  • Out-of-order resolution — resolve the first result that completes, not in array order
  • Cancellation — if the client disconnects, cancel all pending fetches
  • No allocator hacks — fibers share the request arena safely (cooperative, not preemptive)

This is the path to true Suspense

The current implementation resolves all placeholders in order after fetchAll returns. With Zag scopes, each placeholder could resolve independently as its data arrives — real out-of-order streaming, like React Suspense.

Status

Blocked on Zag's ZEP-0002 runtime landing. Track at justrach/zag#1.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions