Skip to content

chore(release): version packages - #3

Merged
drav0011 merged 1 commit into
mainfrom
changeset-release/main
Sep 20, 2026
Merged

drav0011 merged 1 commit into
mainfrom
changeset-release/main

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and publish to npm yourself or setup this action to publish automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.

Releases

@bedrock-core/db@0.1.0

Minor Changes

  • #2 d75b88e Thanks @drav0011! - Add @bedrock-core/db: typed documents persisted on whatever dynamic properties a target can
    hold.

    The host resolver probes a target for where its documents can live — its own dynamic
    properties through the engine's six-method or component ABI, or a world property keyed by its
    identity when it holds nothing — behind one adapter over both ABIs, with a capability record per
    host, refusals by name for an ItemStack and a stackable slot, and a per-type cache that never
    caches a throw.

    Collections key documents by target on the host the resolver finds. One JSON envelope per
    document carries its version; migrations run lazily per document; defaults fill deep on read and
    a normalize runs on every write; patch merges plain objects recursively and replaces arrays and
    scalars, with undefined deleting a key. A document that cannot be read is quarantined under
    <key>#bad rather than deleted. A budget check runs before the engine, with chunking on the direct
    ABI, accept and require are checked at compile time against what the target type can do, and a
    validity gate re-resolves the target on every operation.

    The chunked index has a resumable all() that heals a replaced block, a blockCleanup custom
    component for onBreak, and coalesce write-behind that parks on entityLoad and flushes on
    playerLeave — wired to the engine only when a coalescing collection exists.

    A collection is local. Nothing here is reachable from another realm: what crosses is what the
    owning addon puts on one of the runtime's channels.

    Tuned for the engine throughout: hosts, resolutions, document stores and handles are classes with
    prototype methods, a handle resolves its target once per tick, the resolver trusts a cached type
    decision, index chunks are written behind, and a single value skips the batch write.

Patch Changes

  • Updated dependencies [d75b88e]:
    • @bedrock-core/observable@0.1.0

@bedrock-core/i18n@0.2.0

Minor Changes

  • #2 d75b88e Thanks @drav0011! - A library's strings can be overridden by the world it runs in.

    A library that draws UI ships its own bundle, keyed under a namespace it shares with the rest of
    its family. A running realm has more than that: every addon present has announced a bundle, and
    one of them may carry the very same key — deliberately, to rename what the library calls something
    ("Addons" becomes "Mods"), or simply because it ships a locale the library does not.

    overlay(bound, published, bundle) is that precedence, as verbs. t() prefers the published value
    wherever it carries the key, so an override and an unshipped locale reach the strings a script
    renders rather than only the keys a client paints. resolve() and display() become the world's,
    so a key from any addon's bundle resolves — which is what a screen showing another addon's display
    fields needs.

    A realm with no published bundles gets the bound instance back untouched and allocates nothing.

@bedrock-core/observable@0.1.0

Minor Changes

  • #2 d75b88e Thanks @drav0011! - Add @bedrock-core/observable, the reactive primitive the stack notifies through: observable
    with get / set / subscribe, computed, effect and batch, delivering synchronously with
    listeners isolated from each other. last(signal) turns anything with subscribe — a Minecraft event
    signal included — into an observable of its most recent payload, undefined until the first one
    arrives, released with dispose(). The /minecraft entry bridges one of ours to a data-driven UI
    observable with toNative, keeping the native in step for a form's lifetime and writing back only
    when the control is client-writable.

@bedrock-core/server-runtime@0.2.0

Minor Changes

  • #2 d75b88e Thanks @drav0011! - Who is present is a value, not a stream: discovery, the registry and the host election are
    observables.

    Breaking. discovery.peers and discovery.incompatiblePeers are ReadonlyObservable lists
    rather than array getters, so they read .get(), watch with .subscribe(), and compose with
    computed() like a config leaf or a shared value:

    sync.discovery.peers.subscribe(peers => redraw(peers));
    core.registry.addons.subscribe(addons => redraw(addons));
    
    const hostBanner = computed(() => `hosted by ${core.host.id.get()}`, [core.host.id]);

    The lists republish only when the world actually changes. A heartbeat that repeats what a peer
    already said refreshes its liveness and notifies nobody, which is what lets a listener sit on the
    list without waking every five seconds per peer. lastSeen therefore left PeerInfo and
    IncompatiblePeer — a tick that moves on every heartbeat cannot live inside an observable value —
    and is asked for by id instead:

    sync.discovery.lastSeen('drav0011_economy');   // tick, or undefined

    onPeerUp / onPeerDown / onRegister / onUnregister stay: an arrival is a delta, and a
    caller that wants the one peer that changed still wants an event. A TTL sweep publishes the list
    once for the whole sweep, before any listener runs, so a handler never sees a half-swept world.

    Registry keeps no directory of its own — all(), get() and has() read addons, so a cached
    answer can no longer disagree with the live one — and HostElection is computed over that list,
    which retires its start(). core.host.hostId is now core.host.id, an observable; HostListener
    takes a previousHostId: string, since a derived value always has one.

  • #2 d75b88e Thanks @drav0011! - Breaking. Guides left the runtime: the guide and guideReference options of register(), core.guides, core.guides.manifest, GuidesRegistry, GuideReference and GuideManifest are removed. A guide is a set of compiled screens from @bedrock-core/guides, reached by navigating to its key, and the screens an addon publishes are read through screens(core) from @bedrock-core/navigation.

  • #2 d75b88e Thanks @drav0011! - Everything an addon owns is declared in register(), and everything that crosses a realm goes over
    one of three channels.

    Breaking. register() installs declarations, not plain objects: shared: registerShared(keys),
    events: registerEvents(tree), and one field per app, such as config: registerConfig(definition)
    from @bedrock-core/config. Config and guides are no longer part of the runtime. They are the
    @bedrock-core/config and @bedrock-core/guides apps, which keep their state in a RuntimeSlots
    slot the runtime fills and hands back (core.fill / core.slot); core.config, core.guides and
    core.pages are gone.

    core.shared — a flat shared shape comes back as a typed tree, one observable per key with
    get / set / subscribe and the usual (next, prev) listener. Peers read
    core.shared.of<Def>(ns), materialized from the key names the owner announces under
    core-shared/shape, and never write: a peer's tree has no set, in the type and at runtime. The
    backend subscription is attached with the first listener and released with the last. The mirror
    stores nothing — a value that must survive a restart is a db document the owner maps onto a key.

    core.events — an events shape comes back as a typed tree with emit and subscribe, the
    payload type declared by event<T>(). A peer reads core.events.of<Def>(ns), which has
    subscribe alone and answers before the owning addon exists, so a listener attached early hears
    the first event announced. Nothing is replayed.

    core.rpc — a question the owner answers. authorize(target, actorId, operation) is the one
    rule such a handler applies: an operator reaches anything, anyone else only their own entity, and a
    request with no acting player is an addon acting for itself.

    core.db is this addon's @bedrock-core/db, keyed under its namespace, with the declaration
    API (schema, the acceptors and combinators, the errors) re-exported so a collection is declared
    from the runtime import alone. It is local: a peer reaches a document only through a method the
    owner wrote.

    Every cross-addon feed is an Announcement — one value under a core- key in the owner's
    namespace, with provide / own / of(ns) / namespaces / subscribe and a guard on read.
    core.translations is one over the bundle, its verbs at i18n(ns); core.features.flags
    announces every flag as one record under core-feature/flags; the shared shape sits at
    core.shared.shape. provideManifest, provideReference, referenceOf, bundleOf,
    addonsWithGuides and has are gone with it.

    The package exports what an addon writes against. Registries are exported as types — the
    runtime constructs them — and the schema, document and wire helpers (flattenSchema,
    defaultsOf, normalizeAgainst, coerce, CONFIG_COLLECTIONS, configMethod,
    SHARED_SHAPE_KEY, validateManifest, addonNamespace, compareVersions, PROTOCOL_MIN /
    PROTOCOL_MAX) are no longer exported.

    core.state and ScopedState are removed. A shared key covers every call they had; the raw
    namespace, framework keys included, stays reachable at core.node.state.

  • #2 d75b88e Thanks @drav0011! - Breaking. A node writes one namespace, the one named by its id. ownedNamespaces and strictOwnership are removed from createSync / SyncNode, and StateOptions from the exports: every mirror only ever applied a namespace's writes from the node whose id it is, so the options could not widen that. A write to another node's namespace is dropped by every mirror and counted in droppedForeign, and a node answers snapshot requests for its own namespace.

  • #2 d75b88e Thanks @drav0011! - Rework the script-event wire format, and negotiate the protocol per peer instead of demanding a
    match.

    A message now opens with a tag saying which shape follows: one envelope, a batch of envelopes, or
    one frame of a chunked envelope. An envelope that fits in a message is sent whole instead of nested
    inside a frame's p field, so it is no longer JSON-escaped to sit inside a JSON string — a small
    message loses about a third of its length, and a round trip costs roughly half the CPU.

    The outbound queue packs consecutive envelopes into one message up to the size cap. The engine
    bounds script events per tick by count rather than by size, so a node that sends a burst in a single
    tick — a run of State.set calls, a snapshot broadcast, an RPC fan-out — now spends a few of its
    per-tick slots instead of one per envelope. Each addon has its own queue, so this packs one node's
    own traffic and never several nodes' together.

    Framing charges each character what JSON actually spends escaping it, rather than reserving two
    characters for every one. Real payloads fill a frame instead of half of it: a 16KB envelope splits
    into 10 frames where it previously took 18.

    PROTOCOL_VERSION is replaced by PROTOCOL_MIN and PROTOCOL_MAX. A node advertises the
    range it speaks in every announce and talks to each peer at the newest version both know, so a
    world may hold addons built against different releases without partitioning. Gating on one exact
    version would have made this bump — and every later one — a silent split: two meshes on a single
    channel, each listing only its own half, each electing its own UI host, each timing out every RPC
    to the other.

    Consequently:

    • A protocol-1 message is a bare frame with no tag, and is read as one. Announces and whois are
      pinned to PROTOCOL_MIN so the message that establishes a version never assumes one.
    • Broadcasts go out at the lowest version any live peer can read, and packing stops while a peer
      that predates the batch tag is present. Both recover on their own once that peer expires.
    • PeerInfo gains protocol and caps. Capabilities are advertised per node and narrowed by the
      negotiated version, so a later addition can appear or degrade without a version bump.
    • A node whose range does not overlap this build's is reported through
      Discovery.onIncompatible / Registry.onIncompatible and listed by Registry.incompatible(),
      with a warning naming both ranges. It is named rather than silently absent.
    • negotiateProtocol and capsFor are exported for anyone writing an interoperating
      implementation.

    The support window is two versions wide. Raising PROTOCOL_MIN drops everything below it and is a
    breaking change.

    MAX_MESSAGE, the default per-message character budget, is now exported alongside the existing
    BusOptions.maxMessage override.

Patch Changes

@bedrock-core/sync@0.2.0

Minor Changes

  • #2 d75b88e Thanks @drav0011! - Who is present is a value, not a stream: discovery, the registry and the host election are
    observables.

    Breaking. discovery.peers and discovery.incompatiblePeers are ReadonlyObservable lists
    rather than array getters, so they read .get(), watch with .subscribe(), and compose with
    computed() like a config leaf or a shared value:

    sync.discovery.peers.subscribe(peers => redraw(peers));
    core.registry.addons.subscribe(addons => redraw(addons));
    
    const hostBanner = computed(() => `hosted by ${core.host.id.get()}`, [core.host.id]);

    The lists republish only when the world actually changes. A heartbeat that repeats what a peer
    already said refreshes its liveness and notifies nobody, which is what lets a listener sit on the
    list without waking every five seconds per peer. lastSeen therefore left PeerInfo and
    IncompatiblePeer — a tick that moves on every heartbeat cannot live inside an observable value —
    and is asked for by id instead:

    sync.discovery.lastSeen('drav0011_economy');   // tick, or undefined

    onPeerUp / onPeerDown / onRegister / onUnregister stay: an arrival is a delta, and a
    caller that wants the one peer that changed still wants an event. A TTL sweep publishes the list
    once for the whole sweep, before any listener runs, so a handler never sees a half-swept world.

    Registry keeps no directory of its own — all(), get() and has() read addons, so a cached
    answer can no longer disagree with the live one — and HostElection is computed over that list,
    which retires its start(). core.host.hostId is now core.host.id, an observable; HostListener
    takes a previousHostId: string, since a derived value always has one.

  • #2 d75b88e Thanks @drav0011! - Breaking. A node writes one namespace, the one named by its id. ownedNamespaces and strictOwnership are removed from createSync / SyncNode, and StateOptions from the exports: every mirror only ever applied a namespace's writes from the node whose id it is, so the options could not widen that. A write to another node's namespace is dropped by every mirror and counted in droppedForeign, and a node answers snapshot requests for its own namespace.

  • #2 d75b88e Thanks @drav0011! - Owner-only state. A mirror applies an entry for a namespace only from the namespace's owner —
    the sending node for a delta, the recorded writer for a snapshot entry, so a snapshot relayed by a
    third party still names the original writer. Anything else is dropped and counted in
    droppedForeign. The rule holds for a node's own writes too, so a foreign write is visible locally
    exactly when it is visible everywhere, which is never.

    Events, the subsystem for a happening. node.events.emit(name, payload) broadcasts one
    message; on(namespace, name, handler) subscribes to one sender's name. The namespace a handler
    matches is the envelope's src, read from the transport rather than the payload, so a message
    cannot claim to come from a node that did not send it. The sender dispatches to its own handlers
    first, synchronously, before the message leaves; a handler that throws is caught and the others
    still run. Nothing is stored and nothing is replayed.

  • #2 d75b88e Thanks @drav0011! - Rework the script-event wire format, and negotiate the protocol per peer instead of demanding a
    match.

    A message now opens with a tag saying which shape follows: one envelope, a batch of envelopes, or
    one frame of a chunked envelope. An envelope that fits in a message is sent whole instead of nested
    inside a frame's p field, so it is no longer JSON-escaped to sit inside a JSON string — a small
    message loses about a third of its length, and a round trip costs roughly half the CPU.

    The outbound queue packs consecutive envelopes into one message up to the size cap. The engine
    bounds script events per tick by count rather than by size, so a node that sends a burst in a single
    tick — a run of State.set calls, a snapshot broadcast, an RPC fan-out — now spends a few of its
    per-tick slots instead of one per envelope. Each addon has its own queue, so this packs one node's
    own traffic and never several nodes' together.

    Framing charges each character what JSON actually spends escaping it, rather than reserving two
    characters for every one. Real payloads fill a frame instead of half of it: a 16KB envelope splits
    into 10 frames where it previously took 18.

    PROTOCOL_VERSION is replaced by PROTOCOL_MIN and PROTOCOL_MAX. A node advertises the
    range it speaks in every announce and talks to each peer at the newest version both know, so a
    world may hold addons built against different releases without partitioning. Gating on one exact
    version would have made this bump — and every later one — a silent split: two meshes on a single
    channel, each listing only its own half, each electing its own UI host, each timing out every RPC
    to the other.

    Consequently:

    • A protocol-1 message is a bare frame with no tag, and is read as one. Announces and whois are
      pinned to PROTOCOL_MIN so the message that establishes a version never assumes one.
    • Broadcasts go out at the lowest version any live peer can read, and packing stops while a peer
      that predates the batch tag is present. Both recover on their own once that peer expires.
    • PeerInfo gains protocol and caps. Capabilities are advertised per node and narrowed by the
      negotiated version, so a later addition can appear or degrade without a version bump.
    • A node whose range does not overlap this build's is reported through
      Discovery.onIncompatible / Registry.onIncompatible and listed by Registry.incompatible(),
      with a warning naming both ranges. It is named rather than silently absent.
    • negotiateProtocol and capsFor are exported for anyone writing an interoperating
      implementation.

    The support window is two versions wide. Raising PROTOCOL_MIN drops everything below it and is a
    breaking change.

    MAX_MESSAGE, the default per-message character budget, is now exported alongside the existing
    BusOptions.maxMessage override.

Patch Changes

  • Updated dependencies [d75b88e]:
    • @bedrock-core/observable@0.1.0

@drav0011
drav0011 merged commit 5074b3d into main Sep 20, 2026
@drav0011
drav0011 deleted the changeset-release/main branch September 20, 2026 14:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant