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

qencode-s3-sdk

v0.1.0

Published

Qencode-flavoured S3 toolkit (browser/Node) - framework-agnostic

Readme

qencode-s3-sdk

Qencode‑flavoured S3 toolkit for Node and browser (framework‑agnostic). It wraps AWS SDK v3 for the Qencode Media Storage endpoints and adds high‑level, ergonomic helpers for common bucket/prefix operations.

Highlights

  • ESM + CJS builds with TypeScript types
  • Simple, focused API: list, upload (with progress), head, copy/move/rename, folder ops
  • Predictable behavior: functions return data or throw (raw AWS errors). getItemInfo is the only one that resolves { exists: false } on 404.
  • Folder ops preserve empty subfolders by moving folder‑marker objects

Installation

npm i qencode-s3-sdk
# or: pnpm add qencode-s3-sdk
# or: yarn add qencode-s3-sdk

This package ships dual builds:

  • dist/index.js (ESM)
  • dist/index.cjs (CJS)
  • dist/index.d.ts (types)

Node ≥ 18 recommended.


Quick start (Node)

index.mjs

import 'dotenv/config';
import { listItems, uploadFile, getItemInfo } from 'qencode-s3-sdk';

const cfg = {
  region: process.env.Q_REGION || 'us-west',   // or 'eu-central'
  access_id: process.env.Q_ACCESS_ID,
  secret_key: process.env.Q_SECRET_KEY
};
const bucket = process.env.Q_BUCKET;
const prefix = process.env.Q_PREFIX || 'sdk-test/';

// 1) Upload with progress
const data = new Uint8Array(Buffer.from('hello from qencode-s3-sdk\n', 'utf8'));
await uploadFile(bucket, data, cfg, {
  key: `${prefix}hello.txt`,
  contentType: 'text/plain',
  onProgress: p => process.stdout.write(`\r${p}%`)
});
process.stdout.write('\n');

// 2) List a prefix
const { items } = await listItems(bucket, cfg, prefix);
console.log(items.map(i => ({ name: i.name, type: i.type, size: i.size })));

// 3) HEAD an object
const info = await getItemInfo(bucket, `${prefix}hello.txt`, cfg);
console.log(info.exists ? info.metadata : { exists: false });

CommonJS

require('dotenv/config');
const { listItems } = require('qencode-s3-sdk');

React usage

Production rule: Do not embed Qencode credentials in the browser. Call the SDK on your server (Express/Fastify/etc.), expose minimal endpoints (/list, /upload, …), and call those endpoints from React.

Below are two dev‑only React patterns for quick local testing. Use them only during development.

A) Dev‑only React (config from env)

.env.local

VITE_Q_REGION=us-west
VITE_Q_ACCESS_ID=your_access_id
VITE_Q_SECRET_KEY=your_secret_key
VITE_Q_BUCKET=your_bucket
VITE_Q_PREFIX=sdk-test/

App.tsx

import { useState } from 'react';
import { uploadFile, listItems, type QencodeS3Config } from 'qencode-s3-sdk';

export default function App() {
  const [items, setItems] = useState<any[]>([]);
  const cfg: QencodeS3Config = {
    region: import.meta.env.VITE_Q_REGION || 'us-west',
    access_id: import.meta.env.VITE_Q_ACCESS_ID,
    secret_key: import.meta.env.VITE_Q_SECRET_KEY
  };
  const bucket = import.meta.env.VITE_Q_BUCKET as string;
  const prefix = (import.meta.env.VITE_Q_PREFIX as string) || 'sdk-test/';

  async function onUpload(e: React.ChangeEvent<HTMLInputElement>) {
    const f = e.target.files?.[0];
    if (!f || !bucket) return;
    await uploadFile(bucket, f, cfg, {
      key: `${prefix}${f.name}`,
      contentType: f.type || 'application/octet-stream',
      onProgress: p => console.log('progress', p)
    });
    const { items } = await listItems(bucket, cfg, prefix);
    setItems(items);
  }

  return (
    <main style={{ padding: 24 }}>
      <h3>Dev‑only React example</h3>
      <input type="file" onChange={onUpload} />
      <ul>
        {items.map(i => <li key={i.name}>{i.type === 'folder' ? '📁' : '📄'} {i.name}</li>)}
      </ul>
      <p style={{ color: '#b00' }}>⚠️ Dev‑only. Do not use this pattern in production.</p>
    </main>
  );
}

B) Dev‑only React (config via API call to a local server)

server.js (Express)

import 'dotenv/config';
import express from 'express';
const app = express();

app.get('/api/qencode/config', (req, res) => {
  res.json({
    region: process.env.Q_REGION || 'us-west',
    access_id: process.env.Q_ACCESS_ID,
    secret_key: process.env.Q_SECRET_KEY,
    bucket: process.env.Q_BUCKET,
    prefix: process.env.Q_PREFIX || 'sdk-test/'
  });
});

app.listen(3001, () => console.log('dev config server on :3001'));

App.tsx

import { useEffect, useState } from 'react';
import { listItems, uploadFile, type QencodeS3Config } from 'qencode-s3-sdk';

export default function App() {
  const [cfg, setCfg] = useState<QencodeS3Config | null>(null);
  const [bucket, setBucket] = useState('');
  const [prefix, setPrefix] = useState('sdk-test/');
  const [items, setItems] = useState<any[]>([]);

  useEffect(() => {
    fetch('http://localhost:3001/api/qencode/config')
      .then(r => r.json())
      .then(c => {
        setCfg({ region: c.region, access_id: c.access_id, secret_key: c.secret_key });
        setBucket(c.bucket);
        setPrefix(c.prefix);
      })
      .catch(console.error);
  }, []);

  async function refresh() {
    if (!cfg || !bucket) return;
    const { items } = await listItems(bucket, cfg, prefix);
    setItems(items);
  }

  async function onUpload(e: React.ChangeEvent<HTMLInputElement>) {
    const f = e.target.files?.[0];
    if (!cfg || !bucket || !f) return;
    await uploadFile(bucket, f, cfg, {
      key: `${prefix}${f.name}`,
      contentType: f.type || 'application/octet-stream'
    });
    refresh();
  }

  return (
    <main style={{ padding: 24 }}>
      <h3>Dev‑only React (config via API)</h3>
      <input type="file" onChange={onUpload} />
      <button onClick={refresh}>Refresh</button>
      <ul>{items.map(i => <li key={i.name}>{i.name}</li>)}</ul>
      <p style={{ color: '#b00' }}>⚠️ Dev‑only — do not expose credentials in production.
        In production, call the SDK on your server and return only the data you need.</p>
    </main>
  );
}

API (summary)

Types

export const REGIONS = ['eu-central', 'us-west'] as const;
export type QencodeRegion = (typeof REGIONS)[number];

export interface QencodeS3Config {
  region: QencodeRegion;
  access_id: string;
  secret_key: string;
  /** Advanced: override the endpoint base (default: "s3.qencode.com"). */
  endpointBase?: string;
}

export type FileOpResult = { message: string; changed: boolean };
export type FolderOpResult = { message: string; moved: number };

Client helpers

import { createQencodeS3Client, virtualHostedUrl } from 'qencode-s3-sdk';

const s3 = createQencodeS3Client(cfg); // low-level AWS S3Client if you need it
const url = virtualHostedUrl('my-bucket', 'folder/a b.txt', cfg);
// → https://my-bucket.us-west.s3.qencode.com/folder/a%20b.txt

Storage

All functions throw on failure (reject with raw AWS errors), unless noted.

  • listItems(bucket, cfg, prefix = "", delimiter = "/"){ items } (folders + files)
  • uploadFile(bucket, data, cfg, opts?){ path, result } (progress callback supported)
  • getItemInfo(bucket, key, cfg){ exists: false } on 404; otherwise { exists: true, metadata }
  • countItems(bucket, cfg, prefix = ""){ itemCount }
  • totalSize(bucket, cfg, prefix = ""){ totalSize } (bytes)
  • createFolder(bucket, prefix, cfg){ message } (zero‑byte marker via PutObject)
  • deleteFile(bucket, key, cfg){ message }
  • deleteFolder(bucket, prefix, cfg){ message } (throws if nothing matched)
  • copyFile(bucket, srcKey, dstKey, cfg){ message } (multipart for >5 GiB)
  • renameFile(bucket, oldKey, newKey, cfg){ message, changed } (no‑op if keys equal)
  • moveFile(bucket, oldKey, newKey, cfg){ message, changed }
  • renameFolder(bucket, oldPrefix, newPrefix, cfg){ message, moved } (preserves empty subfolders)
  • moveFolder(bucket, oldPrefix, newParentPrefix, cfg){ message, moved }

Prefix rules & folder semantics

  • All prefix parameters are normalized: no leading /, guaranteed trailing /.
  • S3 has no real folders; folder UIs rely on prefix and optional zero‑byte folder markers (<prefix>/).
  • listItems filters markers from files, but folder ops move/delete markers so your empty subfolders are preserved.

Error model

  • All functions return data on success and throw raw AWS errors on failure (network/auth/403/etc.).

  • getItemInfo is the only function that normalizes 404 to { exists: false }.

  • Validation errors you might see:

    • Destination is inside the source folder. (rename/move folder)
    • Folder does not exist or is empty. (rename/move folder when nothing matched)

Local testing

Use the built‑in tests (they read credentials from .env).

cp .env.example .env  # fill Q_ACCESS_ID, Q_SECRET_KEY, Q_REGION, Q_BUCKET
npm run build
node tests/listItems.test.js
# seed data when needed
Q_SEED=1 node tests/renameFolder.test.js

Test packaging without publishing

npm run build
npm publish --dry-run
npm pack            # → qencode-s3-sdk-x.y.z.tgz
mkdir /tmp/sdk-tmp && cd /tmp/sdk-tmp
npm init -y
npm i /path/to/qencode-s3-sdk-x.y.z.tgz

License

MIT © Qencode