npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@devnovaa-id/sambung-kata

v2.0.0

Published

Library universal untuk bermain game Sambung Kata (word chain) menggunakan API dari devnova.icu. Mendukung Node.js, browser, React, dan serverless.

Readme

🎮 @devnovaa-id/sambung-kata

npm version npm downloads License: MIT Node.js Version

Library untuk bermain Sambung Kata menggunakan API dari devnova.icu
Dapat digunakan di Node.js, React, browser, dan serverless (Vercel, Netlify, Cloudflare Workers, dll).

📖 Dokumentasi🚀 Quick Start💻 Contoh📚 API🤝 Kontribusi


✨ Fitur

  • 🚀 Zero dependencies – Menggunakan native fetch, ringan dan cepat
  • 📦 Universal – Bekerja di Node.js 18+, browser modern, dan lingkungan serverless
  • 🔷 TypeScript first – Mendukung type definitions secara penuh
  • 🔄 ESM & CommonJS – Support kedua format module
  • 🛡️ Error handling – Exception yang jelas dan mudah ditangani
  • 🎯 Sederhana – API yang intuitif dan mudah digunakan
  • 🌍 Dukungan bahasa – Pilih bahasa Indonesia (id) atau Inggris (en)

🚀 Quick Start

Instalasi

npm install @devnovaa-id/sambung-kata

Atau dengan yarn:

yarn add @devnovaa-id/sambung-kata

Penggunaan Dasar

import { SambungKataClient } from '@devnovaa-id/sambung-kata';

const client = new SambungKataClient();

// Mulai game (default bahasa Indonesia)
const game = await client.startGame();
console.log(game.data.currentWord); // "kilat"

// Mulai game dalam bahasa Inggris
const englishGame = await client.startGame({ lang: 'en' });
console.log(englishGame.data.currentWord); // "nature"

// Jawab kata
const answer = await client.submitAnswer(game.data.gameId, 'atap');
console.log(answer.data.totalMoves); // 1

// Akhiri game
await client.endGame(game.data.gameId);

💻 Contoh Lengkap

Node.js (ESM)

import { SambungKataClient } from '@devnovaa-id/sambung-kata';

async function playGame() {
  const client = new SambungKataClient();

  try {
    // 1. Start game dalam bahasa Inggris
    const start = await client.startGame({ lang: 'en' });
    console.log('Game ID:', start.data.gameId);
    console.log('Kata pertama:', start.data.currentWord);
    console.log('Petunjuk:', start.data.message);

    let gameId = start.data.gameId;

    // 2. Jawab dengan kata yang valid
    const jawaban = 'retry';
    const answer = await client.submitAnswer(gameId, jawaban);
    console.log('Jawaban diterima! Total langkah:', answer.data.totalMoves);
    console.log('Kata saat ini:', answer.data.currentWord);

    // 3. Lanjutkan game (refresh state)
    const continueGame = await client.continueGame(gameId);
    console.log('State terbaru:', continueGame.data.currentWord);

    // 4. Akhiri game
    const end = await client.endGame(gameId);
    console.log(end.message);
  } catch (error) {
    console.error('Error:', error.message);
  }
}

playGame();

React Component

import { useEffect, useState } from 'react';
import { SambungKataClient } from '@devnovaa-id/sambung-kata';

function SambungKataGame() {
  const [game, setGame] = useState(null);
  const [answer, setAnswer] = useState('');
  const [loading, setLoading] = useState(false);
  const [lang, setLang] = useState('id');
  const client = new SambungKataClient();

  useEffect(() => {
    startNewGame();
  }, [lang]);

  const startNewGame = async () => {
    setLoading(true);
    try {
      const res = await client.startGame({ lang });
      setGame(res.data);
    } catch (err) {
      console.error(err);
    } finally {
      setLoading(false);
    }
  };

  const submitAnswer = async () => {
    if (!answer.trim()) return;
    setLoading(true);
    try {
      const res = await client.submitAnswer(game.gameId, answer);
      setGame(res.data);
      setAnswer('');
    } catch (err) {
      alert(err.message);
    } finally {
      setLoading(false);
    }
  };

  const endGame = async () => {
    setLoading(true);
    try {
      await client.endGame(game.gameId);
      setGame(null);
    } catch (err) {
      alert(err.message);
    } finally {
      setLoading(false);
    }
  };

  if (!game) {
    return (
      <div>
        <select value={lang} onChange={(e) => setLang(e.target.value)}>
          <option value="id">Bahasa Indonesia</option>
          <option value="en">English</option>
        </select>
        <button onClick={startNewGame}>Mulai Game</button>
      </div>
    );
  }

  return (
    <div style={{ padding: '20px' }}>
      <h2>🎯 Sambung Kata</h2>
      <p>Kata saat ini: <strong>{game.currentWord}</strong></p>
      <p>Total langkah: {game.totalMoves}</p>
      <p>{game.message}</p>
      <input
        type="text"
        value={answer}
        onChange={(e) => setAnswer(e.target.value)}
        placeholder="Masukkan kata..."
        disabled={loading}
      />
      <button onClick={submitAnswer} disabled={loading}>
        Kirim
      </button>
      <button onClick={endGame} disabled={loading}>
        Akhiri Game
      </button>
    </div>
  );
}

Browser (HTML + ESM)

<!DOCTYPE html>
<html>
<head>
  <title>Sambung Kata Demo</title>
</head>
<body>
  <select id="langSelect">
    <option value="id">Bahasa Indonesia</option>
    <option value="en">English</option>
  </select>
  <div id="game"></div>
  <script type="module">
    import { SambungKataClient } from 'https://unpkg.com/@devnovaa-id/[email protected]/dist/index.js';

    const client = new SambungKataClient();
    const gameDiv = document.getElementById('game');
    const langSelect = document.getElementById('langSelect');

    async function start() {
      const lang = langSelect.value;
      const game = await client.startGame({ lang });
      gameDiv.innerHTML = `
        <h2>Game Dimulai! (Bahasa: ${game.data.language})</h2>
        <p>Kata: ${game.data.currentWord}</p>
        <p>${game.data.message}</p>
        <input id="answer" placeholder="Jawaban..." />
        <button id="submit">Kirim</button>
        <button id="end">Akhiri</button>
      `;
      document.getElementById('submit').onclick = async () => {
        const answer = document.getElementById('answer').value;
        const res = await client.submitAnswer(game.data.gameId, answer);
        alert(res.data.message);
      };
      document.getElementById('end').onclick = async () => {
        await client.endGame(game.data.gameId);
        gameDiv.innerHTML = '<p>Game selesai.</p>';
      };
    }
    start();
    langSelect.addEventListener('change', start);
  </script>
</body>
</html>

Serverless (Vercel)

// api/sambung-kata.js
import { SambungKataClient } from '@devnovaa-id/sambung-kata';

export default async function handler(req, res) {
  const client = new SambungKataClient();
  const lang = req.query.lang || 'id';
  const game = await client.startGame({ lang });
  res.status(200).json(game.data);
}

📚 API

| Method | Deskripsi | Parameters | Returns | |--------|-----------|------------|---------| | startGame(options) | Memulai game baru | options?: { lang?: 'id' | 'en' } | Promise<GameResponse> | | submitAnswer(gameId, answer) | Mengirim jawaban | gameId: string, answer: string | Promise<GameResponse> | | continueGame(gameId) | Mendapatkan state game | gameId: string | Promise<GameResponse> | | endGame(gameId) | Mengakhiri game | gameId: string | Promise<GameResponse> |

Tipe GameResponse

interface GameResponse {
  success: boolean;
  data?: {
    gameId: string;
    currentWord: string;
    usedWords: string[];
    totalMoves: number;
    language: string;  // 'id' atau 'en'
    message: string;
  };
  error?: string;
  message?: string;
  gameId?: string;
}

🛠️ Error Handling

Semua error dilempar sebagai ApiError yang mewarisi SambungKataError.

import { SambungKataClient, ApiError } from '@devnovaa-id/sambung-kata';

try {
  await client.submitAnswer('invalid-id', 'kata');
} catch (error) {
  if (error instanceof ApiError) {
    console.error(`Status: ${error.statusCode}, Pesan: ${error.message}`);
  }
}

🤝 Kontribusi

Kami sangat menerima kontribusi! Silakan buka issue atau pull request di GitHub.

Development

git clone https://github.com/devnovaa-id/sambung-kata.git
cd sambung-kata
npm install
npm run build
npm run type-check

Menjalankan Contoh

# Node.js
cd examples/node
npm start

# Browser (jalankan server statis)
npx serve .
# Buka http://localhost:3000/examples/browser

📄 Lisensi

MIT © 2026 devnovaa-id