@colyseus/schema 5.0.4 → 5.0.6

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.
@@ -97,6 +97,28 @@ export type ToJSON<T> = NonFunctionProps<{
97
97
  } & {
98
98
  [K in ToJSONOptionalKeys<T>]?: ToJSONField<Exclude<T[K], undefined>>;
99
99
  }>;
100
+ /**
101
+ * The plain DATA shape of a Schema instance type `T`: its synchronized fields
102
+ * with all `Schema` machinery stripped (`assign`, `clone`, `toJSON`, the
103
+ * change-tracking state, the internal symbol keys, …), so a plain object literal
104
+ * satisfies it. Field types — including narrowed primitives like
105
+ * `t.int8<-1 | 0 | 1>()` — are preserved exactly.
106
+ *
107
+ * Use it to type code that operates on schema-shaped *plain objects* rather than
108
+ * decoded instances: deterministic simulation / physics steps, synthesized or
109
+ * buffered input commands, plain DTOs, etc.
110
+ *
111
+ * ```ts
112
+ * function applyInput(state: Player, cmd: Data<MoveInput>) { … }
113
+ * applyInput(player, { moveX: 1, jump: false, dt }); // plain literal — OK
114
+ * ```
115
+ *
116
+ * Unlike {@link ToJSON} (a recursive *serialization* shape), this is a flat
117
+ * structural projection: nested Schema / collection fields keep their instance
118
+ * types, and it does not retain the non-method `Schema` members that `ToJSON`'s
119
+ * `NonFunctionProps` pass leaves behind.
120
+ */
121
+ export type Data<T> = Omit<T, keyof Schema>;
100
122
  export type IsNever<T> = [T] extends [never] ? true : false;
101
123
  /**
102
124
  * Type helper for .assign() method - allows assigning values in a flexible way
@@ -162,6 +162,31 @@ export declare class FieldBuilder<T = unknown, HasDefault extends boolean = fals
162
162
  private toDefinition;
163
163
  }
164
164
  export declare function isBuilder(value: any): value is FieldBuilder<any>;
165
+ /**
166
+ * Primitive field factory. Calling it bare (`t.int8()`) yields the natural type
167
+ * for the wire codec (`number` for the int/float formats, plus `string` /
168
+ * `boolean` / `bigint`). Pass an explicit type argument to refine the inferred
169
+ * value at the TYPE level, while the wire encoding is unchanged:
170
+ *
171
+ * moveX: t.int8<-1 | 0 | 1>(), // typed -1|0|1, still encoded as int8
172
+ * team: t.string<"red" | "blue">(),
173
+ *
174
+ * Two call signatures, NOT a defaulted generic `<T extends TBase = TBase>`: the
175
+ * bare form must return a CONCRETE `FieldBuilder<TBase>` so `schema({ x:
176
+ * t.number() })` still infers `x: number`. A defaulted free type parameter gets
177
+ * captured as `any` during `schema()`'s self-referential field inference (and
178
+ * `undefined extends any` then flips every field optional).
179
+ *
180
+ * NOTE: the refinement is a TYPE-LEVEL assertion, not a runtime guarantee — the
181
+ * wire still carries the codec's full range and the DECODER writes whatever
182
+ * bytes arrive. Sound for server-authored state; for INPUT schemas the value
183
+ * comes from an untrusted client (the type reads `-1|0|1` while a peer can send
184
+ * any int8), so keep validating/clamping on the receiving side.
185
+ */
186
+ interface PrimitiveFactory<TBase> {
187
+ (): FieldBuilder<TBase>;
188
+ <T extends TBase>(): FieldBuilder<T>;
189
+ }
165
190
  export type ChildType = RawPrimitiveType | Constructor<Schema> | FieldBuilder<any>;
166
191
  interface ArrayFactory {
167
192
  <C extends Constructor<Schema>>(child: C): FieldBuilder<ArraySchema<InstanceType<C>>, true, false>;
@@ -195,21 +220,21 @@ interface RefFactory {
195
220
  <C extends Constructor<Schema>>(ctor: C): FieldBuilder<InstanceType<C>, RefHasDefault<C>, false>;
196
221
  }
197
222
  export declare const t: Readonly<{
198
- string: () => FieldBuilder<string, false, false>;
199
- number: () => FieldBuilder<number, false, false>;
200
- boolean: () => FieldBuilder<boolean, false, false>;
201
- int8: () => FieldBuilder<number, false, false>;
202
- uint8: () => FieldBuilder<number, false, false>;
203
- int16: () => FieldBuilder<number, false, false>;
204
- uint16: () => FieldBuilder<number, false, false>;
205
- int32: () => FieldBuilder<number, false, false>;
206
- uint32: () => FieldBuilder<number, false, false>;
207
- int64: () => FieldBuilder<number, false, false>;
208
- uint64: () => FieldBuilder<number, false, false>;
209
- float32: () => FieldBuilder<number, false, false>;
210
- float64: () => FieldBuilder<number, false, false>;
211
- bigint64: () => FieldBuilder<bigint, false, false>;
212
- biguint64: () => FieldBuilder<bigint, false, false>;
223
+ string: PrimitiveFactory<string>;
224
+ number: PrimitiveFactory<number>;
225
+ boolean: PrimitiveFactory<boolean>;
226
+ int8: PrimitiveFactory<number>;
227
+ uint8: PrimitiveFactory<number>;
228
+ int16: PrimitiveFactory<number>;
229
+ uint16: PrimitiveFactory<number>;
230
+ int32: PrimitiveFactory<number>;
231
+ uint32: PrimitiveFactory<number>;
232
+ int64: PrimitiveFactory<number>;
233
+ uint64: PrimitiveFactory<number>;
234
+ float32: PrimitiveFactory<number>;
235
+ float64: PrimitiveFactory<number>;
236
+ bigint64: PrimitiveFactory<bigint>;
237
+ biguint64: PrimitiveFactory<bigint>;
213
238
  /** Reference to a Schema subtype. `t.array(Item)` usually reads better, but this is available when a plain ref is needed. */
214
239
  ref: RefFactory;
215
240
  array: ArrayFactory;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colyseus/schema",
3
- "version": "5.0.4",
3
+ "version": "5.0.6",
4
4
  "description": "Binary state serializer with delta encoding for games",
5
5
  "type": "module",
6
6
  "bin": {
package/src/index.ts CHANGED
@@ -71,7 +71,7 @@ export { t, FieldBuilder, isBuilder, type BuilderDefinition, type ChildType } fr
71
71
  export { TypeContext } from "./types/TypeContext.js";
72
72
 
73
73
  // Helper types for type inference
74
- export type { InferValueType, InferSchemaInstanceType, AssignableProps, BuilderInitProps } from "./types/HelperTypes.js";
74
+ export type { InferValueType, InferSchemaInstanceType, AssignableProps, BuilderInitProps, Data } from "./types/HelperTypes.js";
75
75
 
76
76
  export { getDecoderStateCallbacks, type CallbackProxy, type SchemaCallback, type CollectionCallback, type SchemaCallbackProxy } from "./decoder/strategy/getDecoderStateCallbacks.js";
77
77
  export { Callbacks, StateCallbackStrategy } from "./decoder/strategy/Callbacks.js";
@@ -143,6 +143,29 @@ export type ToJSON<T> = NonFunctionProps<
143
143
  & { [K in ToJSONOptionalKeys<T>]?: ToJSONField<Exclude<T[K], undefined>> }
144
144
  >;
145
145
 
146
+ /**
147
+ * The plain DATA shape of a Schema instance type `T`: its synchronized fields
148
+ * with all `Schema` machinery stripped (`assign`, `clone`, `toJSON`, the
149
+ * change-tracking state, the internal symbol keys, …), so a plain object literal
150
+ * satisfies it. Field types — including narrowed primitives like
151
+ * `t.int8<-1 | 0 | 1>()` — are preserved exactly.
152
+ *
153
+ * Use it to type code that operates on schema-shaped *plain objects* rather than
154
+ * decoded instances: deterministic simulation / physics steps, synthesized or
155
+ * buffered input commands, plain DTOs, etc.
156
+ *
157
+ * ```ts
158
+ * function applyInput(state: Player, cmd: Data<MoveInput>) { … }
159
+ * applyInput(player, { moveX: 1, jump: false, dt }); // plain literal — OK
160
+ * ```
161
+ *
162
+ * Unlike {@link ToJSON} (a recursive *serialization* shape), this is a flat
163
+ * structural projection: nested Schema / collection fields keep their instance
164
+ * types, and it does not retain the non-method `Schema` members that `ToJSON`'s
165
+ * `NonFunctionProps` pass leaves behind.
166
+ */
167
+ export type Data<T> = Omit<T, keyof Schema>;
168
+
146
169
  // Helper type to check if T is exactly 'never' (meaning no InitProps was provided)
147
170
  export type IsNever<T> = [T] extends [never] ? true : false;
148
171
 
@@ -257,8 +257,33 @@ export function isBuilder(value: any): value is FieldBuilder<any> {
257
257
  // Factory helpers
258
258
  // ---------------------------------------------------------------------------
259
259
 
260
- function primitive<T>(name: RawPrimitiveType): () => FieldBuilder<T> {
261
- 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>;
262
287
  }
263
288
 
264
289
  // Accepts a Schema class, a primitive string, or another FieldBuilder as a child type.