Async & networking
s2script integrates Promises with the game frame. Timers and network I/O never block the main thread.
Timers
import { delay, nextTick, nextFrame, threadSleep } from '@s2script/sdk/timers';
await nextTick(); // after current microtasks
await nextFrame(); // next game frame
await delay(1000); // ~1s, frame-integrated
threadSleep(50); // block inside a threadSleep-capable fiber HTTP
import { fetch } from '@s2script/sdk/http';
const res = await fetch('https://example.com/api', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ steamId }),
timeoutMs: 10_000
});
if (res.ok) {
const data = await res.json();
} HTTP status errors (4xx/5xx) resolve with ok: false (web-fetch parity — useful on the join path). Only network failures and timeouts reject. Bodies are buffered (10MB cap).
WebSocket
import { WebSocket } from '@s2script/sdk/ws';
const ws = await WebSocket.connect('wss://example.com/ws');
ws.onMessage((text) => console.log(text));
ws.onClose((code) => console.log('closed', code));
ws.send('hello');
ws.close(); Subscribe to onMessage synchronously after await connect so early frames are not missed. Connections are owner-scoped and closed on plugin unload.
Database
import { Database } from '@s2script/sdk/db';
const db = await Database.open('myplugin');
await db.execute('CREATE TABLE IF NOT EXISTS boots (id INTEGER PRIMARY KEY)');
const rows = await db.query('SELECT * FROM boots');
await db.close(); The API is Promise-based (SQLite today; MySQL/Postgres via the same Database seam). Handles are opaque, owner-scoped, and ledgered.