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

@fiction/sdk

v1.0.125

Published

SDK for Fiction app authentication and user management

Readme

Fiction SDK

Lightweight client library for Fiction digital agent integration. Built for Fiction-owned properties (app, www, widget).

Installation

npm install @fiction/sdk

Quick Start

import { FictionSDK } from '@fiction/sdk'

// Singleton pattern - shares state across your app
const sdk = FictionSDK.getInstance({ isDev: true })

// Get public digital agent
const agent = await sdk.getPublicAgent({ handle: 'andrew' })

// Authenticate user
await sdk.requestAuthCode({ email: '[email protected]' })
await sdk.loginWithCode({ email: '[email protected]', code: '123456' })

// Access reactive state
console.log(sdk.activeUser.value)
console.log(sdk.currentAgent.value)
console.log(sdk.currentOrg.value)

Core Philosophy

Minimal API Surface: Clean wrapper methods hide internal complexity. No direct apiClient exposure prevents type generation issues.

Agent-First Architecture: Digital agents are primary entities. Organization context derived from agent, not passed redundantly.

Singleton by Default: Browser-only singleton prevents duplicate API calls and separate reactive state. Multiple getInstance() calls return same instance.

SSR-Safe: No singleton in Node.js—each request gets new instance to prevent request bleeding.

Public API

Authentication

// Request email verification code
await sdk.requestAuthCode({ email: '[email protected]' })

// Login with code
await sdk.loginWithCode({
  email: '[email protected]',
  code: '123456'
})

// Get current user
const user = await sdk.getCurrentUser()

// Logout
await sdk.logout()

Digital Agent Access

// Public agent by handle (no auth)
const agent = await sdk.getPublicAgent({ handle: 'andrew' })

// Public agent by email (no auth)
const agent = await sdk.getAgentByEmail({ email: '[email protected]' })

Usage Tracking

// Unified tracking method
await sdk.trackUsage({
  agentId: 'agt_456',      // Agent contains orgId
  type: 'voice',          // 'voice' | 'chat'
  quantity: 45,           // Seconds for voice, characters for chat
  participantId: 'usr_123' // Optional: conversation participant
})

Reactive State

// Core state
sdk.activeUser.value    // EnrichedUser | undefined
sdk.token.value         // string | null
sdk.loading.value       // boolean
sdk.error.value         // string | null

// Computed properties (auto-derived from activeUser)
sdk.currentAgent.value   // Agent | undefined
sdk.currentOrg.value    // OrgInfo | undefined

Singleton Pattern

Why Singleton: Prevents duplicate API requests and maintains shared reactive state across your application.

// Both return same instance
const sdk1 = FictionSDK.getInstance({ isDev: true })
const sdk2 = FictionSDK.getInstance({ isDev: true })

console.log(sdk1 === sdk2) // true

// Reactive state shared
sdk1.token.value = 'test-token'
console.log(sdk2.token.value) // 'test-token'

Cross-Bundle Singleton: Uses globalThis to share instance between separate bundles (www + widget).

// Global SDK (www.fiction.com)
window.fictionSDK = FictionSDK.getInstance({ isDev: true })

// Widget automatically uses singleton
const widget = new FictionWidget({ handle: 'andrew' })
console.log(window.fictionSDK === widget.sdk) // true

Testing Pattern: Use clear() to reset singleton between tests.

import { afterEach, it, expect } from 'vitest'

afterEach(() => {
  const sdk = FictionSDK.getInstance({ isDev: true })
  sdk.clear() // Clears session + state + destroys singleton
})

it('should test singleton behavior', () => {
  const sdk = FictionSDK.getInstance({ isDev: true })
  // Test runs with clean state
})

Environment Detection: apiBase auto-detected from environment. Only override for testing.

// ✅ Normal usage (95%+ cases) - apiBase omitted
const sdk = FictionSDK.getInstance({ isDev: true })
// Dev: http://localhost:5555 (auto)
// Prod: https://app.fiction.com (auto)

// ✅ Testing only - custom apiBase
const testSDK = FictionSDK.getInstance({
  isDev: true,
  apiBase: 'http://localhost:9999'
})

Usage Patterns

Blog Author Attribution

// Astro component (www.fiction.com)
import { FictionSDK } from '@fiction/sdk'

const sdk = FictionSDK.getInstance({ isDev: import.meta.env.DEV })
const authorAgent = await sdk.getAgentByEmail({ email: '[email protected]' })

// Use digital agent data instead of static strings
const authorAvatar = authorAgent?.avatar?.url
const authorName = authorAgent?.name
const authorBio = authorAgent?.summary

Widget Integration

import { FictionWidget } from '@fiction/sdk/widget'

const widget = new FictionWidget({
  mode: 'modal',        // 'inline' | 'popup' | 'modal'
  handle: 'andrew',     // Or provide agent object directly
  context: 'support',   // Optional: agent context override
  firstMessage: 'Hi!'   // Optional: custom greeting
})

// Update without re-mount
widget.update({
  agent: newAgent,
  context: 'sales'
})

// Cleanup
widget.destroy()

Profile Page

import { AgentChat } from '@fiction/sdk/agent'

const sdk = FictionSDK.getInstance({ isDev: true })
const agent = await sdk.getPublicAgent({ handle: 'andrew' })
<template>
  <AgentChat
    :sdk="sdk"
    :agent="agent"
    theme-color="#3b82f6"
  />
</template>

Error Handling

All wrapper methods return undefined on error (no exceptions thrown). Check reactive error state for details.

const agent = await sdk.getPublicAgent({ handle: 'nonexistent' })

if (!agent) {
  console.error(sdk.error.value) // 'Agent not found'
}

Build Strategy

SDK Build: ES modules, tree-shakeable, externals (Vue, ElevenLabs)

  • Format: ES modules
  • Bundle size: < 256KB
  • Build time: 5 seconds

Widget Build: IIFE, single-file CDN bundle

  • Format: IIFE (global window.FictionWidget)
  • Bundle size: < 1MB
  • Includes all dependencies

Import Patterns

// ✅ Correct - Use bundled entry points
import { FictionSDK } from '@fiction/sdk'
import { AgentChat, AgentProvider } from '@fiction/sdk/agent'
import { FictionWidget } from '@fiction/sdk/widget'
import { getDemoAgents } from '@fiction/sdk/demo'

// ❌ Wrong - Deep imports don't work
import { FictionSDK } from '@fiction/sdk/FictionSDK'
import AgentChat from '@fiction/sdk/agent/ui/AgentChat.vue'

Package Exports

{
  "exports": {
    ".": {
      "import": "./dist/sdk.js",
      "types": "./dist/sdk.d.ts"
    },
    "./agent": {
      "import": "./dist/agent.js",
      "types": "./dist/agent.d.ts"
    },
    "./widget": {
      "import": "./dist/widget.js",
      "types": "./dist/widget.d.ts"
    },
    "./demo": {
      "import": "./dist/demo.js",
      "types": "./dist/demo/index.d.ts"
    }
  }
}

Agent-First Architecture

Digital agents are primary entities that determine organizational context.

// ✅ Agent contains all context
await sdk.trackUsage({
  agentId: 'agt_456',  // Includes orgId internally
  type: 'voice',
  quantity: 45
})

// ❌ Don't pass redundant orgId
await sdk.trackUsage({
  orgId: 'org_123',   // Redundant - derived from agentId
  agentId: 'agt_456',
  type: 'voice',
  quantity: 45
})

Benefits:

  • Simpler APIs (fewer parameters)
  • Single source of truth (agent determines org)
  • Automatic context resolution
  • Consistent across all SDK methods

Reactive State Pattern

SDK maintains reactive state that auto-updates components.

const sdk = FictionSDK.getInstance({ isDev: true })

// Core refs (direct state)
sdk.activeUser.value    // Updated by auth operations
sdk.token.value         // Updated by login/logout
sdk.loading.value       // Updated during API calls
sdk.error.value         // Updated on errors

// Computed properties (auto-derived)
sdk.currentAgent.value = computed(() => {
  const user = sdk.activeUser.value
  if (!user?.agents) return undefined

  const agentId = user.primaryAgentId || user.agents[0]?.agentId
  return user.agents.find(a => a.agentId === agentId)
})

sdk.currentOrg.value = computed(() => {
  const agent = sdk.currentAgent.value
  if (!agent?.orgId) return undefined

  return sdk.activeUser.value?.orgs.find(org =>
    org.orgId === agent.orgId
  )
})

Benefits:

  • Components auto-update when user changes
  • No manual state management needed
  • Consistent with Fiction app patterns
  • Agent-first architecture (org derived from agent)

Component Integration

Prop-Based Dependency Injection

SDK components accept sdk prop instead of using global services.

<script setup>
import { FictionSDK } from '@fiction/sdk'
import { AgentChat } from '@fiction/sdk/agent'

const sdk = FictionSDK.getInstance({ isDev: true })
const agent = await sdk.getPublicAgent({ handle: 'andrew' })
</script>

<template>
  <AgentChat
    :sdk="sdk"
    :agent="agent"
    theme-color="#3b82f6"
  />
</template>

Available Components

AgentChat - Main agent interface (chat/voice) AgentProvider - Handle → agent resolver wrapper ElAgentAbout - Profile information display ElAgentChat - Text chat interface ElAgentVoice - Voice call interface ElAgentSidebar - Navigation sidebar

Demo Data

Static demo agents for marketing/showcase.

import { getDemoAgents, getDemoAgentByHandle } from '@fiction/sdk/demo'

// All demo agents
const demoAgents = getDemoAgents()

// Specific demo agent
const andrew = getDemoAgentByHandle('andrew')

Single Source of Truth: Demo data maintained in Fiction app at src/modules/agent/static/data.ts, bundled into SDK at build time.

Security Model

Public-Only Access: getPublicAgent() and getAgentByEmail() only return agents with visibility: 'public'.

Authentication Required: User operations require valid JWT token in sdk.token.value.

Rate Limiting: Widget includes 10 comments/hour per agent, 100 likes/hour (future).

Performance Optimization

Single Query Pattern: Public endpoints use single SQL query with LEFT JOINs for media.

Voice Recordings Excluded: Public endpoints exclude voice recordings (performance).

Build Time Bundling: App dependencies (@/ imports) bundled during SDK build.

Ultra-Fast Builds: 5-second builds via custom tsconfig + esbuild optimization.

Type Safety

Full TypeScript Inference: From API routes to SDK methods to component props.

Wrapper Method Pattern: Clean function signatures serialize perfectly to .d.ts files.

No Complex Types: Simple Promise<Agent | undefined> instead of Hono RPC types.

// Clean wrapper method
async getPublicAgent(args: {
  handle: string
}): Promise<Agent | undefined>

// Internal complexity hidden
const response = await this.api.agent.public[':handle'].$get({
  param: { handle: args.handle }
})

Best Practices

1. Use Singleton: Always use getInstance() instead of new FictionSDK().

// ✅ Recommended
const sdk = FictionSDK.getInstance({ isDev: true })

// ⚠️ Works but creates singleton anyway
const sdk = new FictionSDK({ isDev: true })

2. Don't Override apiBase: Let SDK auto-detect environment.

// ✅ Normal usage
const sdk = FictionSDK.getInstance({ isDev: true })

// ❌ Only for testing
const sdk = FictionSDK.getInstance({
  isDev: true,
  apiBase: 'http://localhost:9999'
})

3. Clear Between Tests: Use clear() for test isolation.

afterEach(() => {
  FictionSDK.getInstance({ isDev: true }).clear()
})

4. Check Error State: Methods return undefined on error.

const agent = await sdk.getPublicAgent({ handle: 'test' })
if (!agent) {
  console.error(sdk.error.value)
}

5. Agent-First APIs: Pass agentId, let SDK resolve orgId.

// ✅ Minimal args
await sdk.trackUsage({ agentId, type, quantity })

// ❌ Redundant args
await sdk.trackUsage({ orgId, agentId, type, quantity })

Architecture Principles

Minimal Case First: Start with simplest implementation, add complexity only when needed.

No Globals: All state via dependency injection (SDK prop pattern).

Bundle Safety: Server code never reaches client (strict separation).

Validated Environment: Auto-detected apiBase based on environment.

Static Chaining: Preserves Hono type inference for internal operations.

Browser Compatibility

Singleton Scope: globalThis for cross-bundle state sharing.

SSR Detection: typeof window === 'undefined' prevents Node.js singleton.

Storage: localStorage for user data, cookies for auth tokens.

Vue 3.6+: Required for reactive refs and computed properties.

Development

# Watch mode - rebuilds on change
pnpm dev

# Production build (ES modules + types)
pnpm build

# Run tests
pnpm test

# Typecheck
pnpm typecheck

Build required before typecheck - Widget/www import from dist/, not source.

Contributing

This SDK is for Fiction-owned projects only. For internal development:

  1. Make changes in packages/sdk/
  2. Build: pnpm run sdk:build
  3. Test: pnpm test packages/sdk/test/
  4. Update this README if public API changes

License

Proprietary - Fiction Co.

Support

Internal developers: See .ai/spec-sdk.md for complete architecture documentation.

External users: Contact [email protected]