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

@scalemule/sdk

v0.0.11

Published

Official TypeScript/JavaScript SDK for ScaleMule Backend-as-a-Service

Readme

@scalemule/sdk

Official TypeScript/JavaScript SDK for ScaleMule Backend-as-a-Service.

Zero dependencies. Works in browsers, Node.js 18+, and edge runtimes.

Install

npm install @scalemule/sdk

Quick Start

import { ScaleMule } from '@scalemule/sdk'

const sm = new ScaleMule({ apiKey: 'sm_pb_xxx' })

// Register
const { data, error } = await sm.auth.register({
  email: '[email protected]',
  password: 'SecurePassword123!',
  name: 'Jane Doe',
})

if (error) {
  console.error(error.code, error.message)
} else {
  console.log('User created:', data.id)
}

Every method returns { data, error } — never throws on API errors.

Configuration

const sm = new ScaleMule({
  apiKey: 'sm_pb_xxx',               // required
  environment: 'prod',               // 'dev' | 'prod' (default: 'prod')
  baseUrl: 'https://api.example.com', // overrides environment preset
  timeout: 30000,                    // request timeout in ms
  retry: {
    maxRetries: 2,                   // retry on 429/5xx (default: 2)
    backoffMs: 300,                  // base delay with jitter (default: 300)
  },
  enableRateLimitQueue: true,        // auto-queue on 429
  enableOfflineQueue: true,          // queue when offline, sync on reconnect
  debug: false,                      // log requests to console
})

| Environment | Gateway URL | |-------------|-------------| | prod | https://api.scalemule.com | | dev | https://api-dev.scalemule.com |

Response Contract

All methods return ApiResponse<T>:

type ApiResponse<T> = {
  data: T | null       // null when error is present
  error: ApiError | null  // null on success
}

type ApiError = {
  code: string         // machine-readable (e.g., 'unauthorized')
  message: string      // human-readable
  status: number       // HTTP status code
  details?: Record<string, unknown>
}

Paginated methods return PaginatedResponse<T>:

type PaginatedResponse<T> = {
  data: T[]
  metadata: { total: number; totalPages: number; page: number; perPage: number }
  error: ApiError | null
}

Auth

// Register & Login
const { data } = await sm.auth.register({ email, password, name })
const { data } = await sm.auth.login({ email, password })
await sm.auth.logout()

// Current user
const { data: user } = await sm.auth.me()

// Session management
await sm.auth.refreshSession()
sm.setAccessToken(token)
sm.clearAccessToken()

// Password
await sm.auth.forgotPassword({ email })
await sm.auth.resetPassword({ token, newPassword })
await sm.auth.changePassword({ currentPassword, newPassword })

// Email verification
await sm.auth.verifyEmail({ token })
await sm.auth.resendVerification()

// Phone OTP
await sm.auth.sendPhoneOtp({ phone, purpose: 'verify_phone' })
await sm.auth.verifyPhoneOtp({ phone, code })
await sm.auth.loginWithPhone({ phone, code })
// SDK auto-sanitizes formatting before send:
// "(415) 555-1234" -> "+4155551234"

// OAuth
const { data } = await sm.auth.getOAuthUrl('google', { redirectUri })
const { data } = await sm.auth.handleOAuthCallback({ provider, code, state })

// Sessions, Devices, Login History
const { data } = await sm.auth.sessions.list()
await sm.auth.sessions.revoke(sessionId)
const { data } = await sm.auth.devices.list()
const { data } = await sm.auth.loginHistory.list()

// MFA
const { data } = await sm.auth.mfa.getStatus()
const { data } = await sm.auth.mfa.setupTotp()
await sm.auth.mfa.verifySetup({ code })
await sm.auth.mfa.disable({ password })

Phone Country Picker Helpers

import { PHONE_COUNTRIES, composePhoneNumber, normalizeAndValidatePhone } from '@scalemule/sdk'

const us = PHONE_COUNTRIES.find((country) => country.code === 'US')
const phone = composePhoneNumber(us?.dialCode ?? '+1', '(415) 555-1234')
const normalized = normalizeAndValidatePhone(phone)

if (!normalized.valid) {
  console.error(normalized.error)
} else {
  await sm.auth.register({ email, password, phone: normalized.normalized! })
}

Storage

// Upload (3-step presigned URL flow, hidden from you)
const { data, error } = await sm.storage.upload(file, {
  filename: 'photo.jpg',
  isPublic: false,
  onProgress: (pct) => console.log(`${pct}%`),
  signal: abortController.signal,
})

// Signed URLs for viewing/downloading
const { data } = await sm.storage.getViewUrl(fileId)
const { data: urls } = await sm.storage.getViewUrls(fileIds)  // batch, up to 100
const { data } = await sm.storage.getDownloadUrl(fileId)

// File operations
const { data: info } = await sm.storage.getInfo(fileId)
const { data: files, metadata } = await sm.storage.list({ page: 1, perPage: 20 })
await sm.storage.delete(fileId)

// Split upload (server-side: get URL → client uploads to S3 → complete)
const { data: upload } = await sm.storage.getUploadUrl('photo.jpg', 'image/jpeg')
// ... client uploads to upload.upload_url ...
const { data: result } = await sm.storage.completeUpload(upload.file_id, upload.completion_token)

Data

// CRUD
const { data: doc } = await sm.data.create('todos', { title: 'Ship SDK', done: false })
const { data: doc } = await sm.data.get('todos', docId)
await sm.data.update('todos', docId, { done: true })
await sm.data.delete('todos', docId)

// Query with filters and sorting
const { data: docs, metadata } = await sm.data.query('todos', {
  filters: [{ operator: 'eq', field: 'done', value: false }],
  sort: [{ field: 'created_at', direction: 'desc' }],
  page: 1,
  perPage: 20,
})

// Collections
await sm.data.createCollection('todos')
const { data } = await sm.data.listCollections()

// My documents (filtered by current user)
const { data } = await sm.data.myDocuments('todos')

// Aggregations
const { data } = await sm.data.aggregate('orders', {
  pipeline: [{ $group: { field: 'status', fn: 'count' } }],
})

Filter operators: eq, ne, gt, gte, lt, lte, in, contains

Video

const { data } = await sm.video.upload(videoFile, {
  onProgress: (pct) => console.log(`${pct}%`),
})
const { data } = await sm.video.get(videoId)
const { data } = await sm.video.getStreamUrl(videoId)
await sm.video.trackPlayback(videoId, { event: 'play', position: 0 })

Realtime

// Subscribe (auto-connects WebSocket on first call)
const unsub = sm.realtime.subscribe('chat:room-1', (msg) => {
  console.log('Message:', msg)
})

// Publish
await sm.realtime.publish('chat:room-1', { text: 'hello' })

// Connection status
console.log(sm.realtime.status) // 'disconnected' | 'connecting' | 'connected' | 'reconnecting'

// Cleanup
unsub()
sm.realtime.disconnect()

Features: auto-reconnect with backoff, re-subscribe on reconnect, heartbeat/ping-pong.

Chat

const { data: convo } = await sm.chat.createConversation({ participantIds: [a, b] })
const { data: msg } = await sm.chat.sendMessage(convo.id, { content: 'Hello!' })
const { data: msgs } = await sm.chat.getMessages(convo.id, { limit: 50 })
await sm.chat.sendTyping(convo.id)
await sm.chat.markRead(convo.id)

Social

await sm.social.follow(userId)
await sm.social.unfollow(userId)
const { data: post } = await sm.social.createPost({ content: 'Hello!', visibility: 'public' })
const { data: feed } = await sm.social.getFeed({ limit: 20 })
await sm.social.like('post', postId)
const { data } = await sm.social.getComments(postId)

Teams

const { data: team } = await sm.teams.create({ name: 'Engineering' })
await sm.teams.invite(team.id, { email: '[email protected]', role: 'member' })
await sm.teams.acceptInvitation(token)
const { data: members } = await sm.teams.listMembers(team.id)

Billing

const { data: customer } = await sm.billing.createCustomer({ email, name })
const { data: sub } = await sm.billing.subscribe({ customerId, planId })
await sm.billing.reportUsage({ metric: 'api_calls', quantity: 1000 })
const { data: invoices } = await sm.billing.listInvoices()

Analytics

await sm.analytics.track('button_clicked', { buttonId: 'signup' })
await sm.analytics.trackPageView({ path: '/pricing' })
await sm.analytics.trackBatch([
  { event: 'page_view', properties: { path: '/home' } },
  { event: 'page_view', properties: { path: '/pricing' } },
])

More Services

// Queue
await sm.queue.enqueue({ job_type: 'email.welcome', payload: { userId }, priority: 'high' })

// Scheduler
const { data } = await sm.scheduler.createJob({
  name: 'daily-report', cron: '0 9 * * *',
  type: 'webhook', config: { url: 'https://myapp.com/api/report' },
})
await sm.scheduler.pauseJob(jobId)
await sm.scheduler.runNow(jobId)

// Permissions
await sm.permissions.createRole({ name: 'editor' })
await sm.permissions.assignPermissions(roleId, ['posts.create', 'posts.edit'])
const { data: { allowed } } = await sm.permissions.check(userId, 'posts.create')

// Communication
await sm.communication.sendEmail({ to: '[email protected]', subject: 'Welcome!', body: '<h1>Hi</h1>' })
await sm.communication.sendEmailTemplate('welcome', { to: email, variables: { name: 'Jane' } })

// Search
await sm.search.index('products', { id: '1', name: 'Widget', price: 9.99 })
const { data } = await sm.search.query('products', { query: 'widget', limit: 10 })

// Cache
await sm.cache.set('key', { value: 'data' }, { ttl: 3600 })
const { data } = await sm.cache.get('key')

// Graph
const { data: node } = await sm.graph.createNode({ label: 'person', properties: { name: 'Alice' } })
const { data: edge } = await sm.graph.createEdge({ fromId: a, toId: b, type: 'knows' })

// Content Moderation
await sm.flagContent.createFlag({ content_type: 'post', content_id: postId, category: 'spam' })

// Webhooks
await sm.webhooks.create({ url: 'https://myapp.com/hooks', events: ['auth.user.created'] })

// Leaderboard
await sm.leaderboard.submitScore(boardId, { userId, score: 1500 })

// Listings
const { data } = await sm.listings.nearby({ lat: 40.7, lng: -74.0, radius: 10 })

// Events
const { data: event } = await sm.events.create({ title: 'Launch Party', startDate: '2026-03-01' })

// Functions
const { data } = await sm.functions.invoke('resize', { imageUrl: '...', width: 200 })

// Photo
const { data } = await sm.photo.upload(imageFile)

// Compliance
await sm.compliance.log({ action: 'user.deleted', resourceType: 'user', resourceId: id })

// Orchestrator
const { data } = await sm.orchestrator.execute(workflowId, { userId })

All 30 Services

| Service | Property | Description | |---------|----------|-------------| | Auth | sm.auth | Authentication, sessions, MFA, OAuth, phone | | Storage | sm.storage | File upload (presigned S3), signed URLs | | Realtime | sm.realtime | WebSocket pub/sub with auto-reconnect | | Video | sm.video | Video upload, streaming, analytics | | Data | sm.data | Document CRUD, queries, aggregations | | Chat | sm.chat | Conversations, messages, typing, read receipts | | Social | sm.social | Follow, posts, feed, likes, comments | | Billing | sm.billing | Customers, subscriptions, invoices, usage | | Analytics | sm.analytics | Event tracking, page views, funnels | | Communication | sm.communication | Email, SMS, push notifications | | Scheduler | sm.scheduler | Cron jobs, one-time jobs | | Permissions | sm.permissions | Roles, permissions, RBAC | | Teams | sm.teams | Team management, invitations, SSO | | Accounts | sm.accounts | Client/application management | | Identity | sm.identity | API key management | | Cache | sm.cache | Redis key-value cache | | Queue | sm.queue | Async job processing | | Search | sm.search | Full-text search | | Webhooks | sm.webhooks | Webhook management | | Graph | sm.graph | Graph database (nodes, edges, traversal) | | Functions | sm.functions | Serverless function execution | | Listings | sm.listings | Marketplace listings | | Events | sm.events | Event management, registration | | Leaderboard | sm.leaderboard | Gamification leaderboards | | Photo | sm.photo | Image processing | | Flag Content | sm.flagContent | Content moderation | | Compliance | sm.compliance | GDPR, audit logs | | Orchestrator | sm.orchestrator | Workflow automation | | Logger | sm.logger | Centralized logging | | Catalog | sm.catalog | Service registry |

Error Codes

| Code | HTTP | When | |------|------|------| | unauthorized | 401 | Missing or invalid auth | | forbidden | 403 | Valid auth, insufficient permissions | | not_found | 404 | Resource doesn't exist | | validation_error | 422 | Bad input (details has per-field errors) | | rate_limited | 429 | Too many requests (details.retryAfter) | | conflict | 409 | Duplicate resource | | file_scanning | 202 | File uploaded, scan not complete | | file_threat | 403 | Malware detected | | internal_error | 500 | Server error |

TypeScript

Full type definitions included. All service methods, request/response types, and error codes are typed.

import type {
  ScaleMuleConfig,
  ApiResponse,
  ApiError,
  PaginatedResponse,
  QueryFilter,
  QuerySort,
  PresignedUploadResponse,
  UploadCompleteResponse,
} from '@scalemule/sdk'

License

Proprietary - ScaleMule Inc.