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

@supashiphq/vue-sdk

v1.0.3

Published

Supaship SDK for Vue 3

Readme

Supaship Vue SDK

Supaship SDK for Vue 3 that provides composables for feature flag management with full TypeScript type safety.

Installation

npm install @supashiphq/vue-sdk
# or
yarn add @supashiphq/vue-sdk
# or
pnpm add @supashiphq/vue-sdk

Quick Start

// main.ts
import { createApp } from 'vue'
import { createSupaship, FeaturesWithFallbacks } from '@supashiphq/vue-sdk'
import App from './App.vue'

// Define your features with type safety
const FEATURE_FLAGS = {
  'new-header': false,
  'theme-config': { mode: 'dark' as const, showLogo: true },
  'beta-features': [] as string[],
} satisfies FeaturesWithFallbacks

// REQUIRED: for type safety
declare module '@supashiphq/vue-sdk' {
  // eslint-disable-next-line @typescript-eslint/no-empty-object-type
  interface Features extends InferFeatures<typeof FEATURE_FLAGS> {}
}

const supaship = createSupaship({
  config: {
    sdkKey: 'your-sdk-key',
    environment: 'production',
    features: FEATURE_FLAGS,
    context: {
      userId: '123',
      email: '[email protected]',
    },
  },
})

const app = createApp(App)

// Configure the Supaship plugin
app.use(supaship)

app.mount('#app')

Using the Composable

<script setup lang="ts">
import { useFeature } from '@supashiphq/vue-sdk'

const { feature: newHeader, isLoading } = useFeature('new-header')
</script>

<template>
  <div v-if="isLoading">Loading...</div>
  <NewHeader v-else-if="newHeader" />
  <OldHeader v-else />
</template>

API Reference

createSupaship

Creates a Vue plugin for Supaship. This is the standard Vue way to set up the SDK globally.

// main.ts
import { createApp } from 'vue'
import { createSupaship } from '@supashiphq/vue-sdk'

const supaship = createSupaship(options)

const app = createApp(App)
app.use(supaship)
app.mount('#app')

Options:

| Option | Type | Required | Description | | --------- | ------------------ | -------- | ---------------------------- | | config | SupaClientConfig | Yes | Configuration for the client | | toolbar | ToolbarConfig | No | Development toolbar settings |

Configuration Options:

const config = {
  sdkKey: 'your-sdk-key',
  environment: 'production',
  features: {
    // Required: define all feature flags with fallback values
    'my-feature': false,
    config: { theme: 'light' },
  },
  context: {
    // Optional: targeting context
    userId: 'user-123',
    email: '[email protected]',
    plan: 'premium',
  },
  // Hash sensitive context properties such as PII on the client before sending to Edge
  sensitiveContextProperties: ['email', 'userID'],
  networkConfig: {
    // Optional: network settings
    featuresAPIUrl: 'https://api.supashiphq.com/features',
    retry: {
      enabled: true,
      maxAttempts: 3,
      backoff: 1000,
    },
    requestTimeoutMs: 5000,
  },
}

Privacy note: set sensitiveContextProperties to hash PII/sensitive context property values on the client before requests are sent to the Edge API.

Supported Feature Value Types:

| Type | Example | Description | | --------- | ----------------------------------- | ------------------------- | | boolean | false | Simple on/off flags | | object | { theme: 'dark', showLogo: true } | Configuration objects | | array | ['feature-a', 'feature-b'] | Lists of values | | null | null | Disabled/unavailable flag |

Note: Strings and numbers are not supported as standalone feature values. Use objects instead: { value: 'string' } or { value: 42 }.

useFeature Composable

Retrieves a single feature flag value with Vue reactivity and full TypeScript type safety.

const result = useFeature(featureName, options?)

Parameters:

  • featureName: string - The feature flag key
  • options?: object
    • context?: Record<string, unknown> - Context override for this request
    • shouldFetch?: boolean - Whether to fetch the feature (default: true)

Return Value:

{
  feature: ComputedRef<T>,      // The feature value (typed based on your Features interface)
  isLoading: ComputedRef<boolean>,  // Loading state
  isSuccess: ComputedRef<boolean>,  // Success state
  isError: ComputedRef<boolean>,    // Error state
  error: Ref<Error | null>,         // Error object if failed
  status: Ref<'idle' | 'loading' | 'success' | 'error'>,
  refetch: () => Promise<void>,     // Function to manually refetch
  // ... other query state properties
}

Examples:

<script setup lang="ts">
import { useFeature } from '@supashiphq/vue-sdk'

// Simple boolean feature
const { feature: isEnabled, isLoading } = useFeature('new-ui')
</script>

<template>
  <Skeleton v-if="isLoading" />
  <NewUI v-else-if="isEnabled" />
  <OldUI v-else />
</template>
<script setup lang="ts">
import { useFeature } from '@supashiphq/vue-sdk'

// Object feature
const { feature: config } = useFeature('theme-config')
</script>

<template>
  <div v-if="config" :class="config.theme">
    <Logo v-if="config.showLogo" />
    <div :style="{ color: config.primaryColor }">Content</div>
  </div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useFeature } from '@supashiphq/vue-sdk'
import { useUser } from './composables/user'

const { user, isLoading: userLoading } = useUser()

// Only fetch when user is loaded
const { feature } = useFeature('user-specific-feature', {
  context: computed(() => ({ userId: user.value?.id })),
  shouldFetch: computed(() => !userLoading.value && !!user.value),
})
</script>

<template>
  <SpecialContent v-if="feature" />
</template>

useFeatures Composable

Retrieves multiple feature flags in a single request with type safety.

const result = useFeatures(featureNames, options?)

Parameters:

  • featureNames: readonly string[] - Array of feature flag keys
  • options?: object
    • context?: Record<string, unknown> - Context override for this request
    • shouldFetch?: boolean - Whether to fetch features (default: true)

Return Value:

{
  features: ComputedRef<{ [key: string]: T }>, // Object with feature values (typed based on keys)
  isLoading: ComputedRef<boolean>,
  isSuccess: ComputedRef<boolean>,
  isError: ComputedRef<boolean>,
  error: Ref<Error | null>,
  status: Ref<'idle' | 'loading' | 'success' | 'error'>,
  refetch: () => Promise<void>,
  // ... other query state properties
}

Examples:

<script setup lang="ts">
import { useFeatures } from '@supashiphq/vue-sdk'
import { useUser } from './composables/user'

const { user } = useUser()

// Fetch multiple features at once (more efficient than multiple useFeature calls)
const { features, isLoading } = useFeatures(['new-dashboard', 'beta-mode', 'show-sidebar'], {
  context: computed(() => ({
    userId: user.value?.id,
    plan: user.value?.plan,
  })),
})
</script>

<template>
  <LoadingSpinner v-if="isLoading" />
  <div v-else :class="features['new-dashboard'] ? 'new-layout' : 'old-layout'">
    <Sidebar v-if="features['show-sidebar']" />
    <BetaBadge v-if="features['beta-mode']" />
    <MainContent />
  </div>
</template>

useFeatureContext Composable

Access and update the feature context within components.

const { updateContext, getContext } = useFeatureContext()

Example:

<script setup lang="ts">
import { ref } from 'vue'
import { useFeatureContext } from '@supashiphq/vue-sdk'

const { updateContext } = useFeatureContext()
const user = ref(null)

const handleUserUpdate = newUser => {
  user.value = newUser

  // Update feature context when user changes
  // This will trigger refetch of all features
  updateContext({
    userId: newUser.id,
    plan: newUser.subscriptionPlan,
    segment: newUser.segment,
  })
}
</script>

<template>
  <form @submit="handleUserUpdate">
    <!-- User profile form -->
  </form>
</template>

Best Practices

1. Always Use satisfies for Feature Definitions

// ✅ Good - preserves literal types
const features = {
  'dark-mode': false,
  theme: { mode: 'light' as const, variant: 'compact' as const },
} satisfies FeaturesWithFallbacks

// ❌ Bad - loses literal types (don't use type annotation)
const features: FeaturesWithFallbacks = {
  'dark-mode': false,
  theme: { mode: 'light', variant: 'compact' }, // Types widened to string
}

2. Centralize Feature Definitions

// ✅ Good - centralized feature definitions
// lib/features.ts
export const FEATURE_FLAGS = {
  'new-header': false,
  theme: { mode: 'light' as const },
  'beta-features': [] as string[],
} satisfies FeaturesWithFallbacks

// ❌ Bad - scattered feature definitions
const config1 = { features: { 'feature-1': false } satisfies FeaturesWithFallbacks }
const config2 = { features: { 'feature-2': true } satisfies FeaturesWithFallbacks }

3. Use Type Augmentation for Type Safety

// ✅ Good - type augmentation for global type safety
declare module '@supashiphq/vue-sdk' {
  interface Features extends InferFeatures<typeof FEATURE_FLAGS> {}
}

// Now all useFeature calls are type-safe
const { feature } = useFeature('new-header') // ✅ TypeScript knows this is ComputedRef<boolean>
const { feature } = useFeature('invalid') // ❌ TypeScript error

4. Use Context for User Targeting

// main.ts
import { createApp } from 'vue'
import { createSupaship } from '@supashiphq/vue-sdk'

const supaship = createSupaship({
  config: {
    sdkKey: 'your-sdk-key',
    features: FEATURE_FLAGS,
    context: {
      // Initial context - can be updated later with useFeatureContext
      version: import.meta.env.VITE_APP_VERSION,
      environment: import.meta.env.MODE,
    },
  },
})

const app = createApp(App)
app.use(supaship)
app.mount('#app')

Then update context dynamically in your components:

<script setup lang="ts">
import { watch } from 'vue'
import { useFeatureContext } from '@supashiphq/vue-sdk'
import { useAuth } from './composables/auth'

const { updateContext } = useFeatureContext()
const { user } = useAuth()

// Update context when user changes
watch(
  user,
  newUser => {
    if (newUser) {
      updateContext({
        userId: newUser.id,
        email: newUser.email,
        plan: newUser.plan,
      })
    }
  },
  { immediate: true }
)
</script>

5. Batch Feature Requests

<script setup lang="ts">
// ✅ Good - single API call
const { features } = useFeatures(['feature-1', 'feature-2', 'feature-3'])

// ❌ Less efficient - multiple API calls
const feature1 = useFeature('feature-1')
const feature2 = useFeature('feature-2')
const feature3 = useFeature('feature-3')
</script>

6. Handle Loading States

<script setup lang="ts">
import { computed } from 'vue'
import { useUser } from './composables/user'
import { useFeatures } from '@supashiphq/vue-sdk'

const { user, isLoading: userLoading } = useUser()

const { features, isLoading: featuresLoading } = useFeatures(['user-specific-feature'], {
  context: computed(() => ({ userId: user.value?.id })),
  shouldFetch: computed(() => !userLoading.value && !!user.value),
})

const isLoading = computed(() => userLoading.value || featuresLoading.value)
</script>

<template>
  <Skeleton v-if="isLoading" />
  <SpecialContent v-else-if="features['user-specific-feature']" />
</template>

7. Update Context Reactively

<script setup lang="ts">
import { ref, watch } from 'vue'
import { useFeatureContext } from '@supashiphq/vue-sdk'

const { updateContext } = useFeatureContext()
const currentPage = ref('dashboard')

// Update context when navigation changes
watch(currentPage, newPage => {
  updateContext({ currentPage: newPage })
})
</script>

<template>
  <div>
    <Navigation @page-change="page => (currentPage = page)" />
    <PageContent :page="currentPage" />
  </div>
</template>

Framework Integration

Vite

// main.ts
import { createApp } from 'vue'
import { createSupaship, FeaturesWithFallbacks } from '@supashiphq/vue-sdk'
import App from './App.vue'

const FEATURE_FLAGS = {
  'new-ui': false,
  theme: { mode: 'light' as const },
} satisfies FeaturesWithFallbacks

const supaship = createSupaship({
  config: {
    sdkKey: import.meta.env.VITE_SUPASHIP_SDK_KEY,
    environment: import.meta.env.MODE,
    features: FEATURE_FLAGS,
  },
})

const app = createApp(App)
app.use(supaship)
app.mount('#app')

Nuxt 3

// plugins/supaship.client.ts
import { defineNuxtPlugin } from '#app'
import { createSupaship, FeaturesWithFallbacks } from '@supashiphq/vue-sdk'

const FEATURE_FLAGS = {
  'new-homepage': false,
  'dark-mode': false,
} satisfies FeaturesWithFallbacks

export default defineNuxtPlugin(nuxtApp => {
  const config = useRuntimeConfig()
  const supaship = createSupaship({
    config: {
      sdkKey: config.public.supashipSDKKey as string,
      environment: process.env.NODE_ENV || 'production',
      features: FEATURE_FLAGS,
    },
  })

  nuxtApp.vueApp.use(supaship)
})
// nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    public: {
      supashipSDKKey: process.env.NUXT_PUBLIC_SUPASHIP_SDK_KEY || '',
    },
  },
})

Feature Flag Guards for Vue Router

// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import { useClient } from '@supashiphq/vue-sdk'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/beta',
      component: () => import('./views/BetaFeature.vue'),
      meta: { requiresFeature: 'beta-access' },
    },
  ],
})

router.beforeEach(async (to, from, next) => {
  const featureFlag = to.meta.requiresFeature as string | undefined

  if (featureFlag) {
    try {
      const client = useClient()
      const feature = await client.getFeature(featureFlag)

      if (!feature) {
        // Feature is disabled, redirect
        return next('/404')
      }
    } catch (error) {
      console.error('Error checking feature flag:', error)
      return next('/error')
    }
  }

  next()
})

export default router

Development Toolbar

The SDK includes a development toolbar for testing and debugging feature flags locally.

app.use(
  createSupaship({
    config: { ... },
    toolbar: {
      enabled: 'auto', // 'auto' | 'always' | 'never'
      position: 'bottom-right', // 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'
    },
  })
)
  • 'auto': Shows toolbar in development environments only (default)
  • true: Always shows toolbar
  • false: Never shows toolbar

The toolbar allows you to:

  • View all available feature flags
  • Override feature values locally
  • See feature value types and current values
  • Clear local overrides

Testing

Mocking Feature Flags in Tests

The plugin approach makes testing straightforward - just install the plugin with test features:

// test-utils/setup.ts
import { createSupaship, FeaturesWithFallbacks } from '@supashiphq/vue-sdk'

export function createTestSupaship(features: FeaturesWithFallbacks) {
  return createSupaship({
    config: {
      sdkKey: 'test-key',
      environment: 'test',
      features,
      context: {},
    },
  })
}

Example Test with Vitest

// MyComponent.test.ts
import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import { createTestSupaship } from '../test-utils/setup'
import MyComponent from './MyComponent.vue'

describe('MyComponent', () => {
  it('shows new feature when enabled', () => {
    const wrapper = mount(MyComponent, {
      global: {
        plugins: [
          createTestSupaship({
            'new-feature': true,
          }),
        ],
      },
    })

    expect(wrapper.text()).toContain('New Feature Content')
  })

  it('shows old feature when disabled', () => {
    const wrapper = mount(MyComponent, {
      global: {
        plugins: [
          createTestSupaship({
            'new-feature': false,
          }),
        ],
      },
    })

    expect(wrapper.text()).toContain('Old Feature Content')
  })

  it('handles multiple features', () => {
    const wrapper = mount(MyComponent, {
      global: {
        plugins: [
          createTestSupaship({
            'feature-a': true,
            'feature-b': false,
            config: { theme: 'dark' },
          }),
        ],
      },
    })

    expect(wrapper.find('.feature-a').exists()).toBe(true)
    expect(wrapper.find('.feature-b').exists()).toBe(false)
  })
})

Troubleshooting

Common Issues

Type errors with FeaturesWithFallbacks

If you encounter type errors when defining features, ensure you're using the correct pattern:

Solution: Always use satisfies FeaturesWithFallbacks (not type annotation)

// ✅ Good - preserves literal types
const features = {
  'my-feature': false,
  config: { theme: 'dark' as const },
} satisfies FeaturesWithFallbacks

// ❌ Bad - loses literal types
const features: FeaturesWithFallbacks = {
  'my-feature': false,
  config: { theme: 'dark' }, // Widened to string
}

Plugin Not Installed Error

Error: useFeature must be used within a component tree that has the Supaship plugin installed

Solution: Ensure your app has the plugin installed in main.ts:

// ✅ Correct - main.ts
import { createApp } from 'vue'
import { createSupaship } from '@supashiphq/vue-sdk'
import App from './App.vue'

const app = createApp(App)
app.use(createSupaship({ config: { ... } }))  // Plugin installed
app.mount('#app')

// ❌ Incorrect - plugin not installed or installed after mount
const app = createApp(App)
app.mount('#app')  // Plugin missing!

Features Not Loading

  • Check SDK key: Verify your SDK key is correct
  • Check network: Open browser dev tools and check network requests
  • Check features config: Ensure features are defined in the config

Type Errors

Property 'my-feature' does not exist on type 'Features'

Solution: Add type augmentation:

import { InferFeatures } from '@supashiphq/vue-sdk'
import { FEATURE_FLAGS } from './features'

declare module '@supashiphq/vue-sdk' {
  interface Features extends InferFeatures<typeof FEATURE_FLAGS> {}
}

License

This project is licensed under the MIT License - see the LICENSE file for details.