@colyseus/schema 5.0.3 → 5.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -27,6 +27,8 @@ export interface BuilderDefinition {
27
27
  static?: boolean;
28
28
  stream?: boolean;
29
29
  optional?: boolean;
30
+ /** Local-only field: typed + initialized, but never registered for sync. */
31
+ noSync?: boolean;
30
32
  /** Declaration-scope priority callback for `.stream()` fields. */
31
33
  streamPriority?: (view: any, element: any) => number;
32
34
  }
@@ -64,21 +66,24 @@ export class FieldBuilder<
64
66
  > {
65
67
  readonly [$builder]: true = true;
66
68
 
67
- // Internal configuration. Public so schema() and tests can read it, but not
68
- // meant to be mutated by users directly.
69
- _type: DefinitionType;
70
- _default: any = undefined;
71
- _hasDefault = false;
72
- _view: number | undefined = undefined;
73
- _owned = false;
74
- _unreliable = false;
75
- _transient = false;
76
- _deprecated = false;
77
- _deprecatedThrows = true;
78
- _static = false;
79
- _stream = false;
80
- _optional = false;
81
- _streamPriority: ((view: any, element: any) => number) | undefined = undefined;
69
+ // Internal configuration. Declared `private` (soft-private): hidden from
70
+ // editor autocomplete and from normal external `.field` access, but still
71
+ // reachable at runtime via element access (e.g. `builder['_noSync']`) for
72
+ // internal tooling/tests. Not meant to be mutated by end users.
73
+ private _type: DefinitionType;
74
+ private _default: any = undefined;
75
+ private _hasDefault = false;
76
+ private _view: number | undefined = undefined;
77
+ private _owned = false;
78
+ private _unreliable = false;
79
+ private _transient = false;
80
+ private _deprecated = false;
81
+ private _deprecatedThrows = true;
82
+ private _static = false;
83
+ private _stream = false;
84
+ private _optional = false;
85
+ private _noSync = false;
86
+ private _streamPriority: ((view: any, element: any) => number) | undefined = undefined;
82
87
 
83
88
  constructor(type: DefinitionType) {
84
89
  this._type = type;
@@ -136,6 +141,31 @@ export class FieldBuilder<
136
141
  return this;
137
142
  }
138
143
 
144
+ /**
145
+ * Mark this field as **local-only** — it is typed and initialized on the
146
+ * instance (so `.default()` and the inferred instance type still apply),
147
+ * but is never registered for synchronization: it never enters change
148
+ * tracking, never goes over the wire, and decoders never receive it.
149
+ *
150
+ * Useful for server-side scratch state, per-peer UI state, or values you
151
+ * want on the class for typing convenience without paying any sync cost.
152
+ *
153
+ * Mutually exclusive with the sync-only modifiers (`.view()`, `.owned()`,
154
+ * `.unreliable()`, `.transient()`, `.static()`, `.stream()`) — combining
155
+ * them throws at `schema()` time.
156
+ *
157
+ * ```ts
158
+ * const Player = schema({
159
+ * hp: t.uint8().default(100), // synchronized
160
+ * lastInputTick: t.number().noSync(), // local-only
161
+ * }, 'Player');
162
+ * ```
163
+ */
164
+ noSync(): this {
165
+ this._noSync = true;
166
+ return this;
167
+ }
168
+
139
169
  /**
140
170
  * Opt a collection field into priority-batched streaming delivery —
141
171
  * ADDs drain at most `maxPerTick` per tick per view (or per broadcast
@@ -194,7 +224,12 @@ export class FieldBuilder<
194
224
  return this as unknown as FieldBuilder<T | undefined, HasDefault, true>;
195
225
  }
196
226
 
197
- toDefinition(): BuilderDefinition {
227
+ /**
228
+ * @internal — snapshot of the builder's configuration consumed by
229
+ * `schema()`. `private` keeps it out of autocomplete; internal callers
230
+ * reach it via element access (`builder['toDefinition']()`).
231
+ */
232
+ private toDefinition(): BuilderDefinition {
198
233
  return {
199
234
  type: this._type,
200
235
  default: this._default,
@@ -208,6 +243,7 @@ export class FieldBuilder<
208
243
  static: this._static,
209
244
  stream: this._stream,
210
245
  optional: this._optional,
246
+ noSync: this._noSync,
211
247
  streamPriority: this._streamPriority,
212
248
  };
213
249
  }
@@ -221,8 +257,33 @@ export function isBuilder(value: any): value is FieldBuilder<any> {
221
257
  // Factory helpers
222
258
  // ---------------------------------------------------------------------------
223
259
 
224
- function primitive<T>(name: RawPrimitiveType): () => FieldBuilder<T> {
225
- return () => new FieldBuilder<T>(name);
260
+ /**
261
+ * Primitive field factory. Calling it bare (`t.int8()`) yields the natural type
262
+ * for the wire codec (`number` for the int/float formats, plus `string` /
263
+ * `boolean` / `bigint`). Pass an explicit type argument to refine the inferred
264
+ * value at the TYPE level, while the wire encoding is unchanged:
265
+ *
266
+ * moveX: t.int8<-1 | 0 | 1>(), // typed -1|0|1, still encoded as int8
267
+ * team: t.string<"red" | "blue">(),
268
+ *
269
+ * Two call signatures, NOT a defaulted generic `<T extends TBase = TBase>`: the
270
+ * bare form must return a CONCRETE `FieldBuilder<TBase>` so `schema({ x:
271
+ * t.number() })` still infers `x: number`. A defaulted free type parameter gets
272
+ * captured as `any` during `schema()`'s self-referential field inference (and
273
+ * `undefined extends any` then flips every field optional).
274
+ *
275
+ * NOTE: the refinement is a TYPE-LEVEL assertion, not a runtime guarantee — the
276
+ * wire still carries the codec's full range and the DECODER writes whatever
277
+ * bytes arrive. Sound for server-authored state; for INPUT schemas the value
278
+ * comes from an untrusted client (the type reads `-1|0|1` while a peer can send
279
+ * any int8), so keep validating/clamping on the receiving side.
280
+ */
281
+ interface PrimitiveFactory<TBase> {
282
+ (): FieldBuilder<TBase>;
283
+ <T extends TBase>(): FieldBuilder<T>;
284
+ }
285
+ function primitive<TBase>(name: RawPrimitiveType): PrimitiveFactory<TBase> {
286
+ return (() => new FieldBuilder<TBase>(name)) as PrimitiveFactory<TBase>;
226
287
  }
227
288
 
228
289
  // Accepts a Schema class, a primitive string, or another FieldBuilder as a child type.
@@ -233,7 +294,8 @@ export type ChildType =
233
294
 
234
295
  function resolveChild(child: ChildType): DefinitionType {
235
296
  if (isBuilder(child)) {
236
- return child._type;
297
+ // `_type` is private; element access bypasses the visibility check.
298
+ return child['_type'];
237
299
  }
238
300
  return child as DefinitionType;
239
301
  }
@@ -283,7 +345,7 @@ const collectionFactory: CollectionFactory = ((child: ChildType) =>
283
345
  new FieldBuilder({ collection: resolveChild(child) } as DefinitionType)) as CollectionFactory;
284
346
  const streamFactory: StreamFactory = ((child: ChildType) => {
285
347
  const b = new FieldBuilder({ stream: resolveChild(child) } as DefinitionType);
286
- b._stream = true;
348
+ b['_stream'] = true; // element access bypasses `private`
287
349
  return b;
288
350
  }) as StreamFactory;
289
351