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

@dotuix/core

v0.2.6

Published

Core library for packing, unpacking, and validating .uix files

Readme

@dotuix/core

Core library for creating, reading, and validating .uix files.

A .uix file is a ZIP archive containing an HTML app, assets, and optional SQLite databases. Think of it like a PDF — but interactive, offline, and self-contained in a single file.

Install

npm install @dotuix/core
# or
pnpm add @dotuix/core

Requires Node.js ≥ 22.

Quick Start

import { UIX } from "@dotuix/core";

// Pack a project folder into a .uix archive
await UIX.pack("./my-app", "./dist/myshop.uix");

// Unpack to a directory
await UIX.unpack("./myshop.uix", "./extracted/");

// Validate
const result = await UIX.validate("./myshop.uix");
console.log(result.valid, result.errors, result.warnings);

// Read the manifest
const manifest = await UIX.manifest("./myshop.uix");
console.log(manifest.name, manifest.version);

// Open the embedded read-only database
const data = await UIX.openData("./myshop.uix", { permissions: [] });
if (data) {
  const products = data.find({
    type: "product",
    where: { category: "burgers" },
  });
  data.close();
}

// Create / load state.db in memory
const state = await UIX.createState({ uixVersion: "1.0", permissions: [] });
const id = state.insert({
  type: "cart_item",
  body: { productId: "p:001", qty: 2 },
});
const item = state.get(id);
const bytes = state.export(); // serialize for repacking
state.close();

API

Packing & Unpacking

pack(srcDir, outputPath)

Pack a folder into a .uix file on disk (Node.js).

  • Reads manifest.json from srcDir and validates it.
  • Compresses HTML/CSS/JS/JSON with DEFLATE. Stores .db, images, and media with STORE (no compression).
  • Writes atomically via .tmp → rename.
await UIX.pack("./my-app", "./dist/myshop.uix");

packBuffer(files)

Pack an in-memory file map into a Uint8Array (universal — browser + Node.js).

const encoder = new TextEncoder();
const buf = await UIX.packBuffer({
  "manifest.json": encoder.encode(JSON.stringify(manifest)),
  "index.html": encoder.encode("<html>…</html>"),
});

unpack(uixPath, outDir)

Extract a .uix archive to a directory on disk (Node.js).

await UIX.unpack("./myshop.uix", "./extracted/");

unpackBuffer(data)

Extract a .uix buffer into an in-memory file map (universal).

const files = UIX.unpackBuffer(data); // Record<string, Uint8Array>
const html = new TextDecoder().decode(files["index.html"]);

Validation

validate(uixPath) / validateBuffer(data)

Returns { valid: boolean; errors: string[]; warnings: string[] }.

Checks:

  • manifest.json exists and is valid (required fields, id format, expiry)
  • Entry file declared in manifest exists in the archive
  • File has not expired
  • (Warning, not error) Media/document files found outside assets/ or files/
  • (Warning) security.encryptedPaths lists a file not present in the archive
  • (Warning) security.auth: "pin" declared but encryptedPaths or keySalt missing
const result = await UIX.validate("./myshop.uix");
if (!result.valid) console.error(result.errors);
if (result.warnings.length) console.warn(result.warnings);

Manifest

manifest(uixPath) / manifestFromBuffer(data)

Read and parse the manifest from an archive.

const m = await UIX.manifest("./myshop.uix");
// m.id, m.name, m.version, m.entry, m.mode, m.permissions, …

parseManifest(raw) / safeParseManifest(raw)

Validate a raw manifest object using Zod. parseManifest throws on invalid input; safeParseManifest returns a Zod SafeParseReturnType.


Databases

Both data.db (read-only, creator-shipped) and state.db (read-write, user-owned) share the same schema:

CREATE TABLE records (
  id         TEXT    PRIMARY KEY,
  type       TEXT    NOT NULL,
  body       TEXT    NOT NULL,   -- JSON, shape decided by the .uix app
  created_at INTEGER NOT NULL DEFAULT (unixepoch()),
  updated_at INTEGER NOT NULL DEFAULT (unixepoch())
);

openData(uixPath, opts?) / openDataBuffer(data, opts?)

Open data.db from a .uix archive. Returns UIXDataDB | null (null if the archive has no data.db).

const db = await UIX.openData("./myshop.uix", { permissions: ["raw-sql"] });
if (db) {
  db.find({ type: "product" });
  db.find({ type: "product", where: { category: "مشويات" }, limit: 10 });
  db.get("product:001");
  db.raw("SELECT body FROM records WHERE type = ? ORDER BY rowid", ["product"]);
  db.close();
}

createState(opts)UIXStateDB

Create a fresh state.db in memory, or load from a seed.

const state = await UIX.createState({ uixVersion: "1.0" });

// With a seed (from manifest.state.seed = true)
const seed = files["state.db"]; // Uint8Array from unpackBuffer
const state = await UIX.createState({
  uixVersion: "1.0",
  seed,
  permissions: [],
});

openStateFromFile(statePath, opts?)UIXStateDB

Load an existing state.db file from disk (Node.js). Useful for CLI export commands.


UIXDataDB methods

| Method | Description | | ------------------- | ----------------------------------------------------------------- | | find(query) | Find records by type, with optional where, orderBy, limit | | get(id) | Get a single record by id, or null | | raw(sql, params?) | Execute a SELECT/WITH statement. Requires "raw-sql" permission. | | close() | Release the database |

UIXStateDB methods

| Method | Description | | ---------------------------- | ------------------------------------------------------------------------------- | | find(query) | Find records by type | | get(id) | Get a single record by id, or null | | insert({ type, body }) | Insert a record. Returns the generated id ({type}:{uuid}). | | update(id, body) | Update a record's body. Bumps updated_at. | | delete(id) | Delete a record by id | | raw(sql, params?) | Execute any SQL. Requires "raw-sql" permission. | | purge({ type, olderThan }) | Delete records older than a duration ('30d', '12h', '1y'). Returns count. | | export() | Serialize the database to Uint8Array for repacking. | | close() | Release the database |

FindQuery

interface FindQuery {
  type: string;
  where?: Record<string, unknown>; // json_extract filters on body fields
  orderBy?: string; // column name or body field
  limit?: number;
}

Format

Archive structure

myapp.uix (ZIP)
├── manifest.json        ← required
├── index.html           ← entry point
├── app.js
├── style.css
├── assets/              ← media files (images, video, audio, fonts)
│   ├── images/
│   ├── videos/
│   ├── audio/
│   └── fonts/
├── files/               ← any other file: PDF, JSON, CSV, plain text, …
├── data.db              ← optional: read-only SQLite (creator data)
└── state.db             ← optional: read-write SQLite (user session)

assets/ and files/ are conventions, not requirements. Files can live anywhere in the archive and are referenced via normal relative HTML paths. The validator emits a warning (never an error) if media files are found outside these folders.

manifest.json

Minimal (restaurant, shop, any regular app):

{
  "uix": "1.0",
  "id": "com.almadina.menu",
  "name": "Al Madina Restaurant Menu",
  "version": "2.1.0",
  "entry": "index.html",
  "mode": "kiosk",
  "permissions": ["local-storage", "print"],
  "network": "blocked",
  "author": "[email protected]",
  "expires": null,
  "state": { "seed": false }
}

With optional security block (government / classified use case — omit entirely for regular apps):

{
  "uix": "1.0",
  "id": "gov.qa.briefing.classified",
  "name": "Ministry Briefing Q2 2026",
  "version": "1.0.0",
  "entry": "index.html",
  "mode": "kiosk",
  "permissions": [],
  "network": "blocked",
  "expires": "2026-06-30",
  "security": {
    "auth": "pin",
    "encryptedPaths": ["data.db", "files/annex-a.pdf"],
    "kdf": "PBKDF2-SHA256",
    "kdfIterations": 200000,
    "keySalt": "base64url-random-salt",
    "maxOpens": 3,
    "screenshot": false
  },
  "signature": {
    "algorithm": "Ed25519",
    "publicKey": "base64url-public-key",
    "value": "base64url-signature",
    "signedAt": "2026-05-19T10:00:00Z"
  }
}

mode: kiosk (locked UI, no browser chrome) or window (developer preview).

permissions: local-storage, print, clipboard-write, fullscreen, raw-sql.

network: blocked (default) or allowed.

security fields (all optional — omit the block for regular apps):

| Field | Default | Description | | ---------------- | ----------------- | ----------------------------------------------------------------- | | auth | "none" | "pin" — viewer prompts for PIN before opening | | encryptedPaths | [] | Paths inside the archive encrypted with AES-256-GCM | | kdf | "PBKDF2-SHA256" | Key derivation function | | kdfIterations | 200000 | PBKDF2 iterations (higher = slower brute-force) | | keySalt | — | Base64url random salt stored in manifest (safe to store publicly) | | maxOpens | unlimited | Max opens tracked by the viewer locally — file cannot bypass this | | screenshot | false | true = viewer blocks OS screenshot API (desktop only) |

Compression

| File type | ZIP method | | ---------------------------------------------------- | ----------------- | | HTML, CSS, JS, JSON, text | DEFLATE (level 6) | | PNG, JPG, JPEG, WEBP, GIF, SVG, MP4, MP3, WEBM, WASM | STORE | | .db files | STORE | | PDF, DOCX, XLSX and other already-compressed formats | STORE |


License

MIT