@s2script/sdk 0.25.0 → 0.26.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.
package/http.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /** Async calls reject with AsyncQueueFull or AsyncPayloadTooLarge before acceptance. Responses exceeding the configured retained UTF-8/header limit reject with HttpResponseTooLarge. */
1
2
  /** @s2script/http — async HTTP. NO runtime code (injected as __s2pkg_http). */
2
3
 
3
4
  /** Options for {@link fetch}. */
package/interfaces.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { HookResultValue } from "./events";
1
2
  /**
2
3
  * @s2script/interfaces — author-time type stubs for typed inter-plugin interfaces.
3
4
  * NO runtime code: the engine injects the implementation at load time.
@@ -18,3 +19,122 @@ export interface PublishHandle {
18
19
  */
19
20
  emit(event: string, payload: unknown): void;
20
21
  }
22
+
23
+ /** Type-only protocol 2 descriptor. Payloads must belong to the supported wire algebra. */
24
+ export interface Notification<P> {
25
+ readonly __notificationPayload: P;
26
+ }
27
+ /** Synchronous decision forward. Changed does not imply payload mutation. */
28
+ export interface Hook<P> {
29
+ readonly __hookPayload: P;
30
+ }
31
+ /** Synchronous shallow transformation; only W fields may be patched. */
32
+ export interface Transform<P extends object, W extends keyof P> {
33
+ readonly __transformPayload: P;
34
+ readonly __transformWritable: W;
35
+ }
36
+ export type TransformResponse<P, W extends keyof P> =
37
+ | { result: 1; patch?: Partial<Pick<P, W>> }
38
+ | { result: Exclude<HookResultValue, 1>; patch?: never };
39
+ /** CLI-generated association; an authored augmentation cannot authorize a build. */
40
+ export interface InterfaceContracts {}
41
+ export type ContractMethods<C> = C extends { methods: infer M extends object }
42
+ ? M
43
+ : never;
44
+ export type ContractForwards<C> = C extends { forwards: infer F } ? F : never;
45
+ export type NotificationPayload<D> = D extends Notification<infer P>
46
+ ? P
47
+ : never;
48
+ export type ForwardPayload<D> = D extends Notification<infer P>
49
+ ? P
50
+ : D extends Hook<infer P>
51
+ ? P
52
+ : D extends Transform<infer P, infer W>
53
+ ? P
54
+ : never;
55
+ export type ForwardResponse<D> = D extends Notification<infer P>
56
+ ? void
57
+ : D extends Hook<infer P>
58
+ ? HookResultValue
59
+ : D extends Transform<infer P, infer W>
60
+ ? TransformResponse<P, W>
61
+ : never;
62
+ type ForwardNames<C, Mode> = {
63
+ [K in keyof ContractForwards<C>]: ContractForwards<C>[K] extends Mode
64
+ ? K
65
+ : never;
66
+ }[keyof ContractForwards<C>] &
67
+ string;
68
+ export type DispatchResponse<D> = D extends Hook<unknown>
69
+ ? HookResultValue
70
+ : D extends { readonly __transformPayload: object }
71
+ ? { result: HookResultValue; payload: ForwardPayload<D> }
72
+ : never;
73
+ export type TypedPublishHandle<C> = {
74
+ emit<K extends ForwardNames<C, Notification<unknown>>>(
75
+ event: K,
76
+ payload: ForwardPayload<ContractForwards<C>[K]>
77
+ ): void;
78
+ dispatch<
79
+ K extends ForwardNames<
80
+ C,
81
+ Hook<unknown> | { readonly __transformPayload: object }
82
+ >
83
+ >(
84
+ event: K,
85
+ payload: ForwardPayload<ContractForwards<C>[K]>
86
+ ): DispatchResponse<ContractForwards<C>[K]>;
87
+ };
88
+ type ExtraPatchKeys<Patch, W> = Patch extends unknown
89
+ ? Exclude<keyof Patch, W>
90
+ : never;
91
+ type ExtraResponseKeys<R, W> = R extends unknown
92
+ ?
93
+ | Exclude<keyof R, "result" | "patch">
94
+ | (R extends { patch?: infer Patch } ? ExtraPatchKeys<Patch, W> : never)
95
+ : never;
96
+ type ExactForwardHandler<D, R> = D extends Transform<infer P, infer W>
97
+ ? ExtraResponseKeys<R, W> extends never
98
+ ? unknown
99
+ : never
100
+ : unknown;
101
+ /** Provider forward names mapped to handlers with their exact payload and result types. */
102
+ export type ForwardHandlers<C> = {
103
+ [K in keyof ContractForwards<C>]?: (
104
+ payload: ForwardPayload<ContractForwards<C>[K]>
105
+ ) => ForwardResponse<ContractForwards<C>[K]>;
106
+ };
107
+ /** Rejects keys and transform response fields outside the selected provider contract. */
108
+ export type ExactForwardHandlers<
109
+ C,
110
+ H extends ForwardHandlers<C>
111
+ > = H & {
112
+ [K in keyof H]: K extends keyof ContractForwards<C>
113
+ ? NonNullable<H[K]> extends (...args: never[]) => unknown
114
+ ? ExactForwardHandler<
115
+ ContractForwards<C>[K],
116
+ ReturnType<Extract<H[K], (...args: never[]) => unknown>>
117
+ >
118
+ : never
119
+ : never;
120
+ } & {
121
+ [K in Exclude<keyof H, keyof ContractForwards<C>>]: never;
122
+ };
123
+ export type TypedInterfaceHandle<C> = ContractMethods<C> & {
124
+ on<
125
+ K extends keyof ContractForwards<C> & string,
126
+ H extends (
127
+ payload: ForwardPayload<ContractForwards<C>[K]>
128
+ ) => ForwardResponse<ContractForwards<C>[K]>
129
+ >(
130
+ event: K,
131
+ handler: H & ExactForwardHandler<ContractForwards<C>[K], ReturnType<H>>
132
+ ): Subscription;
133
+ };
134
+
135
+ /** A consumer-owned registration. Repeated disposal is a no-op. */
136
+ export interface Subscription { dispose(): void; }
137
+ /** Resources owned by one synchronous optional-provider attachment. */
138
+ export interface AttachmentScope {
139
+ own<T extends { dispose(): void }>(resource: T): T;
140
+ }
package/menu.d.ts CHANGED
@@ -80,8 +80,12 @@ export declare class Menu {
80
80
  display(slot: number, seconds?: number): void;
81
81
  /** Close an open menu for `slot` early. */
82
82
  close(slot: number): void;
83
- /** Register a display backend under a MenuStyle value (used by the CS2 center renderer). */
84
- static registerRenderer(name: string, renderer: MenuRenderer): void;
83
+ /**
84
+ * Register a display backend under a MenuStyle value (used by the CS2 center renderer).
85
+ * Returns the previous renderer, or undefined if none was registered, so a replacement can
86
+ * delegate to the previous backend when its own display is unavailable.
87
+ */
88
+ static registerRenderer(name: string, renderer: MenuRenderer): MenuRenderer | undefined;
85
89
  }
86
90
 
87
91
  /** A live display of one menu to one slot, passed to a {@link MenuRenderer}. Owns the page/cursor state and exposes the input verbs a renderer drives. */
package/net.d.ts CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  /** A connected TCP client socket — a per-plugin handle over a copied byte stream (no live socket crosses to JS). */
4
4
  export interface TcpSocket {
5
- /** Send bytes; a `string` is sent as its UTF-8 encoding. */
6
- send(data: Uint8Array | string): void;
5
+ /** Send bytes; a `string` is sent as its UTF-8 encoding. Returns false on closed/oversized/full admission. */
6
+ send(data: Uint8Array | string): boolean;
7
7
  /** Register a handler invoked for each inbound chunk of bytes. */
8
8
  onData(handler: (bytes: Uint8Array) => void): void;
9
9
  /** Register a handler for the peer closing the connection. */
@@ -15,8 +15,8 @@ export interface TcpSocket {
15
15
  }
16
16
  /** A bound UDP socket — a per-plugin handle for connectionless datagrams. */
17
17
  export interface UdpSocket {
18
- /** Send a datagram to `host:port`; a `string` is sent as its UTF-8 encoding. */
19
- sendTo(host: string, port: number, data: Uint8Array | string): void;
18
+ /** Send a datagram to `host:port`; a `string` is sent as its UTF-8 encoding. Returns false on closed/oversized/full admission. */
19
+ sendTo(host: string, port: number, data: Uint8Array | string): boolean;
20
20
  /** Register a handler invoked for each inbound datagram; `from` is the sender's address. */
21
21
  onMessage(handler: (from: { host: string; port: number }, bytes: Uint8Array) => void): void;
22
22
  /** Close the socket. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@s2script/sdk",
3
- "version": "0.25.0",
3
+ "version": "0.26.0",
4
4
  "license": "MIT OR Apache-2.0",
5
5
  "description": "s2script SDK — the builtin capability types (imported as @s2script/sdk/entity, …) plus the `s2s` CLI for building Source 2 / CS2 plugins.",
6
6
  "type": "module",
package/plugin.d.ts CHANGED
@@ -1,3 +1,13 @@
1
+ import type {
2
+ InterfaceContracts,
3
+ ContractMethods,
4
+ TypedPublishHandle,
5
+ TypedInterfaceHandle,
6
+ Subscription,
7
+ AttachmentScope,
8
+ ForwardHandlers,
9
+ ExactForwardHandlers,
10
+ } from "./interfaces";
1
11
  /**
2
12
  * @s2script/sdk/plugin — load-window authoring (`hook`, `publish`, `translations`, `Scope`)
3
13
  * and the internal load-scoped {@link PluginContext} types. NO runtime code: the engine injects
@@ -52,20 +62,44 @@ export interface CtxClients {
52
62
  /** A client's persisted cookies finished loading and are now readable. */
53
63
  onCookiesCached(handler: (client: Client) => void): void;
54
64
  /** A client sent chat: return a {@link HookResultValue} to suppress it. @param teamonly - team-channel say. */
55
- onSay(handler: (slot: number, text: string, teamonly: boolean) => HookResultValue | void): void;
65
+ onSay(
66
+ handler: (
67
+ slot: number,
68
+ text: string,
69
+ teamonly: boolean
70
+ ) => HookResultValue | void
71
+ ): void;
56
72
  /** Per-tick usercmd hook (SM `OnPlayerRunCmd`): read/modify {@link UserCmdView}; return `Handled` to block the tick. */
57
- onRunCmd(handler: (cmd: UserCmdView, info: { slot: number }) => HookResultValue | void): void;
73
+ onRunCmd(
74
+ handler: (
75
+ cmd: UserCmdView,
76
+ info: { slot: number }
77
+ ) => HookResultValue | void
78
+ ): void;
58
79
  }
59
80
  /** Entity lifecycle / I/O subscriptions on this plugin's load-scope ({@link PluginContext.entities}). */
60
81
  export interface CtxEntities {
61
82
  /** An entity of `className` was created (not yet spawned). @param className - match, or `"*"` for all. */
62
- onCreate(className: string, handler: (entity: EntityRef | null, className: string) => void): void;
83
+ onCreate(
84
+ className: string,
85
+ handler: (entity: EntityRef | null, className: string) => void
86
+ ): void;
63
87
  /** An entity of `className` spawned (post-`DispatchSpawn`). */
64
- onSpawn(className: string, handler: (entity: EntityRef | null, className: string) => void): void;
88
+ onSpawn(
89
+ className: string,
90
+ handler: (entity: EntityRef | null, className: string) => void
91
+ ): void;
65
92
  /** An entity of `className` is being deleted; the ref goes stale right after. */
66
- onDelete(className: string, handler: (entity: EntityRef | null, className: string) => void): void;
93
+ onDelete(
94
+ className: string,
95
+ handler: (entity: EntityRef | null, className: string) => void
96
+ ): void;
67
97
  /** Hook a named entity output (`FireOutputInternal`); return a {@link HookResultValue} to suppress it. */
68
- onOutput(classname: string, output: string, handler: (ev: OutputEvent) => HookResultValue | void): void;
98
+ onOutput(
99
+ classname: string,
100
+ output: string,
101
+ handler: (ev: OutputEvent) => HookResultValue | void
102
+ ): void;
69
103
  }
70
104
  /** Per-frame + map/precache hooks on this plugin's load-scope ({@link PluginContext.server}). */
71
105
  export interface CtxServer {
@@ -79,7 +113,10 @@ export interface CtxServer {
79
113
  */
80
114
  onGameFrame(
81
115
  fn: () => void,
82
- opts?: { priority?: "high" | "normal" | "low" | "monitor"; phase?: "pre" | "post" },
116
+ opts?: {
117
+ priority?: "high" | "normal" | "low" | "monitor";
118
+ phase?: "pre" | "post";
119
+ }
83
120
  ): void;
84
121
  /** A new map became live; `mapName` is the BSP name. */
85
122
  onMapStart(handler: (mapName: string) => void): void;
@@ -103,7 +140,7 @@ export interface CtxCommands {
103
140
  */
104
141
  onClientCommand(
105
142
  name: string,
106
- handler: (slot: number, argString: string) => HookResultValue | void,
143
+ handler: (slot: number, argString: string) => HookResultValue | void
107
144
  ): void;
108
145
  }
109
146
  /**
@@ -147,7 +184,7 @@ export interface CtxTopMenu {
147
184
  /** A producer-backed inter-plugin interface: its methods, plus forward subscriptions. */
148
185
  export type InterfaceHandle<T extends object> = T & {
149
186
  /** Subscribe to a producer forward. Load-window only (buffered, armed at Active) — like every registration. */
150
- on(event: string, handler: (payload: any) => void): void;
187
+ on(event: string, handler: (payload: any) => void): Subscription;
151
188
  };
152
189
 
153
190
  /**
@@ -198,11 +235,41 @@ export interface PluginContext {
198
235
  /** TopMenu (adminmenu) contribution ({@link CtxTopMenu}). */
199
236
  readonly topmenu: CtxTopMenu;
200
237
  /** Publish this plugin's manifest-declared interface. Buffered; goes live at Active. */
201
- publish<T extends object>(name: string, impl: T): PublishHandle;
238
+ publish<N extends keyof InterfaceContracts>(
239
+ name: N,
240
+ impl: ContractMethods<InterfaceContracts[N]>
241
+ ): TypedPublishHandle<InterfaceContracts[N]>;
242
+ publish<T extends object>(
243
+ name: keyof InterfaceContracts extends never ? string : never,
244
+ impl: T
245
+ ): PublishHandle;
202
246
  /** Resolve a HARD dep (must be in `pluginDependencies`). Immediate — the proxy is callable during OnPluginStart. */
203
- use<T extends object>(name: string): InterfaceHandle<T>;
247
+ use<N extends keyof InterfaceContracts>(
248
+ name: N
249
+ ): TypedInterfaceHandle<InterfaceContracts[N]>;
250
+ use<T extends object>(
251
+ name: keyof InterfaceContracts extends never ? string : never
252
+ ): InterfaceHandle<T>;
204
253
  /** Resolve an OPTIONAL dep (must be in `optionalPluginDependencies`); null while unpublished. */
205
- tryUse<T extends object>(name: string): InterfaceHandle<T> | null;
254
+ tryUse<N extends keyof InterfaceContracts>(
255
+ name: N
256
+ ): TypedInterfaceHandle<InterfaceContracts[N]> | null;
257
+ tryUse<T extends object>(
258
+ name: keyof InterfaceContracts extends never ? string : never
259
+ ): InterfaceHandle<T> | null;
260
+ /** Observe a declared optional provider, after both plugins are Active. */
261
+ watchOptional<N extends keyof InterfaceContracts>(
262
+ name: N,
263
+ attach: (service: TypedInterfaceHandle<InterfaceContracts[N]>, scope: AttachmentScope) => void
264
+ ): Subscription;
265
+ /** Bind a provider-qualified map of forward names to local handlers. */
266
+ bindForwards<
267
+ N extends keyof InterfaceContracts,
268
+ H extends ForwardHandlers<InterfaceContracts[N]>
269
+ >(
270
+ name: N,
271
+ handlers: ExactForwardHandlers<InterfaceContracts[N], H>
272
+ ): Subscription;
206
273
  /** Allocate a disposable subscription scope (load-window only — the capability originates at load). */
207
274
  createScope(): Scope;
208
275
  }
@@ -254,7 +321,7 @@ export declare const hook: {
254
321
  export declare function onOutput(
255
322
  classname: string,
256
323
  output: string,
257
- handler: (ev: OutputEvent) => HookResultValue | void,
324
+ handler: (ev: OutputEvent) => HookResultValue | void
258
325
  ): void;
259
326
 
260
327
  /**
@@ -277,18 +344,35 @@ export declare function createScope(): Scope;
277
344
  * Publish this plugin's manifest-declared interface. Load-window only (buffered, armed at Active).
278
345
  * Same contract as {@link PluginContext.publish}.
279
346
  */
280
- export declare function publish<T extends object>(name: string, impl: T): PublishHandle;
347
+ export declare function publish<N extends keyof InterfaceContracts>(
348
+ name: N,
349
+ impl: ContractMethods<InterfaceContracts[N]>
350
+ ): TypedPublishHandle<InterfaceContracts[N]>;
351
+ export declare function publish<T extends object>(
352
+ name: keyof InterfaceContracts extends never ? string : never,
353
+ impl: T
354
+ ): PublishHandle;
281
355
  /**
282
356
  * Resolve a HARD dep (must be in `pluginDependencies`). Load-window only.
283
357
  * Same contract as {@link PluginContext.use}. Prefer `import { greet } from "@demo/greeter"` for
284
358
  * the producer-as-import form; `use()` remains the explicit load-window form and the optional-dep path.
285
359
  */
286
- export declare function use<T extends object>(name: string): InterfaceHandle<T>;
360
+ export declare function use<N extends keyof InterfaceContracts>(
361
+ name: N
362
+ ): TypedInterfaceHandle<InterfaceContracts[N]>;
363
+ export declare function use<T extends object>(
364
+ name: keyof InterfaceContracts extends never ? string : never
365
+ ): InterfaceHandle<T>;
287
366
  /**
288
367
  * Resolve an OPTIONAL dep (must be in `optionalPluginDependencies`); null while unpublished.
289
368
  * Load-window only. Same contract as {@link PluginContext.tryUse}.
290
369
  */
291
- export declare function tryUse<T extends object>(name: string): InterfaceHandle<T> | null;
370
+ export declare function tryUse<N extends keyof InterfaceContracts>(
371
+ name: N
372
+ ): TypedInterfaceHandle<InterfaceContracts[N]> | null;
373
+ export declare function tryUse<T extends object>(
374
+ name: keyof InterfaceContracts extends never ? string : never
375
+ ): InterfaceHandle<T> | null;
292
376
 
293
377
  /**
294
378
  * Load-window TopMenu contribution. Same contract as {@link PluginContext.topmenu}.
@@ -302,3 +386,26 @@ export declare const topmenu: CtxTopMenu;
302
386
  * the same way they collect `ctx.translations.load(...)`.
303
387
  */
304
388
  export declare const translations: CtxTranslations;
389
+
390
+ /**
391
+ * Declare an optional-provider watch during load. Requires optionalPluginDependencies.
392
+ * Attachment runs synchronously after both plugins are Active, once per compatible
393
+ * provider generation. Failed attachments roll back; reappearance creates a fresh scope.
394
+ * Only the supplied service permits forward registration during the attachment callback.
395
+ */
396
+ export declare function watchOptional<N extends keyof InterfaceContracts>(
397
+ name: N,
398
+ attach: (service: TypedInterfaceHandle<InterfaceContracts[N]>, scope: AttachmentScope) => void
399
+ ): Subscription;
400
+
401
+ /**
402
+ * Bind provider forward names to local functions during the normal load window.
403
+ * The returned subscription disposes every registration created by this call.
404
+ */
405
+ export declare function bindForwards<
406
+ N extends keyof InterfaceContracts,
407
+ H extends ForwardHandlers<InterfaceContracts[N]>
408
+ >(
409
+ name: N,
410
+ handlers: ExactForwardHandlers<InterfaceContracts[N], H>
411
+ ): Subscription;
package/timers.d.ts CHANGED
@@ -5,21 +5,22 @@
5
5
 
6
6
  /**
7
7
  * Await a delay of `ms` milliseconds before continuing. Tick-integrated (resumes on a game frame).
8
+ * Rejects with `AsyncQueueFull` at the global or owning plugin timer limit.
8
9
  * @example
9
10
  * import { delay } from "@s2script/sdk/timers";
10
11
  * // plugins/funcommands/src/plugin.ts:77 — sm_freeze auto-unfreeze after `secs`
11
12
  * delay(secs * 1000).then(() => { const q = Player.fromSlot(slot); if (q && q.pawn) q.pawn.moveType = WALK; });
12
13
  */
13
14
  export declare function delay(ms: number): Promise<void>;
14
- /** Yield to the next microtick. */
15
+ /** Yield to the next microtick. Rejects with `AsyncQueueFull` at the timer admission limit. */
15
16
  export declare function nextTick(): Promise<void>;
16
- /** Yield until the next game frame. */
17
+ /** Yield until the next game frame. Rejects with `AsyncQueueFull` at the timer admission limit. */
17
18
  export declare function nextFrame(): Promise<void>;
18
19
  /**
19
- * Block the current thread (fiber) for `ms` milliseconds.
20
- * Only valid inside a threadSleep-capable fiber context.
20
+ * Sleep on a worker thread; the returned promise settles on a later game frame.
21
+ * Rejects with `AsyncQueueFull` when outstanding jobs or the worker queue are full.
21
22
  */
22
- export declare function threadSleep(ms: number): void;
23
+ export declare function threadSleep(ms: number): Promise<void>;
23
24
 
24
25
  /**
25
26
  * A live callback timer, returned by {@link after} and {@link every}. Ledgered against the creating
@@ -35,6 +36,7 @@ export interface Timer {
35
36
 
36
37
  /**
37
38
  * Run `fn` once after `ms` milliseconds (SourceMod `CreateTimer` without `TIMER_REPEAT`).
39
+ * Throws an error named `AsyncQueueFull` if no timer slot is available.
38
40
  *
39
41
  * Prefer {@link delay} when you can `await`; use this when you need something cancellable.
40
42
  * A throwing callback is reported and contained — it never kills the frame or the timer system.
@@ -48,6 +50,7 @@ export declare function after(ms: number, fn: () => void): Timer;
48
50
 
49
51
  /**
50
52
  * Run `fn` every `ms` milliseconds until killed (SourceMod `CreateTimer` with `TIMER_REPEAT`).
53
+ * Throws an error named `AsyncQueueFull` if no timer slot is available.
51
54
  *
52
55
  * The next firing is scheduled *after* the callback returns, so a slow callback cannot pile up.
53
56
  * `ms` must be greater than 0 — a zero-interval repeat would re-arm every drain and starve the
package/ws.d.ts CHANGED
@@ -9,7 +9,8 @@ export interface WebSocket {
9
9
  /** Register a handler for a transport error; `err` is the error text. */
10
10
  onError(handler: (err: string) => void): void;
11
11
  /** Send a text frame. */
12
- send(data: string): void;
12
+ /** Returns false if closed, oversized, or the connection/global outbound budget is full. */
13
+ send(data: string): boolean;
13
14
  /** Close the connection. */
14
15
  close(): void;
15
16
  }