Skip to main content

Collections

A collection names a document type and which targets hold one. for(target) hands back a document handle: an observable over that one target's bytes, with get, set, patch, delete and subscribe.

Import​

import { createEngineDb } from '@bedrock-core/db/minecraft';
import type { Db, Collection, CollectionOptions, Document, IndexedDocument, Where } from '@bedrock-core/db';

From an addon, core.db is the Db.

db.collection​

db.collection<T extends object, Target = StorableTarget, R extends Requirements = Requirements>(
name: string,
options: CollectionOptions<T, Target, R>,
): Collection<T, Target>
OptionTypeDefaultDescription
schema *Schema<T>—The document type — see schema
acceptAcceptor<Target>any storable targetWhich targets for() takes — see targets
requireRequirements{}Capabilities the host must have: own, enumerable, readableWhenUnloaded
coalescebooleanfalseWrite-behind: one property write per dirty document per tick

The document type comes from schema<T>(); collection takes no type argument.

const balances = db.collection('balances', {
schema: schema<{ gold: number }>({ defaults: { gold: 0 } }),
accept: players(),
});

coalesce​

With coalesce: true a write marks the document dirty and one property write per dirty document happens at the end of the tick. Offered on the world and on entities only — a block or slot document could die with its target before the flush, so those targets are refused with a reason. An entity that unloads before the flush has its document parked and written on entityLoad; a leaving player is flushed in beforeEvents.playerLeave. db.flush(target?) writes now.

Collection​

MemberSignatureDescription
namestringThe collection's name
for(target: Target) => Document<T>The handle for one target's document
where(target: Target) => WhereWhether this collection would store on target, and with which capabilities, without touching it
forget(target: Target) => voidDrop what the collection remembers about a target — its cached document and subscribers
all() => IterableIterator<IndexedDocument<T>>Every target known to hold a document, lazily, from the index
at(kind: TargetKind, identity: string) => IndexedDocument<T> | undefinedThe document for one indexed identity, without holding the target
sizenumberHow many documents the index knows of
type Where
= { ok: true; kind: TargetKind; caps: Capabilities }
| { ok: false; kind: TargetKind; reason: string };

all​

all() iterates the collection's index, kept in chunked world properties and updated on a document's first write, on delete, and by blockCleanup when a block breaks; a block found replaced by another type is removed from it. The iterator is resumable, so a sweep can take a few per tick:

for (const doc of elevators.all()) {
if (!doc.available) { continue; } // unloaded chunk, removed entity

doc.patch({ configured: false });
}

A target that cannot be reached right now is still yielded, with available false — or readable, when its document lives on the world. Slots are never indexed.

at​

What a remote caller has, since a Block or an Entity cannot travel over the wire:

const doc = elevators.at('block', 'overworld:10,64,-3:papi:elevator');

Document​

MemberSignatureDescription
availablebooleanWhether the target can be reached and the collection accepts it right now
reasonstring | undefinedWhy available is false
get() => T | undefinedThe document with its defaults filled, undefined when there is none or the target cannot be reached
set(doc: T) => voidStore doc as it is, after the schema's normalize
patch(changes: DeepPartial<T>) => voidMerge into the stored document, deep
delete() => voidRemove the document; never throws
subscribe(listener: (doc: T | undefined, prev: T | undefined) => void) => UnsubscribeLocal change events for this document

An IndexedDocument — what all() and at() yield — adds kind: TargetKind and identity: string.

patch​

A nested object merges key by key, an array replaces the one there, and undefined deletes a key — which puts it back to its default. What is stored is exactly what was written, after normalize.

balances.for(player).patch({ gold: 10 }); // one key
settings.for(world).patch({ event: { active: false } }); // deep
settings.for(world).patch({ event: undefined }); // back to the default

subscribe​

Fires with the new document, or undefined on delete, and the one before it. A listener attached before the document was first read hears it load, which is what makes this correct at boot as well as on every later change:

settings.for(world).subscribe(doc => shared.event.set(doc?.event ?? defaults.event));

get and subscribe together are a ReadonlyObservable, so a document can be computed over or handed to a UI hook directly.

Db​

MemberSignatureDescription
collectionsee aboveDeclare a collection
find(name: string) => Collection<object, unknown> | undefinedA collection by name, untyped
flush(target?: unknown) => voidWrite every coalesced document now, or one target's
blockRemoved(dimensionId, location, typeId) => voidTell every collection a block is gone; what blockCleanup calls
resolverResolverThe host resolver

createEngineDb(namespace, log?) from @bedrock-core/db/minecraft builds one over the world, with the engine's classifier and locator and the lifecycle hooks for entityLoad and playerLeave wired only when a coalescing collection exists. The namespace prefixes every property key, so two packs never meet.

Notes​

  • Every operation re-resolves the target, at most once per tick. A handle from for() keeps an identity, not the object: get() is undefined and set() throws DbTargetError once an entity is removed, a slot empties, or a block's chunk unloads — except a proxied document (dimension, vanilla block), which lives on the world and stays reachable.
  • Hold a handle when hammering one target. for() is cheap, but the handle resolves its target once per tick and caches the parsed document.
  • A document that cannot be read — bytes that are not the envelope, a migration that throws — is quarantined under <key>#bad, logged, and reads as undefined. Nothing is deleted.
  • Budget. A block entity holds about 950 bytes per pack; a document past it throws DbBudgetError naming the collection before the engine sees the write. On the world, entities and slots a document past 32 767 characters is chunked into <key>#0..n transparently.