The universal reactive contract for Kyneta — a Moore machine identified by [CHANGEFEED].
A changefeed is a reactive value with a current state and a stream of future changes. You read .current to see what's there now; you .subscribe() to learn what changes next.
The protocol is expressed through a single well-known symbol: CHANGEFEED (Symbol.for("kyneta:changefeed")). Any object carrying this symbol participates in the reactive protocol — schema-interpreted refs, local state, peer lifecycle feeds, or anything else.
This package contains the contract only — zero dependencies, no schema, no interpreters, no paths. Schema-specific extensions (Op, TreeChangefeedProtocol, tree observation) live in @kyneta/schema, which depends on this package.
pnpm add @kyneta/changefeed// The universal base type for all changes — an open protocol identified by a string discriminant.
interface ChangeBase {
readonly type: string
}
// A batch of changes with optional provenance.
interface Changeset<C = ChangeBase> {
readonly changes: readonly C[]
readonly origin?: string
}
// The protocol object behind [CHANGEFEED] — a Moore machine coalgebra.
interface ChangefeedProtocol<S, C extends ChangeBase = ChangeBase> {
readonly current: S
subscribe(callback: (changeset: Changeset<C>) => void): () => void
}
// Developer-facing type: [CHANGEFEED] marker + direct .current and .subscribe().
interface Changefeed<S, C extends ChangeBase = ChangeBase> {
readonly [CHANGEFEED]: ChangefeedProtocol<S, C>
readonly current: S
subscribe(callback: (changeset: Changeset<C>) => void): () => void
}
// Marker interface — any object with [CHANGEFEED] participates in the protocol.
interface HasChangefeed<S = unknown, A extends ChangeBase = ChangeBase> {
readonly [CHANGEFEED]: ChangefeedProtocol<S, A>
}
// A Changefeed that is also callable — feed() returns feed.current.
type CallableChangefeed<S, C extends ChangeBase = ChangeBase> =
Changefeed<S, C> & (() => S)Create a standalone changefeed with push semantics. Returns a [feed, emit] tuple.
import { createChangefeed } from "@kyneta/changefeed"
let count = 0
const [feed, emit] = createChangefeed(() => count)
feed.current // 0
feed.subscribe(cs => console.log(cs.changes))
count = 1
emit({ changes: [{ type: "increment", amount: 1 }] })
// subscriber receives the changesetWrap a changefeed in a callable function-object. feed() returns feed.current.
import { createChangefeed, createCallable } from "@kyneta/changefeed"
let count = 0
const [source, emit] = createChangefeed(() => count)
const feed = createCallable(source)
feed() // 0 — callable
feed.current // 0 — getter
feed.subscribe // subscribe to changesProject any object with [CHANGEFEED] into a developer-facing Changefeed — lifting the hidden protocol surface to direct .current and .subscribe() accessibility.
import { changefeed } from "@kyneta/changefeed"
const feed = changefeed(doc.title)
feed.current // live value
feed.subscribe(cb) // subscribe to changesType guard — returns true if value has a [CHANGEFEED] property.
Creates a protocol object that never emits changes — useful for static data sources that still need to participate in the protocol.
@kyneta/schema depends on @kyneta/changefeed and extends the contract with tree-structured observation:
This package (@kyneta/changefeed) |
@kyneta/schema |
|---|---|
ChangeBase |
TextChange, MapChange, SequenceChange, ... |
Changeset<C> |
Op<C> (addressed delta with Path) |
ChangefeedProtocol<S, C> |
TreeChangefeedProtocol<S, C> (adds subscribeTree) |
Changefeed<S, C> |
HasTreeChangefeed<S, C> |
hasChangefeed() |
hasTreeChangefeed(), getOrCreateChangefeed() |
createChangefeed(), createCallable() |
expandMapOpsToLeaves() |
Consumers import the contract from @kyneta/changefeed directly — schema does not re-export contract symbols. The import path tells the truth about the dependency.
The Kyneta compiler detects [CHANGEFEED] structurally on types for automatic reactive subscription. HasChangefeed<S, C> is the type-level marker the compiler looks for. The Cast runtime uses hasChangefeed() at runtime to discover reactive values and subscribe to their change streams.
MIT