Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Releases: colyseus/schema

5.0.6

31 May 04:17
@github-actions github-actions

Choose a tag to compare

5.0.6 Pre-release
Pre-release

Added

  • Data<T> type helper — the plain DATA shape of a Schema instance type: its
    synchronized fields with all Schema machinery stripped (assign, clone,
    toJSON, change-tracking state, internal symbol keys, ...), so a plain object
    literal satisfies it while field types (including narrowed primitives like
    t.int8<-1 | 0 | 1>()) are preserved.

    function applyInput(state: Player, cmd: Data<MoveInput>) { ... }
    applyInput(player, { moveX: 1, jump: false, dt }); // plain literal — OK

    For typing code that works on schema-shaped plain objects rather than
    decoded instances: deterministic simulation steps, synthesized / buffered
    input commands, plain DTOs. Unlike ToJSON<T> (a recursive serialization
    shape that retains non-method Schema members), Data<T> is a flat
    structural projection — Omit<T, keyof Schema> — that plain literals satisfy.

Loading

5.0.5

31 May 03:29
@github-actions github-actions

Choose a tag to compare

5.0.5 Pre-release
Pre-release

Added

  • Generic type narrowing on the primitive field factories. Pass an explicit
    type argument to t.int8() / t.string() / etc. to refine the inferred
    field type, while the wire encoding is unchanged:

    const MoveInput = schema({
     moveX: t.int8<-1 | 0 | 1>(), // typed -1 | 0 | 1, still a 1-byte int8
     team: t.string<"red" | "blue">(), // typed "red" | "blue", still a string
    });

    Each t.<primitive>() now has two call signatures: the bare call returns the
    natural type for the codec (t.int8()number), and an explicit type
    argument returns FieldBuilder<T>. This is an overload pair rather than a
    defaulted generic (<T extends TBase = TBase>()): a defaulted free type
    parameter gets captured as any during schema()'s self-referential field
    inference (and undefined extends any then flips every field optional), so
    the bare form must stay a concrete FieldBuilder<TBase>.

    The refinement is a type-level assertion only — the wire still carries the
    codec's full range and the decoder writes whatever bytes arrive. Sound for
    server-authored state; for input schemas (untrusted client) keep validating /
    clamping on receipt.

Loading

5.0.4

31 May 01:47
@github-actions github-actions

Choose a tag to compare

5.0.4 Pre-release
Pre-release

Added

  • FieldBuilder#noSync() — chainable modifier that marks a field as
    local-only. The field is still typed on the inferred instance and
    still honors .default() / .optional() / collection auto-instantiation,
    but it is never registered for synchronization: it skips change tracking,
    is never encoded, and decoders never receive it. Useful for server-side
    scratch state or per-peer UI state you want on the class for typing
    convenience without paying any sync cost.

    const Player = schema({
     hp: t.uint8().default(100), // synchronized
     lastInputTick: t.number().noSync(), // local-only, never sent
    }, 'Player');

    Combining .noSync() with a sync-only modifier (.view(), .owned(),
    .unreliable(), .transient(), .static(), .stream()) throws at
    schema() time, since a local-only field cannot be synchronized.

Changed

  • FieldBuilder's internal configuration fields (_type, _default,
    _view, _noSync, ...) and toDefinition() are now declared private,
    so editor autocomplete on t.number(). surfaces only the chainable
    fluent modifiers. The fields remain reachable at runtime via element
    access (e.g. builder['_noSync']) for internal tooling, but are no
    longer part of the intended public API.

Fixed

  • npm test now points at mocha's JS entry (node_modules/mocha/bin/mocha.js)
    instead of the .bin/mocha shim. Under pnpm the shim is a POSIX shell
    script, which tsx tried to parse as JavaScript and failed with
    SyntaxError: missing ) after argument list.
  • Resolved a duplicate typecheck script key in package.json; the
    build-config typecheck is now available as typecheck:build.
Loading

4.0.26

29 May 18:44
@github-actions github-actions

Choose a tag to compare

Decoder: fix "refId not found" when replacing a collection that holds a shared child

A Schema instance shared between a collection and another holder (e.g. an
array element also assigned to a sibling field) could be dropped on the client
when that collection was replaced in the same patch, surfacing as
"refId" not found / trying to remove refId that doesn't exist decode errors.

The collection-replace path in decodeValue was decrementing each previous
child's refId, then garbageCollectDeletedRefs() decremented them again — a
shared child got double-counted and dropped while still referenced. Child
reference-counting is now left to GC. A guard also releases the previous
collection's own refId when the replacement op isn't tagged DELETE (e.g. an
encodeAll() not followed by discardChanges()), preventing a leak.

Thanks to @beemdvp for the report.

@view() now accepts bitwise tags

The @view() decorator can now be given a bitmask of tags
(@view(Tag.A | Tag.B)). A field becomes visible to any client whose
view.add(obj, tag) call shares at least one bit with the field's mask, so a
single field can be exposed to multiple tag audiences at once.

Internally, per-ChangeTree tag storage moved from WeakMap<ChangeTree, Set<number>> to a single integer bitmask, with membership resolved via bitwise
& instead of Set lookups. Custom tags must therefore be powers of two
(1 << 0, 1 << 1, ...). The default @view() tag is unaffected.

Thanks to @FTWinston for the contribution.

Contributors

FTWinston and beemdvp
Assets 2
Loading

4.0.25

08 May 14:17
@github-actions github-actions

Choose a tag to compare

@view(N) collections: items pushed after view.add are now visible

Items added to a non-default-tag collection (e.g.
@view(1) @type([Item]) items) after the client called
view.add(state, 1) were silently invisible — the array's ADD op
was emitted but the new item's fields didn't share visibility with the
parent.

Children of @view(N) collections now inherit parent visibility.
Default-tag @view() collections keep per-item gating unchanged —
view.add(item) is still required to opt each one in.

Thanks to @FTWinston for the report
and fix (#226).

Loading

4.0.24

07 May 02:05
@github-actions github-actions

Choose a tag to compare

ChangeTree.delete: fix encodeAll dropping sibling fields after undefined assignment to a @view() field

Thanks to @Gabixel for the follow-up report after 4.0.22.

On a Schema with both @view() and non-@view() fields, assigning
undefined to the @view() field evicted an unrelated sibling from
allChanges. Incremental clients were fine; a fresh client joining via
encodeAll() saw the sibling field silently missing.

delete() was picking its target changeset by filteredChanges !== undefined
instead of mirroring change()'s per-field isFiltered test, so the
matching deleteOperationAtIndex ran on the wrong side and its
"find last operation" fallback removed a neighbor. Now symmetric with
change() across both the *Changes and *allChanges pairs.

Contributors

Gabixel
Loading

4.0.23

06 May 01:00
@github-actions github-actions

Choose a tag to compare

Callbacks: accept Schema instances across multiple @colyseus/schema copies

The nested-instance overloads of onAdd, onChange, onRemove, and bindTo
previously declared <TInstance extends Schema, ...>. When two copies of
@colyseus/schema end up in node_modules (e.g. one in the consuming app and
one transitively pulled in by an SDK), TypeScript infers data parameters
with a structural shape that doesn't extend the local Schema class, and
TInstance collapses to the base Schema. That made
CollectionPropNames<TInstance> evaluate to never, surfacing as the
infamous "Argument of type '"playingUsers"' is not assignable to parameter
of type 'never'"
on otherwise-correct code like:

callbacks.listen("gameData", (data) => {
 callbacks.onAdd(data, "playingUsers", (user) => { /* ... */ });
});

The constraint is now relaxed to match the same pattern already in listen:

  • onAdd, onRemove, bindTo: <TInstance, ...> (no constraint)
  • onChange: <TInstance extends object, ...>extends object is kept
    here only to disambiguate the 2-arg onChange(instance, handler) overload
    from the 2-arg onChange(property, handler) overload, so a string property
    name still routes to the root-collection form.

Misspelled property names and non-collection properties continue to be
rejected, since K extends CollectionPropNames<TInstance> /
K extends PublicPropNames<TInstance> still gates them.

Also fixed: Callbacks.getLegacy() previously fell through to undefined
when the input matched neither Decoder nor { serializer: { decoder } };
it now throws Invalid room or decoder to match Callbacks.get().

Loading

4.0.22

05 May 20:07
@github-actions github-actions

Choose a tag to compare

StateView: fix "refId" not found from out-of-order view.changes

Encoder.encodeView iterated view.changes in Map insertion order, which
isn't always topological. Sequences that mixed view.remove with a later
view.add — including view.add after re-parenting an instance via a
collection push — could leave a child's entry in the Map ahead of an
ancestor that hadn't been touched yet. The wire stream then emitted
SWITCH_TO_STRUCTURE for the child before any earlier op had registered
its refId on the decoder, surfacing as "refId" not found (and the
remainder of that patch silently skipped).

Encoder.encodeView now iterates in topological order via a DFS
post-order over the parent chain. The pass is gated on a
StateView.changesOutOfOrder flag set inside StateView.remove (the
only operation that bypasses addParentOf's deepest-ancestor-first
ordering) and reset when view.changes is cleared, so the hot path
stays at plain Map iteration when no remove happened in the tick.

Same wire-order class as colyseus/colyseus#936; the fix here closes it
at the schema layer so any consumer of Encoder.encodeView gets a
topologically ordered stream by construction.

Thanks to @anaibol for the test cases ported from colyseus/colyseus#936
and to @Gabixel for the standalone reproducer at
Gabixel/colyseus-test-stateview-repo.

Contributors

anaibol and Gabixel
Loading
Gabixel and HeadClot reacted with heart emoji
2 people reacted

4.0.21

29 Apr 23:30
@github-actions github-actions

Choose a tag to compare

@view: nested Schema fields inherit parent visibility

Previously, when a @view-gated field held a nested Schema, the nested
instance was encoded but its fields were not — clients would see the reference
but every property came through as undefined. The only workaround was to wrap
the nested instance in an ArraySchema, which propagated visibility from the
parent.

Nested Schema fields now inherit visibility from a @view-gated parent
regardless of whether the parent is a collection. Nested fields decorated with
their own @view continue to opt out, so explicit per-field gating is
preserved.

Thanks to @FTWinston for the contribution (#218).

Contributors

FTWinston
Loading

5.0.3

27 Apr 20:04
@github-actions github-actions

Choose a tag to compare

5.0.3 Pre-release
Pre-release

Added

  • Reflection.makeEncodable(ctor) — opt-in upgrade for classes
    reconstructed via Reflection.decode. Installs the same prototype
    accessor descriptors and metadata[$encoders] lookup table that the
    schema(...) / @type builders install at class-definition time, so
    the reconstructed class becomes usable as an encode source for
    InputEncoder and Encoder. Idempotent. Reflection.decode itself
    is unchanged — decoder-only callers (the dominant case) pay nothing
    extra; only code that explicitly opts in pays the descriptor + encoder
    install cost. This unblocks Colyseus 0.18's reflection-based input
    schema discovery, where the SDK reconstructs the input class from the
    server's JOIN_ROOM handshake bytes and then needs to encode against
    it.
  • Metadata.defineField(target, metadata, fieldIndex, fieldName, type)
    — internal helper that folds the per-field install logic (descriptor
    build, prototype install, $encoders slot) into a single shared path.
    Called by both Metadata.setFields (build path) and
    Reflection.makeEncodable (Reflection upgrade path) to keep the
    field-installation logic in one place.
Loading
Previous 1 3 4 5 12 13
Previous

AltStyle によって変換されたページ (->オリジナル) /