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

overskill-sdk

v0.5.12

Published

Official SDK for OverSkill generated apps - entity-first development

Downloads

10,630

Readme

@overskill/sdk

Official SDK for OverSkill generated apps - build data-driven applications with zero backend code.

Features

  • ✅ Entity-first development - Define entities, get instant CRUD operations
  • ✅ Zero backend code - No API routes, no database setup, no auth implementation
  • ✅ TypeScript support - Full type definitions included
  • ✅ Edge-optimized - Queries run on Cloudflare Workers globally
  • ✅ Auto-authentication - Built-in OverSkill SSO integration
  • ✅ Lightweight - Just 12 KB (gzipped)

Installation

npm install @overskill/sdk

Quick Start

import { createClient } from '@overskill/sdk'

// Create client instance
const overskill = createClient({
  appId: 'your-app-id',  // Optional
  debug: true            // Enable logging
})

// List entities
const todos = await overskill.entities.Todo.list()

// Create entity
const newTodo = await overskill.entities.Todo.create({
  title: 'Buy groceries',
  completed: false
})

// Update entity
await overskill.entities.Todo.update(newTodo.id, {
  completed: true
})

// Delete entity
await overskill.entities.Todo.delete(newTodo.id)

Entity Methods

list(orderBy?, limit?) / list(options)

List entities with optional sorting, limit, and pagination offset.

// List all todos
const todos = await overskill.entities.Todo.list()

// List 20 most recent
const recent = await overskill.entities.Todo.list('-created_at', 20)

// Oldest first
const oldest = await overskill.entities.Todo.list('created_at', 10)

// Object syntax (Prisma-style) with pagination
const page2 = await overskill.entities.Todo.list({
  orderBy: '-created_at',
  limit: 20,
  skip: 20  // or `offset: 20`
})

listPage(...) / filterPage(...) / searchPage(...)

Same requests as list / filter / search, but they return how many rows exist, not just the rows you asked for:

const { data, total, count, hasMore, offset, limit } =
  await overskill.entities.Member.listPage({ orderBy: '-join_date', limit: 50 })

// "Showing 50 of 508 members"   + a Load more button when hasMore

| field | meaning | |---|---| | data | the rows in this window | | total | rows matching the query IN TOTAL — filter- and search-aware. undefined (never 0) if the app's worker predates the envelope | | count | data.length — the size of this window, never the size of the collection | | hasMore | rows matching the query exist beyond this window | | offset / limit | the window this result describes |

Reach for these whenever the UI shows a count or a "load more". Counting the rows you happen to hold under-reports the collection the moment it outgrows one page — that was BUG-6 (#5358): a generated members directory asked for limit: 500 against 508 members, rendered 500 cards with no pagination, and computed its per-tier chips by reducing over the 500 rows it held. Every tally was short, the smallest tier had no chip at all, and 8 members existed in the database but nowhere in the UI — one of them the signed-in user's own profile.

count(filters?)

How many rows match. One request, zero rows downloaded.

await overskill.entities.Member.count()                              // 508
await overskill.entities.Member.count({ membership_tier: 'founder' }) // 127

This is the right way to build facet counts. Four count() calls are exact and cheaper than one 500-row read you then reduce over.

Returns undefined, never 0, when the worker omitted meta.total — "unknown" must stay distinguishable from "none", because 0 members printed above a full grid is a worse answer than no number at all.

list({ all: true })

Fetch every matching row, paging until the server says there are no more:

const everyone = await overskill.entities.Member.list({ orderBy: '-join_date', all: true })

limit is a request for at most n rows, so no value of it means "however many there turn out to be" — guessing a number bigger than the table is how rows get stranded. An explicit limit still wins ({ all: true, limit: 20 } is a 20-row page), so a paginated view cannot accidentally drain a table.

Prefer count() or listPage() when you only need the size; all downloads every row.

A read that asks for a bulk-sized limit (>= 100) at offset 0 and comes back short now logs one warning naming both numbers, once per entity per session. The original defect was not the row ceiling — it was that truncation was silent.

filter(filters, orderBy?, limit?) / filter(filters, options)

Filter entities by exact field values. Booleans are supported and match records stored via create({ completed: true }).

// Filter by single field
const active = await overskill.entities.Todo.filter({ completed: false })

// Multiple filters
const urgent = await overskill.entities.Todo.filter({
  completed: false,
  priority: 'high'
}, '-created_at', 10)

// Options-object syntax with pagination
const page2 = await overskill.entities.Todo.filter(
  { completed: false },
  { orderBy: '-created_at', limit: 20, skip: 20 }
)

get(id)

Get a single entity by ID.

const todo = await overskill.entities.Todo.get('abc123')

create(data)

Create a new entity.

const todo = await overskill.entities.Todo.create({
  title: 'New todo',
  completed: false,
  due_date: '2025-01-15'
})

update(id, data)

Update an existing entity.

await overskill.entities.Todo.update('abc123', {
  completed: true
})

delete(id)

Delete an entity.

await overskill.entities.Todo.delete('abc123')

bulkCreate(records)

Create multiple entities at once.

await overskill.entities.Todo.bulkCreate([
  { title: 'Todo 1', completed: false },
  { title: 'Todo 2', completed: false },
  { title: 'Todo 3', completed: false }
])

Authentication

How a sign-in lands in the app

After the platform signs a user in it redirects to https://<your-app>/callback#access_token=<jwt>. The SDK adopts that token at construction time only when all of these hold:

  • the page path is /callback;
  • the token is in the URL fragment (#access_token=). A ?access_token= query string is never a token source — it is removed from the URL and ignored;
  • the client was created with appId and the token's aud names that app;
  • the token's exp is in the future.

Anything else is refused and never stored, so a crafted link cannot sign a visitor in as someone else. The overskill default export has no appId, so it never adopts a URL token — construct your own client with createClient({ appId }) (the shipped template does).

me()

Get current authenticated user.

try {
  const user = await overskill.auth.me()
  console.log('Logged in as:', user.email)
} catch (error) {
  // Not authenticated
  overskill.auth.login()
}

login(redirectUrl?)

Redirect to OverSkill login page.

// Login and return to current page
overskill.auth.login()

// Login and redirect to specific URL
overskill.auth.login('https://myapp.com/dashboard')

loginWithRedirect(callbackUrl)

Login with redirect to full URL (must be absolute).

overskill.auth.loginWithRedirect('https://myapp.com/complete-profile')

logout()

Clear authentication and logout.

await overskill.auth.logout()

checkSession()

Check if user is authenticated (does not throw errors).

const user = await overskill.auth.checkSession()

if (user) {
  console.log('Logged in:', user.email)
} else {
  console.log('Not logged in')
}

isAuthenticated()

Check if user has a token (synchronous).

if (overskill.auth.isAuthenticated()) {
  console.log('Has token')
}

Configuration

ClientConfig Options

interface ClientConfig {
  appId?: string;         // App identifier (optional)
  apiUrl?: string;        // Custom API endpoint (defaults to current origin)
  authToken?: string;     // Pre-existing auth token
  autoLogin?: boolean;    // Auto-redirect to login on 401 (default: false)
  debug?: boolean;        // Enable debug logging (default: false)
}

Examples

Basic usage:

const overskill = createClient()

With auto-login:

const overskill = createClient({
  autoLogin: true  // Redirect to login if 401
})

With debug logging:

const overskill = createClient({
  debug: true  // See all SDK operations in console
})

With pre-existing token:

const overskill = createClient({
  authToken: 'your-jwt-token'
})

React Integration

Check Session on Mount

import { createClient } from '@overskill/sdk'
import { useEffect, useState } from 'react'

const overskill = createClient()

function App() {
  const [user, setUser] = useState(null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    // Check if user is logged in
    overskill.auth.checkSession()
      .then(user => {
        if (user) {
          setUser(user)
        } else {
          // Redirect to login
          overskill.auth.login()
        }
      })
      .finally(() => setLoading(false))
  }, [])

  if (loading) return <div>Loading...</div>

  return <div>Welcome, {user.name}!</div>
}

Entity CRUD Example

function TodoList() {
  const [todos, setTodos] = useState([])

  useEffect(() => {
    loadTodos()
  }, [])

  const loadTodos = async () => {
    const data = await overskill.entities.Todo.list('-created_at', 20)
    setTodos(data)
  }

  const handleCreate = async (title: string) => {
    const newTodo = await overskill.entities.Todo.create({
      title,
      completed: false
    })
    setTodos([newTodo, ...todos])
  }

  const handleToggle = async (id: string) => {
    const todo = todos.find(t => t.id === id)
    await overskill.entities.Todo.update(id, {
      completed: !todo.completed
    })
    setTodos(todos.map(t =>
      t.id === id ? { ...t, completed: !t.completed } : t
    ))
  }

  const handleDelete = async (id: string) => {
    await overskill.entities.Todo.delete(id)
    setTodos(todos.filter(t => t.id !== id))
  }

  return (
    <div>
      {/* UI code */}
    </div>
  )
}

Error Handling

The SDK lets errors bubble by design, so OverSkill AI can see and fix them.

try {
  const user = await overskill.auth.me()
} catch (error) {
  if (error.status === 401) {
    // Not authenticated
    overskill.auth.login()
  } else {
    // Other error - let it throw for debugging
    throw error
  }
}

Advanced Usage

Direct HTTP Client

// Make custom API calls
const response = await overskill.http.get('/api/custom/endpoint')

Manual Token Management

// Set token manually
overskill.auth.setToken('your-jwt-token')

// Get current token
const token = overskill.auth.getToken()

TypeScript

Full TypeScript support included:

import { createClient, type User, type EntityRecord } from '@overskill/sdk'

interface Todo extends EntityRecord {
  title: string;
  completed: boolean;
  due_date?: string;
}

const overskill = createClient()
const todos = await overskill.entities.Todo.list() // Type: Todo[]

Architecture

@overskill/sdk connects to OverSkill Workers running on Cloudflare edge:

Your App (React + @overskill/sdk)
         ↓
Cloudflare Worker (per-app, edge-deployed)
         ↓
Turso Database (per-app, isolated)
  • Zero backend code needed - No Express, no API routes, no DB setup
  • Edge performance - ~50ms latency globally
  • Automatic scaling - Handles any traffic volume
  • Secure by default - User data isolated automatically

License

MIT

Support

  • Documentation: https://docs.overskill.app
  • Issues: https://github.com/OverskillDev/overskill/issues
  • Email: [email protected]

Create account

Use overskill.auth.signUp(redirectUrl?) for Create account buttons and overskill.auth.login(redirectUrl?) for Log in. useAuth() exposes both helpers. Signup carries prompt=create through the app’s existing /login route and hosted OAuth flow; return paths and buyer claims use the same callback. Explicit logout still requires fresh email proof (which can create a new account). Do not copy hosted auth URLs or build password forms in generated apps.