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

@grest-ts/schema-file

v0.0.20

Published

File abstraction for Grest framework

Readme

Part of the grest-ts framework. Documentation | All packages

@grest-ts/schema-file

Unified file abstraction for the Grest framework. Works across HTTP (multipart upload/download), WebSocket (base64 JSON), and tests (in-memory buffers) with a single API.

In-memory files — when to use

GGFile holds file content in Node.js process memory. The entire file is buffered as a Uint8Array (or lazily read from a browser File/Blob). This is simple and works great for prototyping, internal tools, and smaller applications where files are modest in size.

For production systems handling large files or high throughput, pass files through your server to an object store (S3, GCS, R2, etc.) and work with URLs/keys instead. A common pattern is to have your upload endpoint stream directly to S3 and return a URL, rather than buffering the whole file in memory. This keeps memory usage predictable and avoids OOM under load.

Features

  • Single GGFile type used everywhere — contracts, services, clients, and tests
  • Schema validation with IsFile — MIME type filtering and size limits
  • Single-consumption model prevents accidental double-reads
  • Works in Node.js and browsers (wraps native File/Blob lazily)
  • Testkit with ready-made files (PNG, JPEG, PDF, CSV, JSON, random bytes)

Quick Example

Defining a contract with file fields

import { IsFile } from "@grest-ts/schema-file"
import { IsObject, IsString, IsArray } from "@grest-ts/schema"

// Accept any file
const IsUploadRequest = IsObject({
    file: IsFile,
    description: IsString.orUndefined
})

// Accept images only, max 5 MB
const IsAvatarRequest = IsObject({
    image: IsFile.accept('image/*').maxSize(5 * 1024 * 1024)
})

// Multiple files
const IsBatchRequest = IsObject({
    files: IsArray(IsFile),
    metadata: IsObject({ tags: IsArray(IsString) })
})

// File as output (for downloads)
const contract = {
    downloadReport: {
        input: IsObject({ reportId: IsString }),
        success: IsFile,
        errors: [SERVER_ERROR]
    }
}

Reading file content in a service

import { GGFile } from "@grest-ts/schema-file"

async function handleUpload(file: GGFile): Promise<void> {
    console.log(file.name)      // "report.pdf"
    console.log(file.mimeType)  // "application/pdf"
    console.log(file.size)      // 1048576

    // Read the content (can only be called once)
    const bytes = await file.buffer()
    // or: const text = await file.text()
    // or: const stream = file.stream()
}

Returning a file from a service

async function downloadReport(request: { reportId: string }): Promise<GGFile> {
    const content = await generateReport(request.reportId)
    return GGFile.fromString(content, "report.csv", "text/csv")
}

GGFile

Abstract base class representing a file. All file operations flow through this type.

Properties

| Property | Type | Description | |---|---|---| | name | string | File name (e.g. "photo.jpg") | | mimeType | string | MIME type (e.g. "image/jpeg") | | size | number | Size in bytes | | consumed | boolean | Whether the content has been read |

Reading content

Each file can only be read once. After calling any of these methods, the file is marked as consumed and further reads throw an error. Use clone() if you need multiple reads.

// Read as Uint8Array
const bytes = await file.buffer()

// Read as UTF-8 string
const text = await file.text()

// Read as ReadableStream
const stream = file.stream()

// Create an independent copy (before consuming)
const copy = file.clone()
const bytes1 = await copy.buffer()
const bytes2 = await file.buffer()  // still works

Factory methods

// From raw bytes
GGFile.fromBuffer(data: Uint8Array, name: string, mimeType?: string): GGFile

// From a string (UTF-8 encoded)
GGFile.fromString(content: string, name: string, mimeType?: string): GGFile

// From base64 (used internally for WebSocket transport)
GGFile.fromBase64(base64: string, name: string, mimeType?: string): GGFile

// From a browser <input type="file"> (lazy — reads only when consumed)
GGFile.fromBrowserFile(file: File): GGFile

Serialization

Files are automatically serialized for transport — multipart FormData over HTTP, base64 JSON over WebSocket. You don't need to call these directly, but they're available:

// Convert to base64 string (consumes the file)
const b64 = await GGFile.toBase64(file)

// Serialize to JSON (consumes the file)
const json = await GGFile.toJSON(file)
// { __ggfile: true, name: "...", mimeType: "...", size: 123, data: "base64..." }

// Deserialize from JSON
const restored = GGFile.fromJSON(json)

// Check if a value is serialized GGFile JSON
GGFile.isJSON(value) // boolean

IsFile (Schema)

IsFile is a GGSchema for validating files in contracts. It supports MIME type filtering and size limits.

import { IsFile } from "@grest-ts/schema-file"

// Accept any file
IsFile

// Constrain by MIME type
IsFile.accept('image/*')                    // any image
IsFile.accept('image/png', 'image/jpeg')    // specific types
IsFile.accept('.pdf')                       // by extension
IsFile.accept('application/pdf')            // by MIME type

// Constrain by size
IsFile.maxSize(10 * 1024 * 1024)  // 10 MB

// Combine constraints
IsFile.accept('image/*').maxSize(5 * 1024 * 1024)

// Optional
IsFile.orUndefined
IsFile.orNull

Static shortcuts

FileSchema.image()                    // image/*
FileSchema.image({ maxSize: 5_000_000 })
FileSchema.pdf()                      // application/pdf
FileSchema.video()                    // video/*
FileSchema.audio()                    // audio/*
FileSchema.any()                      // no constraints
FileSchema.any({ maxSize: 50_000_000 })

Validation errors

| Error code | Description | |---|---| | file.type | Value is not a file | | file.mimeType | File type not in the accept list | | file.maxSize | File exceeds the maxSize limit |

Testkit

Import @grest-ts/schema-file/testkit for test utilities that create GGFile instances without real I/O.

import { GGTestFile } from "@grest-ts/schema-file/testkit"

Generators

// From raw data
GGTestFile.fromBuffer(new Uint8Array([1, 2, 3]), 'data.bin')
GGTestFile.fromString('Hello', 'hello.txt')
GGTestFile.fromBase64('SGVsbG8=', 'hello.txt')

// Random bytes (useful for size-limit tests)
GGTestFile.random(1024, 'random.bin')
GGTestFile.random(6 * 1024 * 1024, 'too-large.png', 'image/png')

// Valid minimal files
GGTestFile.png1x1()             // 1x1 transparent PNG
GGTestFile.jpeg1x1()            // 1x1 red JPEG
GGTestFile.pdf()                // empty-page PDF

// Structured data
GGTestFile.json({ key: 'value' }, 'config.json')
GGTestFile.csv([
    ['Name', 'Age'],
    ['Alice', '30']
], 'users.csv')

Test example

import { callOn, GGTest } from "@grest-ts/testkit"
import { GGTestFile } from "@grest-ts/schema-file/testkit"
import { VALIDATION_ERROR } from "@grest-ts/schema"

const api = callOn(FileUploadTestApi)

test('upload text file', async () => {
    const file = GGTestFile.fromString('Hello', 'hello.txt')

    await api
        .uploadFile({ file, description: 'greeting' })
        .toMatchObject({ fileName: 'hello.txt', size: 5 })
})

test('reject non-image uploads', async () => {
    const textFile = GGTestFile.fromString('not an image', 'doc.txt', 'text/plain')

    await api
        .uploadImage({ image: textFile })
        .toBeError(VALIDATION_ERROR)
})

test('reject oversized files', async () => {
    const large = GGTestFile.random(6 * 1024 * 1024, 'big.png', 'image/png')

    await api
        .uploadImage({ image: large })
        .toBeError(VALIDATION_ERROR)
})