@colyseus/shared-types 0.17.2 → 0.17.4

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts"],
4
- "sourcesContent": ["import type { StandardSchemaV1 } from '@standard-schema/spec';\n\n// Re-export StandardSchemaV1 for convenience\nexport type { StandardSchemaV1 };\n\n// Re-export Protocol types\nexport { Protocol, ErrorCode, CloseCode } from './Protocol.js';\n\n/**\n * Minimal Room-like interface for SDK type inference.\n * Allows typing SDK methods without depending on @colyseus/core.\n */\nexport interface ServerRoomLike<State = any, Options = any> {\n state: State;\n onJoin: (client: any, options?: Options, auth?: any) => any;\n messages?: Record<string, any>;\n '~client'?: { '~messages'?: Record<string, any> };\n}\n\n/**\n * Seat reservation returned by matchmaking operations.\n */\nexport interface ISeatReservation {\n name: string;\n sessionId: string;\n roomId: string;\n publicAddress?: string;\n processId?: string;\n reconnectionToken?: string;\n devMode?: boolean;\n}\n\n/**\n * Helper types for flexible Room generics on the client SDK.\n * Allows: Room<State>, Room<ServerRoom>, or Room<ServerRoom, State>\n *\n * Uses `~state` phantom property to distinguish Room types from plain state types.\n * This prevents incorrectly extracting `state` from a state type that happens to have a `state` property.\n */\nexport type InferState<T, S> = [S] extends [never]\n ? (T extends abstract new (...args: any) => { '~state': infer ST }\n ? ST // Constructor type (typeof MyRoom): extract state via ~state phantom\n : T extends { '~state': infer ST }\n ? ST // Instance type (MyRoom): extract state via ~state phantom\n : T) // State type or other: return as-is\n : S;\n\n/**\n * Normalizes T for message extraction: returns T if it has ~state (Room type),\n * otherwise returns any (plain state type). This ensures Room<State> is equivalent\n * to Room<any, State> when State doesn't have ~state.\n */\nexport type NormalizeRoomType<T> = T extends abstract new (...args: any) => { '~state': any }\n ? T // Constructor type with ~state: keep as-is\n : T extends { '~state': any }\n ? T // Instance type with ~state: keep as-is\n : any; // Plain state type: normalize to any\n\n/**\n * Extract room messages type from a Room constructor or instance type.\n * Supports both constructor types (typeof MyRoom) and instance types (MyRoom)\n */\nexport type ExtractRoomMessages<T> = T extends abstract new (...args: any) => { messages: infer M }\n ? M\n : T extends { messages: infer M }\n ? M\n : {};\n\n/**\n * Extract client-side messages type from a Room constructor or instance type.\n * These are messages that the server can send to the client.\n */\nexport type ExtractRoomClientMessages<T> = T extends abstract new (...args: any) => { '~client': { '~messages': infer M } }\n ? M\n : T extends { '~client': { '~messages': infer M } }\n ? M\n : {};\n\n/**\n * Message handler with automatic type inference from format schema.\n * When a format is provided, the message type is automatically inferred from the schema.\n *\n * @template T - The StandardSchema type for message validation\n * @template Client - The client type (from @colyseus/core Transport)\n * @template This - The Room class context\n */\nexport type MessageHandlerWithFormat<T extends StandardSchemaV1 = any, Client = any, This = any> = {\n format: T;\n handler: (this: This, client: Client, message: StandardSchemaV1.InferOutput<T>) => void;\n};\n\n/**\n * Message handler type that can be either a function or a format handler with validation.\n *\n * @template Client - The client type (from @colyseus/core Transport)\n * @template This - The Room class context\n */\nexport type MessageHandler<Client = any, This = any> =\n | ((this: This, client: Client, message: any) => void)\n | MessageHandlerWithFormat<any, Client, This>;\n\n/**\n * Extract the message payload type from a message handler.\n * Works with both function handlers and format handlers.\n */\nexport type ExtractMessageType<T> =\n T extends { format: infer Format extends StandardSchemaV1; handler: any }\n ? StandardSchemaV1.InferOutput<Format>\n : T extends (this: any, client: any, message: infer Message) => void\n ? Message\n : any;\n\n/**\n * A map of message types to message handlers.\n *\n * @template Room - The Room class type\n * @template Client - The client type\n */\nexport type Messages<Room = any, Client = any> = Record<string, MessageHandler<Client, Room>>;\n\n/**\n * Exposed types for the client-side SDK.\n * Used by defineServer() to expose room and route types to the client.\n *\n * @template RoomTypes - Record of room names to their RegisteredHandler types\n * @template Routes - Router type from @colyseus/better-call\n */\nexport interface SDKTypes<\n RoomTypes extends Record<string, any> = any,\n Routes = any\n> {\n '~rooms': RoomTypes;\n '~routes': Routes;\n}\n"],
4
+ "sourcesContent": ["import type { StandardSchemaV1 } from '@standard-schema/spec';\n\n// Re-export StandardSchemaV1 for convenience\nexport type { StandardSchemaV1 };\n\n// Re-export Protocol types\nexport { Protocol, ErrorCode, CloseCode } from './Protocol.js';\n\n/**\n * Minimal Room-like interface for SDK type inference.\n * Allows typing SDK methods without depending on @colyseus/core.\n * Note: onJoin is optional because core Room defines it as optional.\n */\nexport interface ServerRoomLike<State = any, Options = any> {\n state: State;\n onJoin?: (client: any, options?: Options, auth?: any) => any;\n messages?: Record<string, any>;\n '~client'?: { '~messages'?: Record<string, any> };\n}\n\n/**\n * Seat reservation returned by matchmaking operations.\n */\nexport interface ISeatReservation {\n name: string;\n sessionId: string;\n roomId: string;\n publicAddress?: string;\n processId?: string;\n reconnectionToken?: string;\n devMode?: boolean;\n}\n\n/**\n * Extract instance type from a constructor type.\n * If T is not a constructor, returns T as-is.\n */\ntype Instantiate<T> = T extends abstract new (...args: any) => infer I ? I : T;\n\n/**\n * Check if a type is a Schema (has ~refId marker).\n * Schema defines ~refId as optional, so we check keyof instead of property presence.\n */\ntype IsSchema<T> = '~refId' extends keyof T ? true : false;\n\n/**\n * Check if ~state phantom property contains a useful type (not object/any/never).\n * Returns true if ~state exists and has meaningful structure.\n */\ntype HasUsefulStatePhantom<T> = T extends { '~state': infer S }\n ? [S] extends [never] ? false // never is not useful\n : unknown extends S ? false // any is not useful\n : S extends object\n ? [keyof S] extends [never] ? false // {} or object with no keys is not useful\n : true\n : false\n : false;\n\n/**\n * Extract state from a Room-like instance type.\n * Priority: useful ~state phantom > Schema state property > return T as-is\n */\ntype ExtractStateFromRoom<T> = T extends { '~state': infer S }\n ? HasUsefulStatePhantom<T> extends true\n ? S // Use ~state if it's useful\n : T extends { state: infer St }\n ? IsSchema<St> extends true ? St : T // Fallback to state if Schema\n : T\n : T extends { state: infer S }\n ? IsSchema<S> extends true ? S : T\n : T;\n\n/**\n * Infer the state type from T, or use explicit S if provided.\n *\n * Supports multiple usage patterns:\n * - Room<MyState>: T is a Schema type, return as-is\n * - Room<MyRoom>: T is a Room instance, extract from ~state or state property\n * - Room<typeof MyRoom>: T is a constructor, instantiate first then extract\n * - Room<T, ExplicitState>: S overrides all inference\n */\nexport type InferState<T, S> = [S] extends [never]\n ? Instantiate<T> extends infer I\n ? IsSchema<I> extends true\n ? I // It's a Schema, return as-is\n : ExtractStateFromRoom<I>\n : never\n : S;\n\n/**\n * Normalizes T for message extraction: returns T if it has ~state (Room type),\n * otherwise returns any (plain state type). This ensures Room<State> is equivalent\n * to Room<any, State> when State doesn't have ~state.\n */\nexport type NormalizeRoomType<T> = Instantiate<T> extends { '~state': any } ? T : any;\n\n/**\n * Extract room messages type from a Room constructor or instance type.\n * Supports both constructor types (typeof MyRoom) and instance types (MyRoom)\n */\nexport type ExtractRoomMessages<T> = Instantiate<T> extends { messages: infer M } ? M : {};\n\n/**\n * Extract client-side messages type from a Room constructor or instance type.\n * These are messages that the server can send to the client.\n */\nexport type ExtractRoomClientMessages<T> = Instantiate<T> extends { '~client': { '~messages': infer M } } ? M : {};\n\n/**\n * Message handler with automatic type inference from format schema.\n * When a format is provided, the message type is automatically inferred from the schema.\n *\n * @template T - The StandardSchema type for message validation\n * @template Client - The client type (from @colyseus/core Transport)\n * @template This - The Room class context\n */\nexport type MessageHandlerWithFormat<T extends StandardSchemaV1 = any, Client = any, This = any> = {\n format: T;\n handler: (this: This, client: Client, message: StandardSchemaV1.InferOutput<T>) => void;\n};\n\n/**\n * Message handler type that can be either a function or a format handler with validation.\n *\n * @template Client - The client type (from @colyseus/core Transport)\n * @template This - The Room class context\n */\nexport type MessageHandler<Client = any, This = any> =\n | ((this: This, client: Client, message: any) => void)\n | MessageHandlerWithFormat<any, Client, This>;\n\n/**\n * Extract the message payload type from a message handler.\n * Works with both function handlers and format handlers.\n */\nexport type ExtractMessageType<T> =\n T extends { format: infer Format extends StandardSchemaV1; handler: any }\n ? StandardSchemaV1.InferOutput<Format>\n : T extends (this: any, client: any, message: infer Message) => void\n ? Message\n : any;\n\n/**\n * A map of message types to message handlers.\n *\n * @template Room - The Room class type\n * @template Client - The client type\n */\nexport type Messages<Room = any, Client = any> = Record<string, MessageHandler<Client, Room>>;\n\n/**\n * Exposed types for the client-side SDK.\n * Used by defineServer() to expose room and route types to the client.\n *\n * @template RoomTypes - Record of room names to their RegisteredHandler types\n * @template Routes - Router type from @colyseus/better-call\n */\nexport interface SDKTypes<\n RoomTypes extends Record<string, any> = any,\n Routes = any\n> {\n '~rooms': RoomTypes;\n '~routes': Routes;\n}\n"],
5
5
  "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAMA,sBAA+C;",
6
6
  "names": []
7
7
  }
package/build/index.d.ts CHANGED
@@ -4,10 +4,11 @@ export { Protocol, ErrorCode, CloseCode } from './Protocol.js';
4
4
  /**
5
5
  * Minimal Room-like interface for SDK type inference.
6
6
  * Allows typing SDK methods without depending on @colyseus/core.
7
+ * Note: onJoin is optional because core Room defines it as optional.
7
8
  */
8
9
  export interface ServerRoomLike<State = any, Options = any> {
9
10
  state: State;
10
- onJoin: (client: any, options?: Options, auth?: any) => any;
11
+ onJoin?: (client: any, options?: Options, auth?: any) => any;
11
12
  messages?: Record<string, any>;
12
13
  '~client'?: {
13
14
  '~messages'?: Record<string, any>;
@@ -26,45 +27,63 @@ export interface ISeatReservation {
26
27
  devMode?: boolean;
27
28
  }
28
29
  /**
29
- * Helper types for flexible Room generics on the client SDK.
30
- * Allows: Room<State>, Room<ServerRoom>, or Room<ServerRoom, State>
30
+ * Extract instance type from a constructor type.
31
+ * If T is not a constructor, returns T as-is.
32
+ */
33
+ type Instantiate<T> = T extends abstract new (...args: any) => infer I ? I : T;
34
+ /**
35
+ * Check if a type is a Schema (has ~refId marker).
36
+ * Schema defines ~refId as optional, so we check keyof instead of property presence.
37
+ */
38
+ type IsSchema<T> = '~refId' extends keyof T ? true : false;
39
+ /**
40
+ * Check if ~state phantom property contains a useful type (not object/any/never).
41
+ * Returns true if ~state exists and has meaningful structure.
42
+ */
43
+ type HasUsefulStatePhantom<T> = T extends {
44
+ '~state': infer S;
45
+ } ? [S] extends [never] ? false : unknown extends S ? false : S extends object ? [keyof S] extends [never] ? false : true : false : false;
46
+ /**
47
+ * Extract state from a Room-like instance type.
48
+ * Priority: useful ~state phantom > Schema state property > return T as-is
49
+ */
50
+ type ExtractStateFromRoom<T> = T extends {
51
+ '~state': infer S;
52
+ } ? HasUsefulStatePhantom<T> extends true ? S : T extends {
53
+ state: infer St;
54
+ } ? IsSchema<St> extends true ? St : T : T : T extends {
55
+ state: infer S;
56
+ } ? IsSchema<S> extends true ? S : T : T;
57
+ /**
58
+ * Infer the state type from T, or use explicit S if provided.
31
59
  *
32
- * Uses `~state` phantom property to distinguish Room types from plain state types.
33
- * This prevents incorrectly extracting `state` from a state type that happens to have a `state` property.
60
+ * Supports multiple usage patterns:
61
+ * - Room<MyState>: T is a Schema type, return as-is
62
+ * - Room<MyRoom>: T is a Room instance, extract from ~state or state property
63
+ * - Room<typeof MyRoom>: T is a constructor, instantiate first then extract
64
+ * - Room<T, ExplicitState>: S overrides all inference
34
65
  */
35
- export type InferState<T, S> = [S] extends [never] ? (T extends abstract new (...args: any) => {
36
- '~state': infer ST;
37
- } ? ST : T extends {
38
- '~state': infer ST;
39
- } ? ST : T) : S;
66
+ export type InferState<T, S> = [S] extends [never] ? Instantiate<T> extends infer I ? IsSchema<I> extends true ? I : ExtractStateFromRoom<I> : never : S;
40
67
  /**
41
68
  * Normalizes T for message extraction: returns T if it has ~state (Room type),
42
69
  * otherwise returns any (plain state type). This ensures Room<State> is equivalent
43
70
  * to Room<any, State> when State doesn't have ~state.
44
71
  */
45
- export type NormalizeRoomType<T> = T extends abstract new (...args: any) => {
46
- '~state': any;
47
- } ? T : T extends {
72
+ export type NormalizeRoomType<T> = Instantiate<T> extends {
48
73
  '~state': any;
49
74
  } ? T : any;
50
75
  /**
51
76
  * Extract room messages type from a Room constructor or instance type.
52
77
  * Supports both constructor types (typeof MyRoom) and instance types (MyRoom)
53
78
  */
54
- export type ExtractRoomMessages<T> = T extends abstract new (...args: any) => {
55
- messages: infer M;
56
- } ? M : T extends {
79
+ export type ExtractRoomMessages<T> = Instantiate<T> extends {
57
80
  messages: infer M;
58
81
  } ? M : {};
59
82
  /**
60
83
  * Extract client-side messages type from a Room constructor or instance type.
61
84
  * These are messages that the server can send to the client.
62
85
  */
63
- export type ExtractRoomClientMessages<T> = T extends abstract new (...args: any) => {
64
- '~client': {
65
- '~messages': infer M;
66
- };
67
- } ? M : T extends {
86
+ export type ExtractRoomClientMessages<T> = Instantiate<T> extends {
68
87
  '~client': {
69
88
  '~messages': infer M;
70
89
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts"],
4
- "sourcesContent": ["import type { StandardSchemaV1 } from '@standard-schema/spec';\n\n// Re-export StandardSchemaV1 for convenience\nexport type { StandardSchemaV1 };\n\n// Re-export Protocol types\nexport { Protocol, ErrorCode, CloseCode } from './Protocol.js';\n\n/**\n * Minimal Room-like interface for SDK type inference.\n * Allows typing SDK methods without depending on @colyseus/core.\n */\nexport interface ServerRoomLike<State = any, Options = any> {\n state: State;\n onJoin: (client: any, options?: Options, auth?: any) => any;\n messages?: Record<string, any>;\n '~client'?: { '~messages'?: Record<string, any> };\n}\n\n/**\n * Seat reservation returned by matchmaking operations.\n */\nexport interface ISeatReservation {\n name: string;\n sessionId: string;\n roomId: string;\n publicAddress?: string;\n processId?: string;\n reconnectionToken?: string;\n devMode?: boolean;\n}\n\n/**\n * Helper types for flexible Room generics on the client SDK.\n * Allows: Room<State>, Room<ServerRoom>, or Room<ServerRoom, State>\n *\n * Uses `~state` phantom property to distinguish Room types from plain state types.\n * This prevents incorrectly extracting `state` from a state type that happens to have a `state` property.\n */\nexport type InferState<T, S> = [S] extends [never]\n ? (T extends abstract new (...args: any) => { '~state': infer ST }\n ? ST // Constructor type (typeof MyRoom): extract state via ~state phantom\n : T extends { '~state': infer ST }\n ? ST // Instance type (MyRoom): extract state via ~state phantom\n : T) // State type or other: return as-is\n : S;\n\n/**\n * Normalizes T for message extraction: returns T if it has ~state (Room type),\n * otherwise returns any (plain state type). This ensures Room<State> is equivalent\n * to Room<any, State> when State doesn't have ~state.\n */\nexport type NormalizeRoomType<T> = T extends abstract new (...args: any) => { '~state': any }\n ? T // Constructor type with ~state: keep as-is\n : T extends { '~state': any }\n ? T // Instance type with ~state: keep as-is\n : any; // Plain state type: normalize to any\n\n/**\n * Extract room messages type from a Room constructor or instance type.\n * Supports both constructor types (typeof MyRoom) and instance types (MyRoom)\n */\nexport type ExtractRoomMessages<T> = T extends abstract new (...args: any) => { messages: infer M }\n ? M\n : T extends { messages: infer M }\n ? M\n : {};\n\n/**\n * Extract client-side messages type from a Room constructor or instance type.\n * These are messages that the server can send to the client.\n */\nexport type ExtractRoomClientMessages<T> = T extends abstract new (...args: any) => { '~client': { '~messages': infer M } }\n ? M\n : T extends { '~client': { '~messages': infer M } }\n ? M\n : {};\n\n/**\n * Message handler with automatic type inference from format schema.\n * When a format is provided, the message type is automatically inferred from the schema.\n *\n * @template T - The StandardSchema type for message validation\n * @template Client - The client type (from @colyseus/core Transport)\n * @template This - The Room class context\n */\nexport type MessageHandlerWithFormat<T extends StandardSchemaV1 = any, Client = any, This = any> = {\n format: T;\n handler: (this: This, client: Client, message: StandardSchemaV1.InferOutput<T>) => void;\n};\n\n/**\n * Message handler type that can be either a function or a format handler with validation.\n *\n * @template Client - The client type (from @colyseus/core Transport)\n * @template This - The Room class context\n */\nexport type MessageHandler<Client = any, This = any> =\n | ((this: This, client: Client, message: any) => void)\n | MessageHandlerWithFormat<any, Client, This>;\n\n/**\n * Extract the message payload type from a message handler.\n * Works with both function handlers and format handlers.\n */\nexport type ExtractMessageType<T> =\n T extends { format: infer Format extends StandardSchemaV1; handler: any }\n ? StandardSchemaV1.InferOutput<Format>\n : T extends (this: any, client: any, message: infer Message) => void\n ? Message\n : any;\n\n/**\n * A map of message types to message handlers.\n *\n * @template Room - The Room class type\n * @template Client - The client type\n */\nexport type Messages<Room = any, Client = any> = Record<string, MessageHandler<Client, Room>>;\n\n/**\n * Exposed types for the client-side SDK.\n * Used by defineServer() to expose room and route types to the client.\n *\n * @template RoomTypes - Record of room names to their RegisteredHandler types\n * @template Routes - Router type from @colyseus/better-call\n */\nexport interface SDKTypes<\n RoomTypes extends Record<string, any> = any,\n Routes = any\n> {\n '~rooms': RoomTypes;\n '~routes': Routes;\n}\n"],
4
+ "sourcesContent": ["import type { StandardSchemaV1 } from '@standard-schema/spec';\n\n// Re-export StandardSchemaV1 for convenience\nexport type { StandardSchemaV1 };\n\n// Re-export Protocol types\nexport { Protocol, ErrorCode, CloseCode } from './Protocol.js';\n\n/**\n * Minimal Room-like interface for SDK type inference.\n * Allows typing SDK methods without depending on @colyseus/core.\n * Note: onJoin is optional because core Room defines it as optional.\n */\nexport interface ServerRoomLike<State = any, Options = any> {\n state: State;\n onJoin?: (client: any, options?: Options, auth?: any) => any;\n messages?: Record<string, any>;\n '~client'?: { '~messages'?: Record<string, any> };\n}\n\n/**\n * Seat reservation returned by matchmaking operations.\n */\nexport interface ISeatReservation {\n name: string;\n sessionId: string;\n roomId: string;\n publicAddress?: string;\n processId?: string;\n reconnectionToken?: string;\n devMode?: boolean;\n}\n\n/**\n * Extract instance type from a constructor type.\n * If T is not a constructor, returns T as-is.\n */\ntype Instantiate<T> = T extends abstract new (...args: any) => infer I ? I : T;\n\n/**\n * Check if a type is a Schema (has ~refId marker).\n * Schema defines ~refId as optional, so we check keyof instead of property presence.\n */\ntype IsSchema<T> = '~refId' extends keyof T ? true : false;\n\n/**\n * Check if ~state phantom property contains a useful type (not object/any/never).\n * Returns true if ~state exists and has meaningful structure.\n */\ntype HasUsefulStatePhantom<T> = T extends { '~state': infer S }\n ? [S] extends [never] ? false // never is not useful\n : unknown extends S ? false // any is not useful\n : S extends object\n ? [keyof S] extends [never] ? false // {} or object with no keys is not useful\n : true\n : false\n : false;\n\n/**\n * Extract state from a Room-like instance type.\n * Priority: useful ~state phantom > Schema state property > return T as-is\n */\ntype ExtractStateFromRoom<T> = T extends { '~state': infer S }\n ? HasUsefulStatePhantom<T> extends true\n ? S // Use ~state if it's useful\n : T extends { state: infer St }\n ? IsSchema<St> extends true ? St : T // Fallback to state if Schema\n : T\n : T extends { state: infer S }\n ? IsSchema<S> extends true ? S : T\n : T;\n\n/**\n * Infer the state type from T, or use explicit S if provided.\n *\n * Supports multiple usage patterns:\n * - Room<MyState>: T is a Schema type, return as-is\n * - Room<MyRoom>: T is a Room instance, extract from ~state or state property\n * - Room<typeof MyRoom>: T is a constructor, instantiate first then extract\n * - Room<T, ExplicitState>: S overrides all inference\n */\nexport type InferState<T, S> = [S] extends [never]\n ? Instantiate<T> extends infer I\n ? IsSchema<I> extends true\n ? I // It's a Schema, return as-is\n : ExtractStateFromRoom<I>\n : never\n : S;\n\n/**\n * Normalizes T for message extraction: returns T if it has ~state (Room type),\n * otherwise returns any (plain state type). This ensures Room<State> is equivalent\n * to Room<any, State> when State doesn't have ~state.\n */\nexport type NormalizeRoomType<T> = Instantiate<T> extends { '~state': any } ? T : any;\n\n/**\n * Extract room messages type from a Room constructor or instance type.\n * Supports both constructor types (typeof MyRoom) and instance types (MyRoom)\n */\nexport type ExtractRoomMessages<T> = Instantiate<T> extends { messages: infer M } ? M : {};\n\n/**\n * Extract client-side messages type from a Room constructor or instance type.\n * These are messages that the server can send to the client.\n */\nexport type ExtractRoomClientMessages<T> = Instantiate<T> extends { '~client': { '~messages': infer M } } ? M : {};\n\n/**\n * Message handler with automatic type inference from format schema.\n * When a format is provided, the message type is automatically inferred from the schema.\n *\n * @template T - The StandardSchema type for message validation\n * @template Client - The client type (from @colyseus/core Transport)\n * @template This - The Room class context\n */\nexport type MessageHandlerWithFormat<T extends StandardSchemaV1 = any, Client = any, This = any> = {\n format: T;\n handler: (this: This, client: Client, message: StandardSchemaV1.InferOutput<T>) => void;\n};\n\n/**\n * Message handler type that can be either a function or a format handler with validation.\n *\n * @template Client - The client type (from @colyseus/core Transport)\n * @template This - The Room class context\n */\nexport type MessageHandler<Client = any, This = any> =\n | ((this: This, client: Client, message: any) => void)\n | MessageHandlerWithFormat<any, Client, This>;\n\n/**\n * Extract the message payload type from a message handler.\n * Works with both function handlers and format handlers.\n */\nexport type ExtractMessageType<T> =\n T extends { format: infer Format extends StandardSchemaV1; handler: any }\n ? StandardSchemaV1.InferOutput<Format>\n : T extends (this: any, client: any, message: infer Message) => void\n ? Message\n : any;\n\n/**\n * A map of message types to message handlers.\n *\n * @template Room - The Room class type\n * @template Client - The client type\n */\nexport type Messages<Room = any, Client = any> = Record<string, MessageHandler<Client, Room>>;\n\n/**\n * Exposed types for the client-side SDK.\n * Used by defineServer() to expose room and route types to the client.\n *\n * @template RoomTypes - Record of room names to their RegisteredHandler types\n * @template Routes - Router type from @colyseus/better-call\n */\nexport interface SDKTypes<\n RoomTypes extends Record<string, any> = any,\n Routes = any\n> {\n '~rooms': RoomTypes;\n '~routes': Routes;\n}\n"],
5
5
  "mappings": ";AAMA,SAAS,UAAU,WAAW,iBAAiB;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colyseus/shared-types",
3
- "version": "0.17.2",
3
+ "version": "0.17.4",
4
4
  "description": "Shared TypeScript types for Colyseus server and client SDK",
5
5
  "type": "module",
6
6
  "input": "./src/index.ts",
@@ -47,8 +47,8 @@
47
47
  },
48
48
  "devDependencies": {
49
49
  "vitest": "^3.1.1",
50
- "@colyseus/sdk": "^0.17.18",
51
- "@colyseus/core": "^0.17.17"
50
+ "@colyseus/core": "^0.17.22",
51
+ "@colyseus/sdk": "^0.17.22"
52
52
  },
53
53
  "publishConfig": {
54
54
  "access": "public"
package/src/index.ts CHANGED
@@ -9,10 +9,11 @@ export { Protocol, ErrorCode, CloseCode } from './Protocol.js';
9
9
  /**
10
10
  * Minimal Room-like interface for SDK type inference.
11
11
  * Allows typing SDK methods without depending on @colyseus/core.
12
+ * Note: onJoin is optional because core Room defines it as optional.
12
13
  */
13
14
  export interface ServerRoomLike<State = any, Options = any> {
14
15
  state: State;
15
- onJoin: (client: any, options?: Options, auth?: any) => any;
16
+ onJoin?: (client: any, options?: Options, auth?: any) => any;
16
17
  messages?: Record<string, any>;
17
18
  '~client'?: { '~messages'?: Record<string, any> };
18
19
  }
@@ -31,18 +32,59 @@ export interface ISeatReservation {
31
32
  }
32
33
 
33
34
  /**
34
- * Helper types for flexible Room generics on the client SDK.
35
- * Allows: Room<State>, Room<ServerRoom>, or Room<ServerRoom, State>
35
+ * Extract instance type from a constructor type.
36
+ * If T is not a constructor, returns T as-is.
37
+ */
38
+ type Instantiate<T> = T extends abstract new (...args: any) => infer I ? I : T;
39
+
40
+ /**
41
+ * Check if a type is a Schema (has ~refId marker).
42
+ * Schema defines ~refId as optional, so we check keyof instead of property presence.
43
+ */
44
+ type IsSchema<T> = '~refId' extends keyof T ? true : false;
45
+
46
+ /**
47
+ * Check if ~state phantom property contains a useful type (not object/any/never).
48
+ * Returns true if ~state exists and has meaningful structure.
49
+ */
50
+ type HasUsefulStatePhantom<T> = T extends { '~state': infer S }
51
+ ? [S] extends [never] ? false // never is not useful
52
+ : unknown extends S ? false // any is not useful
53
+ : S extends object
54
+ ? [keyof S] extends [never] ? false // {} or object with no keys is not useful
55
+ : true
56
+ : false
57
+ : false;
58
+
59
+ /**
60
+ * Extract state from a Room-like instance type.
61
+ * Priority: useful ~state phantom > Schema state property > return T as-is
62
+ */
63
+ type ExtractStateFromRoom<T> = T extends { '~state': infer S }
64
+ ? HasUsefulStatePhantom<T> extends true
65
+ ? S // Use ~state if it's useful
66
+ : T extends { state: infer St }
67
+ ? IsSchema<St> extends true ? St : T // Fallback to state if Schema
68
+ : T
69
+ : T extends { state: infer S }
70
+ ? IsSchema<S> extends true ? S : T
71
+ : T;
72
+
73
+ /**
74
+ * Infer the state type from T, or use explicit S if provided.
36
75
  *
37
- * Uses `~state` phantom property to distinguish Room types from plain state types.
38
- * This prevents incorrectly extracting `state` from a state type that happens to have a `state` property.
76
+ * Supports multiple usage patterns:
77
+ * - Room<MyState>: T is a Schema type, return as-is
78
+ * - Room<MyRoom>: T is a Room instance, extract from ~state or state property
79
+ * - Room<typeof MyRoom>: T is a constructor, instantiate first then extract
80
+ * - Room<T, ExplicitState>: S overrides all inference
39
81
  */
40
82
  export type InferState<T, S> = [S] extends [never]
41
- ? (T extends abstract new (...args: any) => { '~state': infer ST }
42
- ? ST // Constructor type (typeof MyRoom): extract state via ~state phantom
43
- : T extends { '~state': infer ST }
44
- ? ST // Instance type (MyRoom): extract state via ~state phantom
45
- : T) // State type or other: return as-is
83
+ ? Instantiate<T> extends infer I
84
+ ? IsSchema<I> extends true
85
+ ? I // It's a Schema, return as-is
86
+ : ExtractStateFromRoom<I>
87
+ : never
46
88
  : S;
47
89
 
48
90
  /**
@@ -50,31 +92,19 @@ export type InferState<T, S> = [S] extends [never]
50
92
  * otherwise returns any (plain state type). This ensures Room<State> is equivalent
51
93
  * to Room<any, State> when State doesn't have ~state.
52
94
  */
53
- export type NormalizeRoomType<T> = T extends abstract new (...args: any) => { '~state': any }
54
- ? T // Constructor type with ~state: keep as-is
55
- : T extends { '~state': any }
56
- ? T // Instance type with ~state: keep as-is
57
- : any; // Plain state type: normalize to any
95
+ export type NormalizeRoomType<T> = Instantiate<T> extends { '~state': any } ? T : any;
58
96
 
59
97
  /**
60
98
  * Extract room messages type from a Room constructor or instance type.
61
99
  * Supports both constructor types (typeof MyRoom) and instance types (MyRoom)
62
100
  */
63
- export type ExtractRoomMessages<T> = T extends abstract new (...args: any) => { messages: infer M }
64
- ? M
65
- : T extends { messages: infer M }
66
- ? M
67
- : {};
101
+ export type ExtractRoomMessages<T> = Instantiate<T> extends { messages: infer M } ? M : {};
68
102
 
69
103
  /**
70
104
  * Extract client-side messages type from a Room constructor or instance type.
71
105
  * These are messages that the server can send to the client.
72
106
  */
73
- export type ExtractRoomClientMessages<T> = T extends abstract new (...args: any) => { '~client': { '~messages': infer M } }
74
- ? M
75
- : T extends { '~client': { '~messages': infer M } }
76
- ? M
77
- : {};
107
+ export type ExtractRoomClientMessages<T> = Instantiate<T> extends { '~client': { '~messages': infer M } } ? M : {};
78
108
 
79
109
  /**
80
110
  * Message handler with automatic type inference from format schema.