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

@stackshift-cloud/sdk

v1.2.0

Published

Official JavaScript and TypeScript SDK for StackShift.

Readme

@stackshift-cloud/sdk

Official JavaScript and TypeScript SDK for StackShift.

Governed media and galleries

Use a separate server-side client for each Assets space; the namespace is fixed on that client. Project membership does not grant Assets access.

const studio = new StackShift({
  apiKey: process.env.STACKSHIFT_API_KEY,
  assetSpaceId: 'your-asset-space-uuid',
})
const page = await studio.assets.dam.publications({ status: 'published' })
if (page.next_cursor) {
  const next = await studio.assets.dam.publications({ status: 'published', cursor: page.next_cursor })
}
const gallery = await studio.assets.dam.resolveGallery('published-gallery-uuid')

assets.dam also exposes collaborators, typed metadata schemas, bucket governance, publication transitions, model processing, gallery drafts and picker capabilities. Updates use explicit revisions. Submission needs assets:write, review needs assets:admin, and publication/withdrawal needs assets:publish, in addition to the corresponding Assets grant.

Keep account credentials on your server. Give @stackshift-cloud/assets-picker a short-lived, origin-restricted capability; give @stackshift-cloud/assets-gallery a server resolver for approved media. Store publication/gallery IDs and versions, never delivery URLs. Model processing is an explicitly enabled pilot and requires the qualified isolated worker described in packages/assets-model-processor/README.md.

Install

npm install @stackshift-cloud/sdk

Set your API key:

STACKSHIFT_API_KEY=sk_live_xxx

Send email

import { StackShift } from '@stackshift-cloud/sdk'

const stackshift = new StackShift()

await stackshift.mail.send({
  from: 'StackShift <[email protected]>',
  to: '[email protected]',
  subject: 'Welcome',
  text: 'Welcome to StackShift.',
})

HTML and idempotency keys are supported:

const message = await stackshift.mail.send({
  from: { email: '[email protected]', name: 'StackShift' },
  to: ['[email protected]'],
  subject: 'Verify your email',
  html: '<p>Your code is 123456</p>',
  text: 'Your code is 123456',
  idempotencyKey: 'verify-email:user_123',
})

console.log(message.id, message.status, message.idempotencyStatus)

Inspect MTA-accepted messages:

const messages = await stackshift.mail.messages.list({ status: 'mta_accepted', limit: 20 })
const detail = await stackshift.mail.messages.get(messages.data[0].id)
const attempts = await stackshift.mail.messages.attempts(detail.id)
const logs = await stackshift.mail.messages.logs(detail.id)

mta_accepted means StackShift's outbound MTA accepted the message. delivered means the recipient MX accepted a recipient; it does not claim inbox placement or human receipt. Recipient records remain authoritative for mixed outcomes.

Send a template

Templates are created and rendered by the StackShift backend. Variables use {{name}} syntax, and missing variables fail before a message is queued.

await stackshift.mail.templates.create({
  name: 'Welcome Email',
  slug: 'welcome-email',
  subject: 'Welcome, {{name}}',
  html: '<h1>Welcome, {{name}}</h1>',
  text: 'Welcome, {{name}}',
})

const preview = await stackshift.mail.templates.preview('welcome-email', {
  data: { name: 'Ada' },
})

const message = await stackshift.mail.sendTemplate({
  template: 'welcome-email',
  to: '[email protected]',
  from: 'Acme <[email protected]>',
  data: { name: 'Ada' },
  idempotencyKey: 'welcome:user_123',
})

console.log(preview.subject, message.id)

Updating template content creates a new version. You can inspect or reactivate versions with templates.versions(), templates.getVersion(), and templates.activateVersion().

Bounces and suppressions

Hard bounces are automatically suppressed to protect sender reputation. Suppressions are workspace scoped and enforced by the StackShift backend before a message is queued.

const suppressions = await stackshift.mail.suppressions.list()

await stackshift.mail.suppressions.create({
  email: '[email protected]',
  reason: 'manual',
})

await stackshift.mail.suppressions.delete('sup_123')

const bounces = await stackshift.mail.bounces.list({
  type: 'hard',
  limit: 20,
})

const messageBounces = await stackshift.mail.messages.bounces('msg_123')

bounced means StackShift detected a delivery failure after handoff. Hard-bounced recipients are automatically suppressed; removing a suppression allows future sends to be queued again.

Verify a sending domain

Custom domains are workspace/API-key scoped. You do not pass a project ID or deploy an app first.

const domain = await stackshift.mail.domains.create('acme.com')

console.log(domain.records)

await stackshift.mail.domains.verify(domain.id)

await stackshift.mail.send({
  from: 'Acme <[email protected]>',
  to: '[email protected]',
  subject: 'Welcome',
  html: '<h1>Welcome</h1>',
  text: 'Welcome',
})

Add the returned SPF, DKIM, and return-path DNS records before verifying. DMARC is recommended by default and may be required if your StackShift environment sets MAIL_DMARC_REQUIRED=true. DNS propagation can take time, so a missing or pending status usually means you should wait and verify again.

Upload an asset

import { StackShift } from '@stackshift-cloud/sdk'
import { readFile } from 'node:fs/promises'

const stackshift = new StackShift()

const file = new Blob([await readFile('./avatar.png')], { type: 'image/png' })

const asset = await stackshift.assets.upload(file, {
  folder: 'avatars',
  visibility: 'public',
  metadata: { userId: 'user_123' },
})

console.log(asset.url)

You do not pass a deployed StackShift project ID. The API key identifies the StackShift account, and StackShift resolves the default asset space internally.

Private assets

const asset = await stackshift.assets.upload(file, {
  bucket: 'imports',
  key: 'customers.csv',
  visibility: 'private',
})

const signed = await stackshift.assets.signedUrl(asset.id, {
  expiresIn: '10m',
  maxDownloads: 1,
})

console.log(signed.url)

Image transformations

Use built-in presets or create your own named transformations.

import { StackShift, assetTransformOptions, getAssetTransformPreset } from '@stackshift-cloud/sdk'

const stackshift = new StackShift()
const hero = getAssetTransformPreset('hero')!
const heroOptions = assetTransformOptions(hero)

await stackshift.assets.createTransformation({ name: hero.name, ...heroOptions })

const named = stackshift.assets.namedUrl('asset_123', hero.name)
const signed = await stackshift.assets.signedTransformUrl('asset_123', {
  ...heroOptions,
  expiresIn: '10m',
})

await stackshift.assets.deleteTransformation('old-preset')
console.log(named, signed.url)

NestJS

import { Injectable } from '@nestjs/common'
import { StackShift } from '@stackshift-cloud/sdk'

@Injectable()
export class AssetsService {
  private readonly stackshift = new StackShift({
    apiKey: process.env.STACKSHIFT_API_KEY!,
  })

  upload(file: Express.Multer.File) {
    return this.stackshift.assets.upload(
      new Blob([file.buffer], { type: file.mimetype }),
      {
        bucket: 'uploads',
        key: file.originalname,
        visibility: 'private',
        metadata: { originalName: file.originalname },
      },
    )
  }
}

Storage connections and migrations

Use AWS AssumeRole for S3 whenever possible. The returned external_id belongs in the role trust policy; credentials are write-only and are never returned by Stackshift.

const connection = await stackshift.assets.connections.create({
  provider: 'aws_s3',
  name: 'Production media',
  configuration: {
    bucket: 'acme-media',
    region: 'us-east-1',
    role_arn: 'arn:aws:iam::123456789012:role/StackshiftAssets',
    auth_type: 'assume_role',
  },
})

await stackshift.assets.connections.verify(connection.id)

const dryRun = await stackshift.assets.migrations.start({
  source_connection_id: connection.id,
  destination_bucket_id: 'assets_bucket_id',
  source_prefix: 'legacy',
  conflict_policy: 'skip',
  dry_run: true,
})

const report = await stackshift.assets.migrations.downloadReport(dryRun.id, 'csv')

Native Stackshift S2 storage uses a bucket reference and internal authorization, never copied S2 access keys:

const s2 = await stackshift.assets.storage.createS2Bucket({ name: 'asset-originals', region: 'us-east-1' })
await stackshift.assets.storage.configure('assets_bucket_id', 3, {
  storage_mode: 'stackshift_s2',
  s2_bucket_id: s2.id,
  object_prefix: 'originals',
  derivative_prefix: 'derived',
  auto_extract_text: true,
})

OCR and browser upload capabilities

const extraction = await stackshift.assets.text.start('asset_id', { language: 'eng' })
const current = await stackshift.assets.text.get('asset_id')

const capability = await stackshift.assets.uploadCapabilities.create({
  bucket: 'uploads',
  key_prefix: 'customers/user_123',
  allowed_mime_types: ['image/png', 'image/jpeg'],
  allowed_origins: ['https://app.example.com'],
  max_bytes: 25 * 1024 * 1024,
  expires_in: '15m',
})

Create capabilities only in trusted backend code. Send capability.token to @stackshift-cloud/assets-widget; never put the Stackshift API key in a browser bundle.

For larger uploads, create a signed upload URL on your NestJS server and let the browser upload directly.

Direct browser uploads

Create a signed upload URL on your server:

const upload = await stackshift.assets.createUploadSession({
  bucket: 'avatars',
  key: 'users/user_123.png',
  visibility: 'public',
  expiresIn: '10m',
  maxBytes: 5_000_000,
})

Then upload from the browser:

await fetch(upload.url, {
  method: upload.method,
  body: file,
  headers: { 'Content-Type': file.type },
})

This is the recommended pattern for React, Next.js, TanStack Start, and any browser app. The browser never receives your StackShift API key.

StackShift chooses storage nodes, writes to StackShift-owned disks, replicates internally, and returns CDN-ready asset URLs. SDK users never choose storage nodes and never receive disk paths.

Campaign drafts and engagement

Create campaigns with content: { subject, html, text, editorMode: 'code' }, or keep using a template/version. mail.campaigns.update(id, { ...draft, revision }) saves a full draft; preview, test, send(id, revision), and schedule(id, sendAt, revision) support review and dispatch. A stale revision returns 409.

Mail send inputs accept tracking: { opens: false, clicks: true }. Each omitted setting inherits the workspace default. Use mail.tracking.settings() / updateSettings(), message(id) / campaign(id) for reports, and domains() / createDomain() / verifyDomain() / updateDomain() for branded hosts. Features must be enabled on the deployment. Opens are approximate; suspected automation is reported separately. Detailed activity expires after 90 days. Tests and OTPs never track.

Isolated testing, brands, migration, and spending

Use a sspat_test_ credential with simulation: { scenario: 'delayed' } for a 60-second simulated delay. Other outcomes are delivered, bounced, and complained. Test resources are isolated; no real email is delivered.

const brand = await client.mail.brands.create({ name: 'Product', editableFields: ['company'] })
const audience = await client.mail.audiences.create({ brandId: brand.id, name: 'Product news' })

Every audience requires an explicit brand. Contact names/attributes are shared within that brand while consent stays per audience. mail.automations, mail.forms, and mail.segments expose audience-owned resources.

mail.migration supports public DNS audits/plans and authenticated checklists. mail.billing.summary() and mail.campaigns.estimate(id) expose allowance, credit, and blocking reasons. Pass approved maximum kobo as the third argument to send(id, revision, approvedMaxKobo) or the fourth to schedule(id, sendAt, revision, approvedMaxKobo). Zero is a valid hard maximum. Billing is inactive until a rate card is explicitly published and activated; funding and prepaid opt-in are separate owner-only dashboard actions.

Native video, governed DAM and product rendering

Use the space-scoped DAM and video clients for collaborators, metadata schemas, bucket governance, reviewed publications, picker capabilities, model preparation, product rendering, galleries, version-pinned encoding, captions and playback sessions.

const dam = sdk.assets.dam
const video = sdk.assets.video
const schema = await dam.createMetadataSchema({ name: 'Products', fields: [{
  key: 'product_name', label: 'Product name', type: 'text', required: true,
}] })
const published = await dam.publishMetadataSchema(schema.id, schema.revision)
const workspace = await video.workspace(assetId)

Mutations carry the supplied revision in If-Match. Model preparation and rendering carry an idempotency key; render submission also carries the accepted maximum units. Playback renewal and events authenticate with the session credential, not the account key. Keep these clients on your backend and return grants with Cache-Control: private, no-store.

See the Assets SDK guide and its linked feature guides for complete language examples, inputs, permissions, review transitions and browser callbacks.