TypeScript
KCDS runs CommonJS JavaScript, so you compile TypeScript to .js before the server starts your resource. Each
release includes declarations for the whole SDK, which give your editor every type, member and side.
Getting the declarations
KCDS-Types-<version>.zip is on the download page, next to the server. Use the version matching your server.
| File | Declares |
|---|---|
shared.d.ts | what both sides have: console, require and the shared named types |
server-api.d.ts | server scripts: the kcds object with the server's types and events |
client-api.d.ts | client scripts: the kcds object with the client's types and events |
Server and client scripts are checked against different files, so using a client type in a server file is a compile error.
Each SDK type is a value and a type at the same path, such as kcds.world.Door. A name on its own, such as Door,
is an error.
Setting up a project
resources/
hello-ts/
package.json
tsconfig.server.json
tsconfig.client.json
types/ the three .d.ts files from the zip
src/
server/main.ts
client/main.ts
dist/ compiler output, which the manifest points atpackage.json:
{
"name": "hello-ts",
"version": "1.0.0",
"scripts": {
"build": "tsc -p tsconfig.server.json && tsc -p tsconfig.client.json"
},
"devDependencies": {
"typescript": "^5.9.3"
},
"kcds": {
"serverScripts": ["dist/server/main.js"],
"clientScripts": ["dist/client/main.js"]
}
}tsconfig.server.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"lib": ["ES2022"],
"types": [],
"strict": true,
"rootDir": "src/server",
"outDir": "dist/server"
},
"files": ["types/shared.d.ts", "types/server-api.d.ts"],
"include": ["src/server/**/*.ts"]
}tsconfig.client.json is the same, with client in place of server.
Why these settings?
"lib": ["ES2022"]and"types": []leave out browser and Node declarations, so a script sees only what the SDK declares. If your server code uses Node modules, add@types/nodeand"types": ["node"]to the server project.- Two separate projects keep each side's names out of the other.
- Only
dist/client/goes to players, because the manifest sends the folder of the client entry script (see Project structure).
Writing the code
src/server/main.ts:
const { Events, Chat } = kcds.core; const { Player } = kcds.players; function welcomeLine(name: string): string { return `Welcome, ${name}!`; } Events.on('playerReady', (player: kcds.players.Player) => { Chat.sendToPlayer(player, welcomeLine(player.nickname), { author: 'Server' }); }); Events.fromClient('hello:count', (player: kcds.players.Player) => { player.emit('hello:count', { players: Player.all().length }); });
src/client/main.ts:
const { Events } = kcds.core; const { Hud, Key } = kcds.ui; interface CountAnswer { players: number; } Key.bind('f7', () => { Events.toServer('hello:count'); }); Events.on('hello:count', (data: CountAnswer) => { Hud.showNotification(`${data.players} players online`); });
Building
npm install npm run build
Then enter ensure hello-ts in the server console. Build and ensure again after each change. Running tsc --watch on both projects rebuilds as you save.
What gets checked
The compiler catches wrong names, members the SDK does not have, and types from the wrong side. Your editor also shows each member's summary and return type.
Most parameters are typed any. The SDK checks values when the script runs, and throws a TypeError or
RangeError naming the argument.
The declarations compile with strict and skipLibCheck: false. Handler parameters are typed, so none is an
implicit any:
- game and SDK events, through
KcdsTypes.ServerEventsandKcdsTypes.ClientEvents - your own event names, with
KcdsTypes.EventHandler, whose arguments areany Messages.handle, withKcdsTypes.MessageHandler, which receives the payload andreplyPropPlacer.on, withKcdsTypes.PlacementHandler, which receives a pose ornull, and whether it is validKey.bind,Web.onandActionHint.create
Event payloads contain whatever the sender put in, so describe your own with an interface, like CountAnswer
above.
Named types
Your code can also use these types from the KcdsTypes namespace:
| Type | Describes |
|---|---|
InventorySnapshot, InventoryRow | Inventory.get and player.getInventory(), and each row |
InventoryUnit, InventoryResult | { id, amount } for Inventory.remove and transfer, and the answer to any change |
InventoryChange | the change of playerInventoryChanged |
PickpocketedItem | a row of playerPickpocketed's items |
LevelEditDefinition | the argument of LevelEdit.apply |
DialoguePage, DialogueOption | what Dialogue.open and update take |
CookPotSnapshot, CookPotFood | a pot from CookPot.all or get; food is 'goulash', 'lentil', 'soup' or null |
ScreenEffectInfo | an entry of Hud.effectList() |
Appearance | a body's look |
ProgressionSnapshot | a player's skills and perks |
JoinRequest | a join request |
Deprecated members still work but are left out of the declarations. For example, a client blip's state has been
markState since 0.3.4, as on the server.