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

@photostax/core

v0.6.1

Published

Unified photo stack library for Epson FastFoto repositories - Node.js binding

Readme

@photostax/core

Node.js binding for the photostax library — access Epson FastFoto repositories from TypeScript/JavaScript.

npm License

Overview

Epson FastFoto scanners produce multiple files per scanned photo:

| File Pattern | Description | |--------------|-------------| | <name>.jpg or <name>.tif | Original front scan | | <name>_a.jpg or <name>_a.tif | Enhanced version (color-corrected) | | <name>_b.jpg or <name>_b.tif | Back of the photo |

This library groups these files into PhotoStack objects with lazy, cached accessors for image data and metadata.

Architecture

PhotostaxRepository is a native Node.js addon (via napi-rs) that wraps the Rust SessionManager (formerly StackManager) — the unified cache and query engine powering photostax. Each instance creates a SessionManager internally with a single repository, giving you:

  • O(1) stack lookups by opaque ID
  • PhotoStack-centric I/Ostack.original.read(), stack.metadata.read(), etc.
  • Lazy, cached accessors — image data and metadata loaded on demand
  • ScanSnapshot queriesquery() returns a snapshot for consistent pagination

Multi-repo support: Use the StackManager class to manage multiple repositories through a single cache.

Multi-repo with StackManager

import { StackManager } from '@photostax/core';

const mgr = new StackManager();
mgr.addRepo('/photos/2024', { recursive: true });
mgr.addRepo('/photos/2023', { recursive: true });
console.log(`Managing ${mgr.repoCount} repos`);

// Query across all repos — returns a QueryResult
const result = mgr.query({ text: 'birthday' }, 20);
for (const stack of result.currentPage()) {
  console.log(`${stack.name} (${stack.id})`);

  // Per-stack image I/O (no manager methods needed)
  if (stack.original.isPresent) {
    const buf = stack.original.read();
    // ... process image ...
  }
}

Custom Repository Providers

Register a custom backend (e.g., cloud storage) by implementing the RepositoryProvider interface:

import { StackManager, type RepositoryProvider, type FileEntry } from '@photostax/core';

const oneDriveProvider: RepositoryProvider = {
  location: 'onedrive://user/photos',
  listEntries: (prefix: string, recursive: boolean): FileEntry[] => {
    return [
      { name: 'IMG_001.jpg', folder: 'vacation', path: 'onedrive://user/photos/vacation/IMG_001.jpg', size: 2048576 },
      { name: 'IMG_001_a.jpg', folder: 'vacation', path: 'onedrive://user/photos/vacation/IMG_001_a.jpg', size: 2148576 },
    ];
  },
  readFile: (path: string): Buffer => {
    return downloadFromOneDrive(path);
  },
  writeFile: (path: string, data: Buffer): void => {
    uploadToOneDrive(path, data);
  },
};

const mgr = new StackManager();
mgr.addForeignRepo(oneDriveProvider, { recursive: true, profile: 'auto' });
// query() auto-scans on first call

The host providesI/O primitives while Rust handles all scanning, file grouping, naming convention parsing, and metadata operations.

Installation

npm install @photostax/core

Quick Start

import { PhotostaxRepository } from '@photostax/core';

const repo = new PhotostaxRepository('/path/to/photos');

// Query all stacks — query() auto-scans on first call
const result = repo.query(undefined, 20);
for (const stack of result.currentPage()) {
  console.log(`Photo: ${stack.name} (id=${stack.id})`);
  if (stack.original.isPresent) {
    const buf = stack.original.read();
    console.log(`  Original: ${stack.original.size} bytes`);
  }
}

// Search with page navigation
const filtered = repo.query({ text: 'birthday', hasBack: true }, 20);
console.log(`Page 1: ${filtered.currentPage().length} of ${filtered.totalCount} total`);

// Navigate remaining pages
let page;
while ((page = filtered.nextPage()) !== null) {
  for (const stack of page) {
    console.log(`${stack.name} (${stack.id})`);
  }
}

// With progress callback (for initial scan)
const withProgress = repo.query(undefined, 20, (phase, current, total) => {
  console.log(`${phase}: ${current}/${total}`);
});

API Overview

PhotostaxRepository

The main class for accessing photo stacks.

| Method | Description | |--------|-------------| | query(filter?, pageSize?, callback?) | Returns a QueryResult for search + pagination. Auto-scans on first call. |

StackManager

Multi-repository manager for unified access across directories and custom backends.

| Method | Description | |--------|-------------| | new StackManager() | Create an empty manager | | addRepo(path, options?) | Register a local directory | | addForeignRepo(provider, options?) | Register a custom repository provider | | repoCount | Number of registered repositories | | query(filter?, pageSize?, callback?) | Search across all repos, returns QueryResult. Auto-scans on first call. |

RepositoryProvider

Interface for custom repository backends:

interface RepositoryProvider {
  readonly location: string;
  listEntries(prefix: string, recursive: boolean): FileEntry[];
  readFile(path: string): Buffer;
  writeFile(path: string, data: Buffer): void;
}

interface FileEntry {
  name: string;    // File name with extension
  folder: string;  // Relative folder path (empty for root)
  path: string;    // Full path or URI
  size: number;    // File size in bytes
}

PhotoStack (v0.4.0)

All I/O operations are accessed directly on the stack object:

interface PhotoStack {
  id: string;                  // Opaque 16-char hex hash; use for lookups
  name: string;                // Human-readable display name
  folder: string | null;       // Subfolder within the repository
  original: ImageRef;          // Original front scan accessor
  enhanced: ImageRef;          // Enhanced scan accessor
  back: ImageRef;              // Back scan accessor
  metadata: MetadataRef;       // Metadata accessor
}

ImageRef

Lazy, cached accessor for a single image variant:

interface ImageRef {
  isPresent: boolean;          // Whether this image variant exists
  read(): Buffer;              // Read image bytes
  hash(): string;              // SHA-256 content hash (cached)
  dimensions(): { width: number; height: number }; // Image dimensions (cached)
  size: number;                // File size in bytes
  rotate(degrees: number): void; // Rotate image in place
}

MetadataRef

Lazy accessor for stack metadata:

interface MetadataRef {
  read(): Metadata;            // Load EXIF, XMP, and custom tags (lazy-loaded)
  write(metadata: Metadata): void; // Write metadata back
}

Metadata

interface Metadata {
  exifTags: Record<string, string>;    // EXIF tags from image
  xmpTags: Record<string, string>;     // XMP metadata
  customTags: Record<string, unknown>; // Custom JSON metadata
}

SearchQuery

interface SearchQuery {
  text?: string;                       // Free-text search
  exifFilters?: KeyValueFilter[];      // EXIF tag filters
  customFilters?: KeyValueFilter[];    // Custom tag filters
  hasBack?: boolean;                   // Filter by back scan presence
  hasEnhanced?: boolean;               // Filter by enhanced scan presence
  repoId?: string;                     // Filter by repository ID (v0.4.0)
}

QueryResult

Page-based query result with navigation:

interface QueryResult {
  currentPage(): PhotoStack[];       // Stacks on the current page
  totalCount: number;                // Total matching stacks
  pageCount: number;                 // Total number of pages
  currentPageIndex: number;          // Zero-based current page index
  nextPage(): PhotoStack[] | null;   // Advance and return next page
  previousPage(): PhotoStack[] | null; // Go back and return previous page
  setPage(index: number): PhotoStack[] | null; // Jump to specific page
  query(filter: SearchQuery, pageSize?: number): QueryResult; // Sub-query
}

Utility Functions

| Function | Description | |----------|-------------| | isNativeAvailable() | Check if native addon loaded successfully | | getNativeLoadError() | Get error if native addon failed to load |

Building from Source

Prerequisites

  • Rust toolchain (1.70+)
  • Node.js 18+ with npm
  • Python 3.x (for node-gyp)
  • C++ compiler (Visual Studio Build Tools on Windows, GCC/Clang on Unix)

Build Steps

# Clone the repository
git clone https://github.com/JeromySt/photostax.git
cd photostax/bindings/typescript

# Install dependencies
npm install

# Build the native addon
npm run build

# Run tests
npm test

Development Build

For faster iteration during development:

npm run build:debug

Supported Platforms

Pre-built binaries are published for:

| Platform | Architectures | |----------|---------------| | Windows | x64, arm64 | | macOS | x64, arm64 | | Linux | x64, arm64, musl |

File Format Support

| Format | Extensions | EXIF | XMP | |--------|------------|------|-----| | JPEG | .jpg, .jpeg | ✓ | Embedded | | TIFF | .tif, .tiff | ✓ | Embedded or sidecar |

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.


← Back to main README | FFI Documentation