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

@arthurzakharov/convert-service

v1.0.2

Published

Server-side Convert.com SDK proxy service

Readme

convert-service

Server-side proxy for the Convert.com FullStack SDK. Exposes a small HTTP API so frontend apps can bucket visitors, read FullStack configuration, run feature flags, and track conversions without embedding SDK keys in the browser.

The bucketing endpoints follow Convert's documented FullStack flow for experiences and variations: create a visitor context, evaluate targeting/location rules, then return the selected variation or feature state.

Endpoints

| Method | Path | Description | |--------|------|-------------| | GET | /health | Returns service version and commit hash | | POST | /bucket | Run all active experiences for a visitor | | GET | /experiences | List configured experiences for a project | | GET | /experiences/:experienceKey | Read one experience by key | | GET | /experiences/by-id/:experienceId | Read one experience by ID | | GET | /experiences/:experienceKey/variations/:variationKey | Read one variation by experience/variation key | | GET | /experiences/by-id/:experienceId/variations/:variationId | Read one variation by experience/variation ID | | POST | /experiences/run | Run one experience by key for a visitor | | POST | /experiences/run-by-id | Run one experience by ID for a visitor | | GET | /features | List configured FullStack features for a project | | GET | /features/:featureKey | Read one feature by key | | GET | /features/by-id/:featureId | Read one feature by ID | | POST | /features/run-all | Run all feature flags for a visitor | | POST | /features/run | Run one feature flag by key for a visitor | | POST | /features/run-by-id | Run one feature flag by ID for a visitor | | POST | /track | Track a conversion goal |

GET /health

{
  "status": "ok",
  "version": "1.0.0",
  "commit": "a3f9c12",
  "timestamp": "2026-06-11T17:00:00.000Z"
}

POST /bucket

// Request
{
  "projectKey": "passexperten",
  "visitorId": "user-unique-id",
  "visitorProperties": { "country": "DE" }
}

// Response
{
  "visitorId": "user-unique-id",
  "variations": [
    {
      "experienceKey": "exp-key",
      "experienceName": "My Experiment",
      "variationKey": "variation-1",
      "variationName": "Variation 1",
      "changes": []
    }
  ]
}

Shared bucketing fields

POST /bucket, /experiences/run, /experiences/run-by-id, /features/run-all, /features/run, and /features/run-by-id accept these Convert bucketing fields:

| Field | Required | Description | |---|---|---| | projectKey | Yes | Supported project key, for example passexperten | | visitorId | Yes | Stable visitor identifier | | visitorProperties | No | Audience/segment targeting properties | | locationProperties | No | Location rule matching properties | | pageUrl | No | Page URL used while deriving default segments | | campaign | No | Campaign value used while deriving default segments | | updateVisitorProperties | No | Whether to update in-memory visitor properties | | forceVariationId | No | Force a specific variation ID when running experiences | | enableTracking | No | Whether Convert should track the bucketing event immediately | | environment | No | Override Convert environment for this decision | | typeCasting | No | Feature variable type casting flag | | experienceKeys | No | Limit feature evaluation to specific experience keys |

GET /experiences

GET /experiences?projectKey=passexperten
{
  "experiences": [
    {
      "id": "100001",
      "key": "headline-test",
      "name": "Headline Test",
      "status": "active",
      "variations": []
    }
  ]
}

GET /experiences/:experienceKey

GET /experiences/headline-test?projectKey=passexperten
{
  "experience": {
    "id": "100001",
    "key": "headline-test",
    "name": "Headline Test",
    "variations": []
  }
}

The ID-based equivalent is:

GET /experiences/by-id/100001?projectKey=passexperten

GET /experiences/:experienceKey/variations/:variationKey

GET /experiences/headline-test/variations/variation-a?projectKey=passexperten
{
  "variation": {
    "id": "200001",
    "key": "variation-a",
    "name": "Variation A",
    "changes": []
  }
}

The ID-based equivalent is:

GET /experiences/by-id/100001/variations/200001?projectKey=passexperten

POST /experiences/run

// Request
{
  "projectKey": "passexperten",
  "visitorId": "user-unique-id",
  "experienceKey": "headline-test",
  "visitorProperties": { "country": "DE" },
  "locationProperties": { "url": "https://www.passexperten.de/" }
}

// Response
{
  "visitorId": "user-unique-id",
  "variation": {
    "id": "200001",
    "experienceId": "100001",
    "experienceKey": "headline-test",
    "experienceName": "Headline Test",
    "variationKey": "variation-a",
    "variationName": "Variation A",
    "status": "running",
    "bucketingAllocation": 5000,
    "changes": []
  }
}

The ID-based request uses experienceId:

{
  "projectKey": "passexperten",
  "visitorId": "user-unique-id",
  "experienceId": "100001"
}

GET /features

GET /features?projectKey=passexperten
{
  "features": [
    {
      "id": "300001",
      "key": "checkout-flow",
      "name": "Checkout Flow",
      "variables": [
        { "key": "enabled", "type": "boolean" }
      ]
    }
  ]
}

POST /features/run-all

// Request
{
  "projectKey": "passexperten",
  "visitorId": "user-unique-id",
  "visitorProperties": { "country": "DE" }
}

// Response
{
  "visitorId": "user-unique-id",
  "features": [
    {
      "experienceId": "100001",
      "experienceKey": "headline-test",
      "experienceName": "Headline Test",
      "id": "300001",
      "key": "checkout-flow",
      "name": "Checkout Flow",
      "status": "enabled",
      "variables": { "enabled": true }
    }
  ]
}

POST /features/run

// Request
{
  "projectKey": "passexperten",
  "visitorId": "user-unique-id",
  "featureKey": "checkout-flow",
  "experienceKeys": ["headline-test"]
}

// Response
{
  "visitorId": "user-unique-id",
  "feature": {
    "experienceId": "100001",
    "experienceKey": "headline-test",
    "id": "300001",
    "key": "checkout-flow",
    "status": "enabled",
    "variables": { "enabled": true }
  },
  "features": [
    {
      "experienceId": "100001",
      "experienceKey": "headline-test",
      "id": "300001",
      "key": "checkout-flow",
      "status": "enabled",
      "variables": { "enabled": true }
    }
  ]
}

The ID-based request uses featureId:

{
  "projectKey": "passexperten",
  "visitorId": "user-unique-id",
  "featureId": "300001"
}

POST /track

// Request
{
  "projectKey": "passexperten",
  "visitorId": "user-unique-id",
  "goalKey": "purchase",
  "attributes": {
    "conversionData": [
      { "key": "amount", "value": 49.99 }
    ]
  }
}

// Response
{ "success": true }

Supported projects

| projectKey | Site | |---|---| | passexperten | passexperten.de | | bussgeldcheck | bussgeldcheck.de |

Local development

cp .env.example .env
# Fill in real SDK keys from Convert dashboard → Project Settings → SDK Keys

npm install
npm run dev        # ts-node-dev with hot reload on port 3100

Frontend npm helper

This package also exposes a browser-safe helper for frontend applications. Import it from the client subpath so the frontend bundle does not import the Express service entrypoint:

import { createConvertServiceClient } from 'convert-service/client';

const convert = createConvertServiceClient({
  baseUrl: 'https://convert-service.example.com',
  projectKey: 'passexperten',
});

const { variation } = await convert.runExperience({
  experienceKey: 'headline-test',
  visitorProperties: { country: 'DE' },
  locationProperties: { url: window.location.href },
});

if (variation?.variationKey === 'variation-a') {
  // render variation-specific frontend behavior
}

The helper manages a stable visitor ID in a first-party cookie named convert_visitor_id by default. You can override or seed it when needed:

const convert = createConvertServiceClient({
  baseUrl: 'https://convert-service.example.com',
  projectKey: 'bussgeldcheck',
  visitorCookieName: 'bc_convert_vid',
  visitorCookieMaxAgeDays: 180,
  defaultVisitorProperties: { app: 'bussgeldcheck-web' },
});

convert.setVisitorId(currentUser.id);

Available helper methods:

| Method | Service endpoint | |---|---| | health() | GET /health | | bucket() | POST /bucket | | listExperiences() | GET /experiences | | getExperienceByKey() | GET /experiences/:experienceKey | | getExperienceById() | GET /experiences/by-id/:experienceId | | getVariationByKey() | GET /experiences/:experienceKey/variations/:variationKey | | getVariationById() | GET /experiences/by-id/:experienceId/variations/:variationId | | runExperience() | POST /experiences/run | | runExperienceById() | POST /experiences/run-by-id | | listFeatures() | GET /features | | getFeatureByKey() | GET /features/:featureKey | | getFeatureById() | GET /features/by-id/:featureId | | runFeatures() | POST /features/run-all | | runFeature() | POST /features/run | | runFeatureById() | POST /features/run-by-id | | trackConversion() | POST /track |

Endpoint contracts are exported from convert-service/contracts:

import type {
  ConvertApiEndpoints,
  EndpointRequest,
  EndpointResponse,
  RunFeatureResponse,
} from 'convert-service/contracts';

type RunFeatureRequest = EndpointRequest<'POST /features/run'>;
type BucketResponse = EndpointResponse<'POST /bucket'>;
type AllEndpoints = keyof ConvertApiEndpoints;

Use these types in frontend wrappers, tests, or mocks when you need exact request/response compatibility with this service.

Environment variables

| Variable | Required | Description | |---|---|---| | CONVERT_SDK_KEY_PASSEXPERTEN | Yes | SDK key for passexperten project | | CONVERT_SDK_KEY_BUSSGELDCHECK | Yes | SDK key for bussgeldcheck project | | CONVERT_ENVIRONMENT | Yes | staging or live | | CONVERT_DATA_REFRESH_INTERVAL | No | Config refresh interval in ms (default: 300000) | | CORS_ORIGINS | No | Comma-separated allowed origins (default: *) | | PORT | No | Port to listen on (default: 3100) |

Docker

docker build -t convert-service .
docker run -p 3100:3100 --env-file .env convert-service

Deployment

The service deploys to Railway automatically via GitHub Actions.

Flow:

  • Push to any branch / open a PR → CI runs (typecheck + build)
  • Merge to main → auto-deploys to Railway

One-time setup:

  1. Create a Railway project and link it to this repo
  2. Set all env variables in the Railway dashboard
  3. Add RAILWAY_TOKEN to GitHub repo → Settings → Secrets and variables → Actions

Tech stack