Skip to content

fix(typings): correct Client method emit; type the full API; infer implement args from define schemas - #2

Open
Loner1536 wants to merge 6 commits into
KYRORBLX:mainfrom
Loner1536:typings-fixes-and-inference
Open

fix(typings): correct Client method emit; type the full API; infer implement args from define schemas#2
Loner1536 wants to merge 6 commits into
KYRORBLX:mainfrom
Loner1536:typings-fixes-and-inference

Conversation

@Loner1536

@Loner1536 Loner1536 commented Jul 6, 2026

Copy link
Copy Markdown

Types-only PR — no .luau changes and no runtime behavior changes. Rewrites typings/index.d.ts to match the Luau source and adds schema→callback inference for roblox-ts consumers.

Fix

  • Client methods emitted as dot calls. Controller's members are colon-methods (function Controller:show()), but the typings declared them as arrow properties, so roblox-ts emitted client.show() — no self — and every method on a Konsole.create() client failed at runtime from TS. Client now uses method signatures, and the top-level Api no longer extends it, since the top-level functions are dot-style in init.luau and must keep emitting Konsole.show().

Additions (all backward compatible)

  • Schema→callback inference. define captures the args schema and returns a Command whose .server is a branded string (ServerName<A>; plain string at runtime). Passing it to implement types the callback parameters automatically:

    const giveXp = Konsole.define({
        name: "giveXp",
        server: "giveXpServer",
        args: [
            { name: "target", type: "player", required: true },
            { name: "amount", type: "number", required: true },
        ],
    });
    
    Konsole.implement(giveXp.server, (context, player, amount) => {
        // player: Player, amount: number — no annotations or casts
    });

    implement("name", cb) with a plain string still works via overload.

  • Optional-arg truthfulness. required: false with no default types as | undefined, matching Arguments.parse (which skips such args); args with a default stay non-optional (defaults are inserted unconverted), and an absent required behaves as required.

  • Full API surface typed from source: Result (ok/err/table/status/fromRenderResult per result.luau), Ranks (incl. RankEntity = Player | userId and the built-in rank-name union), Dispatch, Kommand, Arguments (incl. the Arguments.types converter registry), Chat, Render, and every Config group so create() overrides autocomplete.

  • Editor completions inside args. define's type parameter now defaults to its constraint instead of [] — the empty-tuple default gave partially-typed arg objects a never contextual type, so editors showed no property completions.

Verification

  • Signatures traced module-by-module against src/ (dispatch, kommand, arguments, ranks, config, result, controller).
  • Typechecked in a roblox-ts project (TS 5.5), including tsserver completion probes and tuple-optionality assertions.
  • const type parameters require TS ≥ 5.0 (roblox-ts already requires newer).

Notes

  • host's implementations record uses Run<Array<any>>unknown[] would reject typed callbacks under contravariance.
  • ExecuteResult is Outcome | RenderResult; commands returning arbitrary tables still pass through at runtime.
  • Render.Formatter / Render.Text are left unknown (UI internals).

Disclosure

The main thing I wanted fixed was auto-typing for implement — in the upstream typings, define returns a plain Command with no arg capture, implement takes (name: string, callback: Run) where Run is (context, ...unknown[]), and all the sub-modules (Ranks, Dispatch, Context.dispatch, etc.) are typed unknown. So there was no inference at all: you'd define a command with a typed args schema and still have to manually annotate every callback parameter.

The upstream Client interface also has every member as an arrow property (dot-call style), but the Luau controller uses colon-methods, so all Konsole.create() client calls were emitting client.show() with no self and failing at runtime.

I didn't have time to audit the full typings rewrite myself, so I ran it through Claude (Fable 5), which traced each declaration against the Luau source module-by-module. I've reviewed the result and it's been tested in-game, but a second pair of eyes on the API surface wouldn't hurt.

🤖 Generated with Claude Code

@dullflowerr dullflowerr self-assigned this Jul 6, 2026
@dullflowerr

Copy link
Copy Markdown
Member

after reviewing, please fix the RunArgs helper first. In a roblox-ts TS 5.5 test with @rbxts/types + @rbxts/compiler-types and skipLibCheck=false, typings/index.d.ts errors at RunArgs: Type '"length"' cannot be used to index type 'T', after that is fixed, i'm good with merging

@dullflowerr dullflowerr added bug Something isn't working invalid This doesn't seem right labels Jul 6, 2026
@Loner1536

Copy link
Copy Markdown
Author

Rebased onto v0.1.9 HF 1 and synced the typings with the latest config refactor (removed stackHistoryMaxHeight, hintHeight, and bottomInset from ConfigGroups.panel to match PanelGroup). PR is up to date with main.

@Loner1536

Copy link
Copy Markdown
Author

Pushed fixes for all four issues (989f412):

  1. Client nominal brand — added readonly _nominal_Client: unique symbol, so const c: Konsole.Client = Konsole no longer compiles and roblox-ts can't emit colon calls against the dot API.
  2. Branded names bypassing implement — the loose overload now rejects ServerNames (name: N & ([N] extends [ServerName] ? never : unknown)), and the schema overload wraps the callback args in NoInfer so the branded name alone determines the tuple — a callback annotated (ctx, s: string) against a number schema is now a compile error.
  3. Widened schemasRunArgs falls back to ReadonlyArray<any> instead of [] when the schema isn't a fixed tuple, so existing define calls with a separately-declared Argument[] and a run callback taking args typecheck again.
  4. panel.hintHeight — restored, since the runtime still defines and uses it (stackHistoryMaxHeight/bottomInset stay removed to match PanelGroup).

Verified all four with tsc --strict against @rbxts/types/@rbxts/compiler-types: valid usage compiles, and each unsound case above is asserted as a compile error via @ts-expect-error.

@Loner1536

Copy link
Copy Markdown
Author

Wrote a full type-test suite against the typings (every module: define/implement/host/run, Client, Ranks, Dispatch, Kommand, Arguments, Chat, Result/Render, Config — with the unsound cases from your review pinned as @ts-expect-error) and audited the typings against the v0.1.9 Luau source. Found one more instance of your issue #2: Dispatch.implement was still typed loosely, so Konsole.Dispatch.implement(cmd.server, (ctx, s: string) => ...) compiled against a number schema — same runtime mistype, one level down (Konsole.implement just delegates to it). Fixed in acf97ca by sharing one Implement overload pair between Api.implement and DispatchModule.implement. Everything else checked out against the runtime (Client colon-method list matches Controller, parse's backtracking/default semantics match ArgumentRuntimeValue, config groups match config.luau).

@dullflowerr

Copy link
Copy Markdown
Member

I'll recheck this soon, please update to the latest version though

Loner1536 and others added 5 commits July 23, 2026 17:39
…mplement args from define schemas

Fixes:
- Client members are colon-methods in Luau (Controller:show, etc.) but were
  typed as arrow properties, so roblox-ts emitted client.show() dot calls
  with no self. Client is now declared with method signatures, and the
  top-level Api (dot-functions in Luau) no longer extends it.
- commands.aliases config is a { [string]: string } map, was typed string[].

Additions (types only, no runtime changes):
- Konsole.define captures the args schema and returns a Command whose
  .server is a branded string; passing it to Konsole.implement types the
  callback parameters automatically (no casts or annotations needed).
  Plain-string implement(name, cb) still works via overload.
- required: false args with no default type as | undefined, matching
  Arguments.parse, which skips them; defaults are inserted unconverted.
- Typed Result (ok/err/table/status/from -> RenderResult), Ranks, Dispatch,
  Kommand, Arguments (incl. the converter registry), Chat, Render, and the
  full Config groups so create()/config overrides autocomplete.
- rank fields accept built-in rank names (player/helper/moderator/admin/
  creator + aliases) alongside numbers.
- define's type parameter defaults to its constraint instead of [] so
  editors show Argument property completions inside partially-typed args
  (an empty-tuple default left completions with a never contextual type).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- brand Client with a nominal marker so the dot-call Api is no longer
  structurally assignable (prevents roblox-ts emitting colon calls
  against dot functions, which shifted args at runtime)
- reject branded ServerNames in the loose implement overload so an
  annotated callback can't bypass the schema-inferred tuple; also block
  inference from the callback (NoInfer) so the branded name alone
  determines the arg types
- RunArgs falls back to ReadonlyArray<any> for widened Argument[]
  schemas instead of [] (run callbacks with args compile again)
- restore panel.hintHeight (runtime still defines and uses it)
Konsole.implement delegates to Dispatch.implement at runtime, but the
Dispatch module's typing still accepted a branded ServerName with any
callback annotation. Both now share one Implement overload pair.
@Loner1536
Loner1536 force-pushed the typings-fixes-and-inference branch from acf97ca to c633312 Compare July 23, 2026 22:42
@Loner1536

Copy link
Copy Markdown
Author

Rebased onto v0.1.10. Merged in the new PanelPosition type and panel.position config field, kept RanksModule.set/setRank accepting number | RankName (superset of the new number | string), and verified the rest of the runtime changes (dispatch/result/controller) don't touch the typed surface. PR is up to date with main.

…ever-hole on player/players

Konsole never mutates these after handing them back out - Command.args/aliases,
Rank.aliases, KommandModule.schemas(), and the default config's
input.activationKeys/commands.aliases are all live references into internal
tables, so exposing them as mutable arrays let consumers silently corrupt
shared state. Also caught the same "suggestions on player/players" gap
Finchasaurus flagged on the Argument union in KYRORBLX#5 - those variants never read
.suggestions, so it should be excluded there too, not just on string/number/boolean.
@Loner1536

Copy link
Copy Markdown
Author

Went back through every .luau file to double-check the typings against actual runtime behavior, since #5 caught a real gap (suggestions readonly). Found the same pattern in a few more places — pushed a follow-up:

  • suggestions?: never on the player/players argument variants too, not just string/number/boolean (same reasoning as Change suggestions to readonly arrays in index.d.ts #5)
  • Command.args/aliases, Rank.aliases, and KommandModule.schemas() are all live references into Konsole's internal tables, never cloned before being handed back — now readonly
  • ConfigGroups.input.activationKeys and commands.aliases are the same story, since Config.merge only shallow-clones with table.clone

Sanity-checked with a small tsc pass (@rbxts/types + @rbxts/compiler-types, strict, skipLibCheck off) asserting the golden-path define/list/config usage still compiles and that mutating any of the above is now a compile error.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working invalid This doesn't seem right

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants