Repository navigation
v0.6.0
- @streamsy/core@0.6.0
- @streamsy/projection@0.6.0
- @streamsy/storage@0.6.0
- @streamsy/serve@0.6.0
Projection.follow returns a Stream (2ddfc6b)
Breaking change: Projection.follow now returns a Stream of committed cycles. Before this release, it returned a scoped Fiber and needed Scope. Progress now has a state type parameter, and Projection.run returns the same Progress.
A follower in the same process, such as a page, a TUI or a test, had no signal for each commit. It could only join the fiber for the end, or poll the checkpoint store. Now each element of the stream tells it what the follower committed.
Projection.follow(bound, options)givesStream<Progress<StateValue<S>, Inputs>, E | ProjectionFault, R>. The stream has noScoperequirement and forks no fiber. To run the stream is to be the writer, so run onefollowstream per key.Progress<S, Inputs>extendsSnapshot<S, Inputs>. It has thestateandoffsetsthat the last pass of the cycle left, andstatus,unitsanditems. Neitherrunnorfollowloads the checkpoint again after the cycle.statusiscaught-up,limit-reachedorsource-closed.- The stream emits after the first cycle, also with 0 units. After that, it emits after each cycle that commits a unit or reaches
limit, and after the last cycle when every input is closed. Then it ends. A wake that finds no input emits nothing. - The stream is pull based. The next cycle runs only when the consumer pulls, so a slow consumer gets one element for all the input that arrived meanwhile. Use
Stream.buffer({ capacity: 1, strategy: "sliding" })to decouple a slow consumer. - An invalid
limitorrepairIntervalMsfails the stream at its first pull. Before, theyield*offollowfailed. Projection.runnow gives thestatethat the last pass of its cycle left.Projection.passkeeps its own result type,PassResult, with theprogressstatus and no state.- One
Projection.followspan covers the whole stream. EachProjection.passandProjection.hintspan of the follower is a child of it. - The wake hints, the retry of
storage-failureand the repair interval do not change.
To migrate a caller:
Effect.gen(function* () {
// Run to the end, as Fiber.join did.
yield* Stream.runDrain(Projection.follow(bound));
// Run in the background of a scope, as follow did.
yield* Projection.follow(bound).pipe(Stream.runDrain, Effect.forkScoped);
// Wait for an offset without a second writer.
yield* Projection.follow(bound).pipe(
Stream.takeUntil(({ offsets }) => offsets.input === offset),
Stream.runLast,
);
});Wake streams, attached projections and object links (59d1920)
Streamsy.wake<Self>()(name, template, { params })is a State preset withwakeCollectionsand the normal State service.WakeRowholds{ stream, offset, closed? }, keyed by source id.DurableState.materializedandmaterializefold State events into collection maps.Projection.wake({ id, params, input, output })usesmapAccumBatchandCommit.producerto publish source tails, closes and removals. The producer creates a missing output on its own owner. An output on another owner must exist first: its owner creates it at start with the State service'screate. HTTP access remains mount policy.- Behaviour change from 0.5.0: a close without new items gives one unit with
slice.closed. A gone input with a checkpoint gives one final unit withslice.gone, then forgets the binding by default. The lastProjection.runresult orProjection.followelement holds the state and offsets that this final unit committed, andProjection.readthen gives the initial state. Custom Commits needremovefor this outcome. Live history gaps remain faults. A removal between a pin and its settle still fails withrange-unreproducible, and the pin stays; retain history until pending units settle.Projection.followalready ended on close in 0.5.0; use attachment or another run for a later removal. - Upgrade from 0.5.0: the checkpoint now records which inputs are closed. A 0.5.0 checkpoint has no such record, so the first run after the upgrade gives one extra unit with no items and
slice.closedfor each input that was already closed. A handler that expects items in every unit sees this unit once. - Breaking change:
Storage.committedis a scopedPubSubsubscription to the ids of each later local commit. The subscription exists when the effect returns. Custom storage adapters must add it, or they do not compile. Projection.attach(projection, { limit, retry })runs affected bindings on Bun or Node.ProjectionWakeup.attach(projection, options)is acquired in a Cloudflare object scope. Both share one runner and accept a retrySchedule. The default schedule has no end. A custom schedule with an end stops the attachment for all bindings of that projection when the schedule ends, until the process or object restarts. Pending bindings live in memory; a restart, such as a crash, a Cloudflare eviction or a deploy, can delay work until the next source commit. Remote process commits do not appear.Streamsy.followWakeandwakeActionscompare rows with a saved cursor map and returnWakeItemactions:Dirty,Removed,ResetandSnapshot.ObjectStreams.layer(group, { objects, placement, prefix })routes stream services through object stubs.ObjectFetchreturns an Effect with typed HTTP client errors, preserving caller context and cancellation. Alchemy'sobjectFetchadapts its binding.ObjectLink.make({ group, prefix, placement, access })declares the internal owner routes and caller client together. Registerlink.routesand hostlinks.link.client(objects)takes a namespace or anObjectFetchand returns oneLinkClientshape for both hosts.StreamsyObjecttakeslinks: (env) => [...]; AlchemyobjectHandlerstakeslinks: [link.client(objectFetch(binding))]. Local storage cannot also provide a linked declaration.- Behaviour change from 0.5.0: public Cloudflare and Alchemy routers return 404 for paths whose first segment starts with
__. Internal links require a/__prefix. Apps using root stream ids starting with__must rename them or use another prefix.