From 4cebe104699b12eabf5c1ad6ea0d46946fe205d0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 20 Sep 2026 14:47:25 +0000 Subject: [PATCH] chore(release): version packages --- .changeset/db.md | 31 ----- .changeset/i18n-overlay.md | 18 --- .changeset/observable.md | 11 -- .changeset/presence-as-a-value.md | 37 ----- .changeset/screens-feed.md | 5 - .changeset/server-runtime.md | 50 ------- .changeset/sync-one-writer-per-namespace.md | 6 - .changeset/sync.md | 16 --- .changeset/tagged-wire-and-batching.md | 49 ------- CHANGELOG.md | 12 ++ package.json | 2 +- packages/db/CHANGELOG.md | 38 ++++++ packages/db/package.json | 2 +- packages/i18n/CHANGELOG.md | 19 +++ packages/i18n/package.json | 2 +- packages/observable/CHANGELOG.md | 13 ++ packages/observable/package.json | 2 +- packages/server-runtime/CHANGELOG.md | 141 ++++++++++++++++++++ packages/server-runtime/package.json | 2 +- packages/sync/CHANGELOG.md | 102 ++++++++++++++ packages/sync/package.json | 2 +- 21 files changed, 331 insertions(+), 229 deletions(-) delete mode 100644 .changeset/db.md delete mode 100644 .changeset/i18n-overlay.md delete mode 100644 .changeset/observable.md delete mode 100644 .changeset/presence-as-a-value.md delete mode 100644 .changeset/screens-feed.md delete mode 100644 .changeset/server-runtime.md delete mode 100644 .changeset/sync-one-writer-per-namespace.md delete mode 100644 .changeset/sync.md delete mode 100644 .changeset/tagged-wire-and-batching.md create mode 100644 packages/db/CHANGELOG.md create mode 100644 packages/observable/CHANGELOG.md diff --git a/.changeset/db.md b/.changeset/db.md deleted file mode 100644 index d2f31c7..0000000 --- a/.changeset/db.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -"@bedrock-core/db": minor ---- - -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 -`#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. diff --git a/.changeset/i18n-overlay.md b/.changeset/i18n-overlay.md deleted file mode 100644 index 8ab80fe..0000000 --- a/.changeset/i18n-overlay.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -"@bedrock-core/i18n": minor ---- - -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. diff --git a/.changeset/observable.md b/.changeset/observable.md deleted file mode 100644 index 06c99a8..0000000 --- a/.changeset/observable.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"@bedrock-core/observable": minor ---- - -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. diff --git a/.changeset/presence-as-a-value.md b/.changeset/presence-as-a-value.md deleted file mode 100644 index affdad6..0000000 --- a/.changeset/presence-as-a-value.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -"@bedrock-core/sync": minor -"@bedrock-core/server-runtime": minor ---- - -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: - -```ts -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: - -```ts -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. diff --git a/.changeset/screens-feed.md b/.changeset/screens-feed.md deleted file mode 100644 index 14ebd2c..0000000 --- a/.changeset/screens-feed.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@bedrock-core/server-runtime": minor ---- - -**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`. diff --git a/.changeset/server-runtime.md b/.changeset/server-runtime.md deleted file mode 100644 index dee694d..0000000 --- a/.changeset/server-runtime.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -"@bedrock-core/server-runtime": minor ---- - -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(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()`. A peer reads `core.events.of(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`. diff --git a/.changeset/sync-one-writer-per-namespace.md b/.changeset/sync-one-writer-per-namespace.md deleted file mode 100644 index 89aa0ee..0000000 --- a/.changeset/sync-one-writer-per-namespace.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@bedrock-core/sync": minor -"@bedrock-core/server-runtime": minor ---- - -**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. diff --git a/.changeset/sync.md b/.changeset/sync.md deleted file mode 100644 index beaca06..0000000 --- a/.changeset/sync.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -"@bedrock-core/sync": minor ---- - -**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. diff --git a/.changeset/tagged-wire-and-batching.md b/.changeset/tagged-wire-and-batching.md deleted file mode 100644 index 9608577..0000000 --- a/.changeset/tagged-wire-and-batching.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -"@bedrock-core/sync": minor -"@bedrock-core/server-runtime": minor ---- - -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. diff --git a/CHANGELOG.md b/CHANGELOG.md index d1dcb4b..344663f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # @bedrock-core/server +## 0.2.0 + +### Patch Changes + +- Curates: + + - @bedrock-core/db@0.1.0 + - @bedrock-core/i18n@0.2.0 + - @bedrock-core/observable@0.1.0 + - @bedrock-core/server-runtime@0.2.0 + - @bedrock-core/sync@0.2.0 + ## 0.1.0 ### Minor Changes diff --git a/package.json b/package.json index 94e9884..d789bbf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bedrock-core/server", - "version": "0.1.0", + "version": "0.2.0", "description": "A framework for cross-addon compatible Minecraft Bedrock development", "keywords": [ "minecraft", diff --git a/packages/db/CHANGELOG.md b/packages/db/CHANGELOG.md new file mode 100644 index 0000000..edff3d1 --- /dev/null +++ b/packages/db/CHANGELOG.md @@ -0,0 +1,38 @@ +# @bedrock-core/db + +## 0.1.0 + +### Minor Changes + +- [#2](https://github.com/bedrock-core/server/pull/2) [`d75b88e`](https://github.com/bedrock-core/server/commit/d75b88efe1e5f9b5594590aab85c2c557e6a37f1) Thanks [@drav0011](https://github.com/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 + `#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`](https://github.com/bedrock-core/server/commit/d75b88efe1e5f9b5594590aab85c2c557e6a37f1)]: + - @bedrock-core/observable@0.1.0 diff --git a/packages/db/package.json b/packages/db/package.json index e2b082a..abdafb5 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,6 +1,6 @@ { "name": "@bedrock-core/db", - "version": "0.0.0", + "version": "0.1.0", "description": "Persisted documents for @bedrock-core: a resolver that finds where a target can hold dynamic properties, and typed collections over it", "keywords": [ "minecraft", diff --git a/packages/i18n/CHANGELOG.md b/packages/i18n/CHANGELOG.md index 58a5d9e..bf354ee 100644 --- a/packages/i18n/CHANGELOG.md +++ b/packages/i18n/CHANGELOG.md @@ -1,5 +1,24 @@ # @bedrock-core/i18n +## 0.2.0 + +### Minor Changes + +- [#2](https://github.com/bedrock-core/server/pull/2) [`d75b88e`](https://github.com/bedrock-core/server/commit/d75b88efe1e5f9b5594590aab85c2c557e6a37f1) Thanks [@drav0011](https://github.com/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. + ## 0.1.0 ### Minor Changes diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 091d50d..12581bf 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@bedrock-core/i18n", - "version": "0.1.0", + "version": "0.2.0", "description": "Localization for Minecraft Bedrock: typed keys, interpolation and plurals, resolved client-side per player", "keywords": [ "i18n", diff --git a/packages/observable/CHANGELOG.md b/packages/observable/CHANGELOG.md new file mode 100644 index 0000000..344b196 --- /dev/null +++ b/packages/observable/CHANGELOG.md @@ -0,0 +1,13 @@ +# @bedrock-core/observable + +## 0.1.0 + +### Minor Changes + +- [#2](https://github.com/bedrock-core/server/pull/2) [`d75b88e`](https://github.com/bedrock-core/server/commit/d75b88efe1e5f9b5594590aab85c2c557e6a37f1) Thanks [@drav0011](https://github.com/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. diff --git a/packages/observable/package.json b/packages/observable/package.json index 3459a8c..56e5cac 100644 --- a/packages/observable/package.json +++ b/packages/observable/package.json @@ -1,6 +1,6 @@ { "name": "@bedrock-core/observable", - "version": "0.0.0", + "version": "0.1.0", "description": "The reactive primitive for @bedrock-core: observable values with get / set / subscribe, computed values, effects and batching, plus a bridge to Minecraft's data-driven UI observables", "keywords": [ "minecraft", diff --git a/packages/server-runtime/CHANGELOG.md b/packages/server-runtime/CHANGELOG.md index aacc4f0..cf438c4 100644 --- a/packages/server-runtime/CHANGELOG.md +++ b/packages/server-runtime/CHANGELOG.md @@ -1,5 +1,146 @@ # @bedrock-core/server-runtime +## 0.2.0 + +### Minor Changes + +- [#2](https://github.com/bedrock-core/server/pull/2) [`d75b88e`](https://github.com/bedrock-core/server/commit/d75b88efe1e5f9b5594590aab85c2c557e6a37f1) Thanks [@drav0011](https://github.com/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: + + ```ts + 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: + + ```ts + 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](https://github.com/bedrock-core/server/pull/2) [`d75b88e`](https://github.com/bedrock-core/server/commit/d75b88efe1e5f9b5594590aab85c2c557e6a37f1) Thanks [@drav0011](https://github.com/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](https://github.com/bedrock-core/server/pull/2) [`d75b88e`](https://github.com/bedrock-core/server/commit/d75b88efe1e5f9b5594590aab85c2c557e6a37f1) Thanks [@drav0011](https://github.com/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(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()`. A peer reads `core.events.of(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](https://github.com/bedrock-core/server/pull/2) [`d75b88e`](https://github.com/bedrock-core/server/commit/d75b88efe1e5f9b5594590aab85c2c557e6a37f1) Thanks [@drav0011](https://github.com/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](https://github.com/bedrock-core/server/pull/2) [`d75b88e`](https://github.com/bedrock-core/server/commit/d75b88efe1e5f9b5594590aab85c2c557e6a37f1) Thanks [@drav0011](https://github.com/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`](https://github.com/bedrock-core/server/commit/d75b88efe1e5f9b5594590aab85c2c557e6a37f1), [`d75b88e`](https://github.com/bedrock-core/server/commit/d75b88efe1e5f9b5594590aab85c2c557e6a37f1), [`d75b88e`](https://github.com/bedrock-core/server/commit/d75b88efe1e5f9b5594590aab85c2c557e6a37f1), [`d75b88e`](https://github.com/bedrock-core/server/commit/d75b88efe1e5f9b5594590aab85c2c557e6a37f1), [`d75b88e`](https://github.com/bedrock-core/server/commit/d75b88efe1e5f9b5594590aab85c2c557e6a37f1), [`d75b88e`](https://github.com/bedrock-core/server/commit/d75b88efe1e5f9b5594590aab85c2c557e6a37f1), [`d75b88e`](https://github.com/bedrock-core/server/commit/d75b88efe1e5f9b5594590aab85c2c557e6a37f1)]: + - @bedrock-core/db@0.1.0 + - @bedrock-core/i18n@0.2.0 + - @bedrock-core/observable@0.1.0 + - @bedrock-core/sync@0.2.0 + ## 0.1.0 ### Minor Changes diff --git a/packages/server-runtime/package.json b/packages/server-runtime/package.json index a609457..92903ad 100644 --- a/packages/server-runtime/package.json +++ b/packages/server-runtime/package.json @@ -1,6 +1,6 @@ { "name": "@bedrock-core/server-runtime", - "version": "0.1.0", + "version": "0.2.0", "description": "The bedrock-core server runtime: addon registration and a cross-addon registry, built on @bedrock-core/sync", "keywords": [ "minecraft", diff --git a/packages/sync/CHANGELOG.md b/packages/sync/CHANGELOG.md index 03d6e86..8037239 100644 --- a/packages/sync/CHANGELOG.md +++ b/packages/sync/CHANGELOG.md @@ -1,5 +1,107 @@ # @bedrock-core/sync +## 0.2.0 + +### Minor Changes + +- [#2](https://github.com/bedrock-core/server/pull/2) [`d75b88e`](https://github.com/bedrock-core/server/commit/d75b88efe1e5f9b5594590aab85c2c557e6a37f1) Thanks [@drav0011](https://github.com/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: + + ```ts + 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: + + ```ts + 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](https://github.com/bedrock-core/server/pull/2) [`d75b88e`](https://github.com/bedrock-core/server/commit/d75b88efe1e5f9b5594590aab85c2c557e6a37f1) Thanks [@drav0011](https://github.com/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](https://github.com/bedrock-core/server/pull/2) [`d75b88e`](https://github.com/bedrock-core/server/commit/d75b88efe1e5f9b5594590aab85c2c557e6a37f1) Thanks [@drav0011](https://github.com/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](https://github.com/bedrock-core/server/pull/2) [`d75b88e`](https://github.com/bedrock-core/server/commit/d75b88efe1e5f9b5594590aab85c2c557e6a37f1) Thanks [@drav0011](https://github.com/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`](https://github.com/bedrock-core/server/commit/d75b88efe1e5f9b5594590aab85c2c557e6a37f1)]: + - @bedrock-core/observable@0.1.0 + ## 0.1.0 ### Minor Changes diff --git a/packages/sync/package.json b/packages/sync/package.json index 8e64c59..aa50abf 100644 --- a/packages/sync/package.json +++ b/packages/sync/package.json @@ -1,6 +1,6 @@ { "name": "@bedrock-core/sync", - "version": "0.1.0", + "version": "0.2.0", "description": "Cross-addon data sync for Minecraft Bedrock: a script-event message bus, peer discovery, RPC and replicated state for @bedrock-core addons", "keywords": [ "minecraft",