@defold-typescript/types 0.37.0 → 0.38.0

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.
@@ -921,10 +921,10 @@
921
921
  "model\u0000PROPERTY\u0000cursor\u0000": "cursor: number;",
922
922
  "model\u0000PROPERTY\u0000playback_rate\u0000": "playback_rate: number;",
923
923
  "model\u0000PROPERTY\u0000textureN\u0000": "textureN: Hash;",
924
- "msg\u0000FUNCTION\u0000msg.post\u0000[{\"types\":[\"hash\",\"string\",\"url\"],\"isOptional\":false},{\"types\":[\"hash\",\"string\"],\"isOptional\":false},{\"types\":[\"nil\",\"table\"],\"isOptional\":true}]->[]": "msg.post<K extends string>(receiver: SceneAddress | Url | Hash, message_id: K, message?: MessagePayload<K>): void\nmsg.post(receiver: SceneAddress | Url | Hash, message_id: Hash, message?: Record<string | number, unknown>): void",
925
- "msg\u0000FUNCTION\u0000msg.url\u0000[]->[{\"types\":[\"url\"],\"isOptional\":false}]": "msg.url(): Url\nmsg.url(urlstring: SceneAddress | undefined): Url\nmsg.url(socket: string | Hash | undefined, path: string | Hash, fragment: string | Hash): Url",
926
- "msg\u0000FUNCTION\u0000msg.url\u0000[{\"types\":[\"hash\",\"string\"],\"isOptional\":true},{\"types\":[\"hash\",\"string\"],\"isOptional\":true},{\"types\":[\"hash\",\"string\"],\"isOptional\":true}]->[{\"types\":[\"url\"],\"isOptional\":false}]": "msg.url(): Url\nmsg.url(urlstring: SceneAddress | undefined): Url\nmsg.url(socket: string | Hash | undefined, path: string | Hash, fragment: string | Hash): Url",
927
- "msg\u0000FUNCTION\u0000msg.url\u0000[{\"types\":[\"string\"],\"isOptional\":false}]->[{\"types\":[\"url\"],\"isOptional\":false}]": "msg.url(): Url\nmsg.url(urlstring: SceneAddress | undefined): Url\nmsg.url(socket: string | Hash | undefined, path: string | Hash, fragment: string | Hash): Url",
924
+ "msg\u0000FUNCTION\u0000msg.post\u0000[{\"types\":[\"hash\",\"string\",\"url\"],\"isOptional\":false},{\"types\":[\"hash\",\"string\"],\"isOptional\":false},{\"types\":[\"nil\",\"table\"],\"isOptional\":true}]->[]": "msg.post<R extends SceneAddress | Hash | Url, K extends (keyof ReceiverMessages<R> & string) | MessageId | (string & {})>(receiver: R, message_id: K, message?: K extends BuiltinMessageId ? MessagePayload<K> : K extends keyof ReceiverMessages<R> ? ReceiverMessages<R>[K] : MessagePayload<K>): void\nmsg.post(receiver: SceneAddress | Url | Hash, message_id: Hash, message?: Record<string | number, unknown>): void",
925
+ "msg\u0000FUNCTION\u0000msg.url\u0000[]->[{\"types\":[\"url\"],\"isOptional\":false}]": "msg.url(): Url\nmsg.url(urlstring: SceneAddress | undefined): Url\nmsg.url(socket: string | Hash | undefined, path: string | Hash, fragment: string | Hash): Url\nmsg.url<M extends object>(): ReceiverUrl<M>\nmsg.url<M extends object>(urlstring: SceneAddress | undefined): ReceiverUrl<M>\nmsg.url<M extends object>(socket: string | Hash | undefined, path: string | Hash, fragment: string | Hash): ReceiverUrl<M>",
926
+ "msg\u0000FUNCTION\u0000msg.url\u0000[{\"types\":[\"hash\",\"string\"],\"isOptional\":true},{\"types\":[\"hash\",\"string\"],\"isOptional\":true},{\"types\":[\"hash\",\"string\"],\"isOptional\":true}]->[{\"types\":[\"url\"],\"isOptional\":false}]": "msg.url(): Url\nmsg.url(urlstring: SceneAddress | undefined): Url\nmsg.url(socket: string | Hash | undefined, path: string | Hash, fragment: string | Hash): Url\nmsg.url<M extends object>(): ReceiverUrl<M>\nmsg.url<M extends object>(urlstring: SceneAddress | undefined): ReceiverUrl<M>\nmsg.url<M extends object>(socket: string | Hash | undefined, path: string | Hash, fragment: string | Hash): ReceiverUrl<M>",
927
+ "msg\u0000FUNCTION\u0000msg.url\u0000[{\"types\":[\"string\"],\"isOptional\":false}]->[{\"types\":[\"url\"],\"isOptional\":false}]": "msg.url(): Url\nmsg.url(urlstring: SceneAddress | undefined): Url\nmsg.url(socket: string | Hash | undefined, path: string | Hash, fragment: string | Hash): Url\nmsg.url<M extends object>(): ReceiverUrl<M>\nmsg.url<M extends object>(urlstring: SceneAddress | undefined): ReceiverUrl<M>\nmsg.url<M extends object>(socket: string | Hash | undefined, path: string | Hash, fragment: string | Hash): ReceiverUrl<M>",
928
928
  "particlefx\u0000CONSTANT\u0000particlefx.EMITTER_STATE_POSTSPAWN\u0000": "const EMITTER_STATE_POSTSPAWN: number & { readonly __brand: \"particlefx.EMITTER_STATE_POSTSPAWN\" };",
929
929
  "particlefx\u0000CONSTANT\u0000particlefx.EMITTER_STATE_PRESPAWN\u0000": "const EMITTER_STATE_PRESPAWN: number & { readonly __brand: \"particlefx.EMITTER_STATE_PRESPAWN\" };",
930
930
  "particlefx\u0000CONSTANT\u0000particlefx.EMITTER_STATE_SLEEPING\u0000": "const EMITTER_STATE_SLEEPING: number & { readonly __brand: \"particlefx.EMITTER_STATE_SLEEPING\" };",
@@ -2223,10 +2223,10 @@
2223
2223
  "model\u0000PROPERTY\u0000cursor\u0000": "cursor: number;",
2224
2224
  "model\u0000PROPERTY\u0000playback_rate\u0000": "playback_rate: number;",
2225
2225
  "model\u0000PROPERTY\u0000textureN\u0000": "textureN: Hash;",
2226
- "msg\u0000FUNCTION\u0000msg.post\u0000[{\"types\":[\"hash\",\"string\",\"url\"],\"isOptional\":false},{\"types\":[\"hash\",\"string\"],\"isOptional\":false},{\"types\":[\"nil\",\"table\"],\"isOptional\":true}]->[]": "msg.post<K extends string>(receiver: SceneAddress | Url | Hash, message_id: K, message?: MessagePayload<K>): void\nmsg.post(receiver: SceneAddress | Url | Hash, message_id: Hash, message?: Record<string | number, unknown>): void",
2227
- "msg\u0000FUNCTION\u0000msg.url\u0000[]->[{\"types\":[\"url\"],\"isOptional\":false}]": "msg.url(): Url\nmsg.url(urlstring: SceneAddress | undefined): Url\nmsg.url(socket: string | Hash | undefined, path: string | Hash, fragment: string | Hash): Url",
2228
- "msg\u0000FUNCTION\u0000msg.url\u0000[{\"types\":[\"hash\",\"string\"],\"isOptional\":true},{\"types\":[\"hash\",\"string\"],\"isOptional\":true},{\"types\":[\"hash\",\"string\"],\"isOptional\":true}]->[{\"types\":[\"url\"],\"isOptional\":false}]": "msg.url(): Url\nmsg.url(urlstring: SceneAddress | undefined): Url\nmsg.url(socket: string | Hash | undefined, path: string | Hash, fragment: string | Hash): Url",
2229
- "msg\u0000FUNCTION\u0000msg.url\u0000[{\"types\":[\"string\"],\"isOptional\":false}]->[{\"types\":[\"url\"],\"isOptional\":false}]": "msg.url(): Url\nmsg.url(urlstring: SceneAddress | undefined): Url\nmsg.url(socket: string | Hash | undefined, path: string | Hash, fragment: string | Hash): Url",
2226
+ "msg\u0000FUNCTION\u0000msg.post\u0000[{\"types\":[\"hash\",\"string\",\"url\"],\"isOptional\":false},{\"types\":[\"hash\",\"string\"],\"isOptional\":false},{\"types\":[\"nil\",\"table\"],\"isOptional\":true}]->[]": "msg.post<R extends SceneAddress | Hash | Url, K extends (keyof ReceiverMessages<R> & string) | MessageId | (string & {})>(receiver: R, message_id: K, message?: K extends BuiltinMessageId ? MessagePayload<K> : K extends keyof ReceiverMessages<R> ? ReceiverMessages<R>[K] : MessagePayload<K>): void\nmsg.post(receiver: SceneAddress | Url | Hash, message_id: Hash, message?: Record<string | number, unknown>): void",
2227
+ "msg\u0000FUNCTION\u0000msg.url\u0000[]->[{\"types\":[\"url\"],\"isOptional\":false}]": "msg.url(): Url\nmsg.url(urlstring: SceneAddress | undefined): Url\nmsg.url(socket: string | Hash | undefined, path: string | Hash, fragment: string | Hash): Url\nmsg.url<M extends object>(): ReceiverUrl<M>\nmsg.url<M extends object>(urlstring: SceneAddress | undefined): ReceiverUrl<M>\nmsg.url<M extends object>(socket: string | Hash | undefined, path: string | Hash, fragment: string | Hash): ReceiverUrl<M>",
2228
+ "msg\u0000FUNCTION\u0000msg.url\u0000[{\"types\":[\"hash\",\"string\"],\"isOptional\":true},{\"types\":[\"hash\",\"string\"],\"isOptional\":true},{\"types\":[\"hash\",\"string\"],\"isOptional\":true}]->[{\"types\":[\"url\"],\"isOptional\":false}]": "msg.url(): Url\nmsg.url(urlstring: SceneAddress | undefined): Url\nmsg.url(socket: string | Hash | undefined, path: string | Hash, fragment: string | Hash): Url\nmsg.url<M extends object>(): ReceiverUrl<M>\nmsg.url<M extends object>(urlstring: SceneAddress | undefined): ReceiverUrl<M>\nmsg.url<M extends object>(socket: string | Hash | undefined, path: string | Hash, fragment: string | Hash): ReceiverUrl<M>",
2229
+ "msg\u0000FUNCTION\u0000msg.url\u0000[{\"types\":[\"string\"],\"isOptional\":false}]->[{\"types\":[\"url\"],\"isOptional\":false}]": "msg.url(): Url\nmsg.url(urlstring: SceneAddress | undefined): Url\nmsg.url(socket: string | Hash | undefined, path: string | Hash, fragment: string | Hash): Url\nmsg.url<M extends object>(): ReceiverUrl<M>\nmsg.url<M extends object>(urlstring: SceneAddress | undefined): ReceiverUrl<M>\nmsg.url<M extends object>(socket: string | Hash | undefined, path: string | Hash, fragment: string | Hash): ReceiverUrl<M>",
2230
2230
  "particlefx\u0000CONSTANT\u0000particlefx.EMITTER_STATE_POSTSPAWN\u0000": "const EMITTER_STATE_POSTSPAWN: number & { readonly __brand: \"particlefx.EMITTER_STATE_POSTSPAWN\" };",
2231
2231
  "particlefx\u0000CONSTANT\u0000particlefx.EMITTER_STATE_PRESPAWN\u0000": "const EMITTER_STATE_PRESPAWN: number & { readonly __brand: \"particlefx.EMITTER_STATE_PRESPAWN\" };",
2232
2232
  "particlefx\u0000CONSTANT\u0000particlefx.EMITTER_STATE_SLEEPING\u0000": "const EMITTER_STATE_SLEEPING: number & { readonly __brand: \"particlefx.EMITTER_STATE_SLEEPING\" };",
@@ -3227,10 +3227,10 @@
3227
3227
  "model\u0000PROPERTY\u0000material\u0000": "material: Hash;",
3228
3228
  "model\u0000PROPERTY\u0000playback_rate\u0000": "playback_rate: number;",
3229
3229
  "model\u0000PROPERTY\u0000textureN\u0000": "textureN: Hash;",
3230
- "msg\u0000FUNCTION\u0000msg.post\u0000[{\"types\":[\"hash\",\"string\",\"url\"],\"isOptional\":false},{\"types\":[\"hash\",\"string\"],\"isOptional\":false},{\"types\":[\"nil\",\"table\"],\"isOptional\":true}]->[]": "msg.post<K extends string>(receiver: SceneAddress | Url | Hash, message_id: K, message?: MessagePayload<K>): void\nmsg.post(receiver: SceneAddress | Url | Hash, message_id: Hash, message?: Record<string | number, unknown>): void",
3231
- "msg\u0000FUNCTION\u0000msg.url\u0000[]->[{\"types\":[\"url\"],\"isOptional\":false}]": "msg.url(): Url\nmsg.url(urlstring: SceneAddress | undefined): Url\nmsg.url(socket: string | Hash | undefined, path: string | Hash, fragment: string | Hash): Url",
3232
- "msg\u0000FUNCTION\u0000msg.url\u0000[{\"types\":[\"hash\",\"string\"],\"isOptional\":true},{\"types\":[\"hash\",\"string\"],\"isOptional\":true},{\"types\":[\"hash\",\"string\"],\"isOptional\":true}]->[{\"types\":[\"url\"],\"isOptional\":false}]": "msg.url(): Url\nmsg.url(urlstring: SceneAddress | undefined): Url\nmsg.url(socket: string | Hash | undefined, path: string | Hash, fragment: string | Hash): Url",
3233
- "msg\u0000FUNCTION\u0000msg.url\u0000[{\"types\":[\"string\"],\"isOptional\":false}]->[{\"types\":[\"url\"],\"isOptional\":false}]": "msg.url(): Url\nmsg.url(urlstring: SceneAddress | undefined): Url\nmsg.url(socket: string | Hash | undefined, path: string | Hash, fragment: string | Hash): Url",
3230
+ "msg\u0000FUNCTION\u0000msg.post\u0000[{\"types\":[\"hash\",\"string\",\"url\"],\"isOptional\":false},{\"types\":[\"hash\",\"string\"],\"isOptional\":false},{\"types\":[\"nil\",\"table\"],\"isOptional\":true}]->[]": "msg.post<R extends SceneAddress | Hash | Url, K extends (keyof ReceiverMessages<R> & string) | MessageId | (string & {})>(receiver: R, message_id: K, message?: K extends BuiltinMessageId ? MessagePayload<K> : K extends keyof ReceiverMessages<R> ? ReceiverMessages<R>[K] : MessagePayload<K>): void\nmsg.post(receiver: SceneAddress | Url | Hash, message_id: Hash, message?: Record<string | number, unknown>): void",
3231
+ "msg\u0000FUNCTION\u0000msg.url\u0000[]->[{\"types\":[\"url\"],\"isOptional\":false}]": "msg.url(): Url\nmsg.url(urlstring: SceneAddress | undefined): Url\nmsg.url(socket: string | Hash | undefined, path: string | Hash, fragment: string | Hash): Url\nmsg.url<M extends object>(): ReceiverUrl<M>\nmsg.url<M extends object>(urlstring: SceneAddress | undefined): ReceiverUrl<M>\nmsg.url<M extends object>(socket: string | Hash | undefined, path: string | Hash, fragment: string | Hash): ReceiverUrl<M>",
3232
+ "msg\u0000FUNCTION\u0000msg.url\u0000[{\"types\":[\"hash\",\"string\"],\"isOptional\":true},{\"types\":[\"hash\",\"string\"],\"isOptional\":true},{\"types\":[\"hash\",\"string\"],\"isOptional\":true}]->[{\"types\":[\"url\"],\"isOptional\":false}]": "msg.url(): Url\nmsg.url(urlstring: SceneAddress | undefined): Url\nmsg.url(socket: string | Hash | undefined, path: string | Hash, fragment: string | Hash): Url\nmsg.url<M extends object>(): ReceiverUrl<M>\nmsg.url<M extends object>(urlstring: SceneAddress | undefined): ReceiverUrl<M>\nmsg.url<M extends object>(socket: string | Hash | undefined, path: string | Hash, fragment: string | Hash): ReceiverUrl<M>",
3233
+ "msg\u0000FUNCTION\u0000msg.url\u0000[{\"types\":[\"string\"],\"isOptional\":false}]->[{\"types\":[\"url\"],\"isOptional\":false}]": "msg.url(): Url\nmsg.url(urlstring: SceneAddress | undefined): Url\nmsg.url(socket: string | Hash | undefined, path: string | Hash, fragment: string | Hash): Url\nmsg.url<M extends object>(): ReceiverUrl<M>\nmsg.url<M extends object>(urlstring: SceneAddress | undefined): ReceiverUrl<M>\nmsg.url<M extends object>(socket: string | Hash | undefined, path: string | Hash, fragment: string | Hash): ReceiverUrl<M>",
3234
3234
  "particlefx\u0000CONSTANT\u0000particlefx.EMITTER_STATE_POSTSPAWN\u0000": "const EMITTER_STATE_POSTSPAWN: number & { readonly __brand: \"particlefx.EMITTER_STATE_POSTSPAWN\" };",
3235
3235
  "particlefx\u0000CONSTANT\u0000particlefx.EMITTER_STATE_PRESPAWN\u0000": "const EMITTER_STATE_PRESPAWN: number & { readonly __brand: \"particlefx.EMITTER_STATE_PRESPAWN\" };",
3236
3236
  "particlefx\u0000CONSTANT\u0000particlefx.EMITTER_STATE_SLEEPING\u0000": "const EMITTER_STATE_SLEEPING: number & { readonly __brand: \"particlefx.EMITTER_STATE_SLEEPING\" };",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defold-typescript/types",
3
- "version": "0.37.0",
3
+ "version": "0.38.0",
4
4
  "description": "TypeScript types for the Defold engine's Lua APIs.",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/lifecycle.ts CHANGED
@@ -571,6 +571,14 @@ export type RenderScriptHooksWithProperties<TProps, TSelf, TInitState> = Omit<
571
571
  properties?: TProps;
572
572
  };
573
573
 
574
+ // The script-local message map an `onMessage` dispatcher carries, lifted onto
575
+ // the factory result so `ScriptMessages<typeof script>` can read it. Matched
576
+ // structurally so this module stays usable through its own `./lifecycle`
577
+ // subpath, where the ambient `MessageDispatcher` is not declared.
578
+ export type ScriptMessageChannel<TMessages> = {
579
+ on_message?: { readonly __messages?: TMessages };
580
+ };
581
+
574
582
  /**
575
583
  * Extract a script module's declared property channel (`TProps`) as a nameable
576
584
  * type. A script declares its editor properties with the value-keyed
@@ -617,9 +625,15 @@ export type ScriptPropertiesOf<T extends { properties?: object }> = NonNullable<
617
625
  * });
618
626
  * ```
619
627
  */
620
- export function defineScript<TProps extends object = Record<never, never>, TInitState = TProps>(
621
- hooks: ScriptHooksWithProperties<TProps, TProps & TInitState, TInitState>,
622
- ): ScriptHooksWithProperties<TProps, TProps & TInitState, TInitState> {
628
+ export function defineScript<
629
+ TProps extends object = Record<never, never>,
630
+ TInitState = TProps,
631
+ TMessages extends object = Record<never, never>,
632
+ >(
633
+ hooks: ScriptHooksWithProperties<TProps, TProps & TInitState, TInitState> &
634
+ ScriptMessageChannel<TMessages>,
635
+ ): ScriptHooksWithProperties<TProps, TProps & TInitState, TInitState> &
636
+ ScriptMessageChannel<TMessages> {
623
637
  return hooks;
624
638
  }
625
639
 
@@ -652,9 +666,15 @@ export function defineScript<TProps extends object = Record<never, never>, TInit
652
666
  * });
653
667
  * ```
654
668
  */
655
- export function defineGuiScript<TProps extends object = Record<never, never>, TInitState = TProps>(
656
- hooks: GuiScriptHooksWithProperties<TProps, TProps & TInitState, TInitState>,
657
- ): GuiScriptHooksWithProperties<TProps, TProps & TInitState, TInitState> {
669
+ export function defineGuiScript<
670
+ TProps extends object = Record<never, never>,
671
+ TInitState = TProps,
672
+ TMessages extends object = Record<never, never>,
673
+ >(
674
+ hooks: GuiScriptHooksWithProperties<TProps, TProps & TInitState, TInitState> &
675
+ ScriptMessageChannel<TMessages>,
676
+ ): GuiScriptHooksWithProperties<TProps, TProps & TInitState, TInitState> &
677
+ ScriptMessageChannel<TMessages> {
658
678
  return hooks;
659
679
  }
660
680
 
@@ -689,8 +709,11 @@ export function defineGuiScript<TProps extends object = Record<never, never>, TI
689
709
  export function defineRenderScript<
690
710
  TProps extends object = Record<never, never>,
691
711
  TInitState = TProps,
712
+ TMessages extends object = Record<never, never>,
692
713
  >(
693
- hooks: RenderScriptHooksWithProperties<TProps, TProps & TInitState, TInitState>,
694
- ): RenderScriptHooksWithProperties<TProps, TProps & TInitState, TInitState> {
714
+ hooks: RenderScriptHooksWithProperties<TProps, TProps & TInitState, TInitState> &
715
+ ScriptMessageChannel<TMessages>,
716
+ ): RenderScriptHooksWithProperties<TProps, TProps & TInitState, TInitState> &
717
+ ScriptMessageChannel<TMessages> {
695
718
  return hooks;
696
719
  }
@@ -3,8 +3,9 @@ import type { Hash, Url } from "./core-types";
3
3
 
4
4
  declare global {
5
5
  // DU-style dispatcher built on `isMessage`'s receive-side narrowing
6
- // (message-guard.d.ts): each handler key is a `BuiltinMessageId` and its
7
- // `message` param narrows to that id's `BuiltinMessages` payload. It returns
6
+ // (message-guard.d.ts): each catalog handler key is a `MessageId` and its
7
+ // `message` param narrows to that id's `MessagePayload`; any other key is a
8
+ // script-local id declared by its annotated `message`. It returns
8
9
  // an `on_message` handler so it reads as `on_message: onMessage<Self>({ ... })`;
9
10
  // `self` threads via the explicit type argument, mirroring `defineScript<Self>`.
10
11
  // The transpiler lowers the call to a flat `message_id == hash("...")`
@@ -18,7 +19,11 @@ declare global {
18
19
  * explicit type argument, mirroring `defineScript<Self>`. The transpiler lowers
19
20
  * it to a flat `if/elseif message_id == hash("...")` chain.
20
21
  *
21
- * @param handlers - a partial map from builtin message id to its handler; each handler's `message` is narrowed to that id's payload.
22
+ * A key outside the catalogs declares a script-local message when its handler
23
+ * annotates `message`; the returned dispatcher carries those ids so
24
+ * {@link ScriptMessages} can hand them to senders.
25
+ *
26
+ * @param handlers - a partial map from message id to its handler; a catalog id's `message` is narrowed to that id's payload, and an annotated handler under any other key declares a script-local id.
22
27
  * @returns the `on_message` lifecycle handler that dispatches to the matching entry.
23
28
  * @example
24
29
  * ```ts
@@ -27,18 +32,55 @@ declare global {
27
32
  * contact_point_response(self, message, sender) {
28
33
  * print(message.other_group);
29
34
  * },
35
+ * spawn_wave(self, message: { count: number }) {
36
+ * print(message.count);
37
+ * },
30
38
  * }),
31
39
  * });
32
40
  * ```
33
41
  */
34
- function onMessage<TSelf = Record<never, never>>(
35
- handlers: Partial<{
36
- [K in MessageId]: (self: TSelf, message: MessagePayload<K>, sender: Url) => void;
37
- }>,
38
- ): (
39
- self: TSelf,
40
- message_id: Hash,
41
- message: Record<string | number, unknown>,
42
- sender: Url,
43
- ) => void;
42
+ function onMessage<TSelf = Record<never, never>, H extends object = Record<never, never>>(
43
+ handlers: H &
44
+ Partial<{
45
+ [K in MessageId]: (self: TSelf, message: MessagePayload<K>, sender: Url) => void;
46
+ }> &
47
+ OnMessageLocalHandlers<TSelf, H>,
48
+ ): MessageDispatcher<TSelf, OnMessageLocalMessages<H>>;
49
+
50
+ // A key outside `MessageId` is a script-local id only when its handler
51
+ // annotates `message`; an unannotated one infers no payload and reds as an
52
+ // implicit `any`, which is the typo guard.
53
+ type OnMessageHandlerPayload<F> = F extends (
54
+ self: never,
55
+ message: infer M,
56
+ ...rest: never[]
57
+ ) => unknown
58
+ ? M
59
+ : never;
60
+
61
+ type OnMessageLocalHandlers<TSelf, H> = {
62
+ [K in Exclude<keyof H, MessageId>]: (
63
+ self: TSelf,
64
+ message: OnMessageHandlerPayload<H[K]>,
65
+ sender: Url,
66
+ ) => void;
67
+ };
68
+
69
+ // A numeric key is sent under its JavaScript property key (`1e3` is "1000"),
70
+ // the id the transpiler hashes.
71
+ type OnMessageLocalMessages<H> = {
72
+ [K in Exclude<keyof H, MessageId> as K extends number ? `${K}` : K]: OnMessageHandlerPayload<
73
+ H[K]
74
+ >;
75
+ };
76
+
77
+ /**
78
+ * The `on_message` handler {@link onMessage} returns. `__messages` is a
79
+ * type-only record of the script-local ids its handlers declared; it never
80
+ * exists at runtime.
81
+ */
82
+ interface MessageDispatcher<TSelf, TLocal = Record<never, never>> {
83
+ (self: TSelf, message_id: Hash, message: Record<string | number, unknown>, sender: Url): void;
84
+ readonly __messages?: TLocal;
85
+ }
44
86
  }
@@ -2,6 +2,44 @@
2
2
  import type { Hash, Url } from "./core-types";
3
3
 
4
4
  declare global {
5
+ /**
6
+ * A `Url` that also names the messages its receiver declares. `__receives` is
7
+ * type-only and never exists at runtime; the value is an ordinary `Url`.
8
+ */
9
+ type ReceiverUrl<M> = Url & { readonly __receives?: M };
10
+
11
+ /**
12
+ * The script-local messages a script declares by handling them: each
13
+ * annotated `onMessage` handler key outside the built-in and `CustomMessages`
14
+ * catalogs, mapped to its payload. Read it off the script module's default
15
+ * export and pass it to `msg.url<M>` to get a receiver-typed address.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * type WaveMessages = ScriptMessages<typeof import("./wave").default>;
20
+ * ```
21
+ */
22
+ type ScriptMessages<T> = T extends { on_message?: infer D }
23
+ ? NonNullable<D> extends { readonly __messages?: infer L }
24
+ ? L extends object
25
+ ? L
26
+ : Record<never, never>
27
+ : Record<never, never>
28
+ : Record<never, never>;
29
+
30
+ /**
31
+ * The messages a `msg.post` receiver declares: the `M` of a
32
+ * {@link ReceiverUrl}, or the handled messages of the script a scene address
33
+ * names in `SceneComponentAddresses`. Empty for every other receiver.
34
+ */
35
+ type ReceiverMessages<R> = R extends { readonly __receives?: infer M }
36
+ ? M extends object
37
+ ? M
38
+ : Record<never, never>
39
+ : R extends keyof SceneComponentAddresses
40
+ ? ScriptMessages<SceneComponentAddresses[R]>
41
+ : Record<never, never>;
42
+
5
43
  namespace msg {
6
44
  /**
7
45
  * Post a message to a receiving URL. The most common case is to send messages
@@ -25,11 +63,31 @@ declare global {
25
63
  * position: go.get_world_position(),
26
64
  * });
27
65
  * ```
66
+ *
67
+ * @remarks
68
+ * The `(string & {})` term keeps every declared message id as a completion
69
+ * while still accepting any other string, so no existing call is rejected.
70
+ *
71
+ * A receiver from `msg.url<M>` also offers and checks the ids in `M`, and a
72
+ * scene address whose generated value is a script module offers and checks
73
+ * that script's handled messages. A built-in id keeps its built-in payload,
74
+ * and any other id stays open.
75
+ *
76
+ * Both receiver routes live in this one signature on purpose: a separate,
77
+ * stricter overload ahead of it would let a mismatched payload fall through
78
+ * to the open one and compile anyway.
28
79
  */
29
- function post<K extends string>(
30
- receiver: SceneAddress | Url | Hash,
80
+ function post<
81
+ R extends SceneAddress | Hash | Url,
82
+ K extends (keyof ReceiverMessages<R> & string) | MessageId | (string & {}),
83
+ >(
84
+ receiver: R,
31
85
  message_id: K,
32
- message?: MessagePayload<K>,
86
+ message?: K extends BuiltinMessageId
87
+ ? MessagePayload<K>
88
+ : K extends keyof ReceiverMessages<R>
89
+ ? ReceiverMessages<R>[K]
90
+ : MessagePayload<K>,
33
91
  ): void;
34
92
  function post(
35
93
  receiver: SceneAddress | Url | Hash,
@@ -60,6 +118,11 @@ declare global {
60
118
  * component (`msg.url("#main")`). The `socket:` prefix only crosses
61
119
  * into a collection-proxy-loaded world.
62
120
  *
121
+ * Pass the receiver's message map as `M` (for example
122
+ * `msg.url<ScriptMessages<typeof import("./wave").default>>("/logic#wave")`)
123
+ * to get a receiver-typed address: `msg.post` then completes and checks
124
+ * those ids. The value is still an ordinary `Url`.
125
+ *
63
126
  * @example
64
127
  * ```ts
65
128
  * // No-arg, then take the current game object's URL.
@@ -91,5 +154,12 @@ declare global {
91
154
  path: string | Hash,
92
155
  fragment: string | Hash,
93
156
  ): Url;
157
+ function url<M extends object>(): ReceiverUrl<M>;
158
+ function url<M extends object>(urlstring: SceneAddress | undefined): ReceiverUrl<M>;
159
+ function url<M extends object>(
160
+ socket: string | Hash | undefined,
161
+ path: string | Hash,
162
+ fragment: string | Hash,
163
+ ): ReceiverUrl<M>;
94
164
  }
95
165
  }