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 🙏

© 2025 – Pkg Stats / Ryan Hefner

tsndef

v1.0.0

Published

A modern, type-safe TypeScript library for creating and parsing NDEF (NFC Data Exchange Format) messages with full compile-time type checking and intelligent autocompletion.

Downloads

6

Readme

📱 tsndef

npm version npm downloads bundle License

A modern, type-safe TypeScript library for creating and parsing NDEF (NFC Data Exchange Format) messages with full compile-time type checking and intelligent autocompletion.

✨ Features

  • 🔒 Type-Safe: Full TypeScript support with compile-time type checking
  • 🚀 Modern: Built with modern TypeScript features and ES modules
  • 📦 Lightweight: No dependencies and tree-shakable
  • 🔧 Comprehensive: Support for most common NDEF record types
  • 🎯 Intelligent: Smart type inference and autocompletion
  • 🛡️ Robust: Comprehensive error handling and validation
  • ⚡ Fast: Optimized parsing and serialization
  • 🧪 Well-Tested: Extensive test coverage

🏆 Advantages

Type Safety First

Unlike other NDEF libraries, tsndef provides compile-time type checking for all NDEF operations. Know exactly what record types you're working with before runtime.

Modern Architecture

  • Built with modern TypeScript (ES2022+)
  • Tree-shakable - only bundle what you use
  • Zero dependencies for core functionality
  • Immutable-style API for better developer experience

Intelligent Type Inference

The library tracks record types at compile time, providing intelligent autocompletion and preventing runtime errors:

const message = new NDEFMessage()
  .add(createNDEFRecordWellKnownURI({ payload: 'https://example.com' }))
  .add(createNDEFRecordMediaApplicationJson({ payload: { hello: 'world' } }))

// TypeScript automatically knows the exact types
const uriRecord = message.records[0] // Type: NDEFRecordWellKnownURI
const jsonRecord = message.records[1] // Type: NDEFRecordMediaApplicationJson

📦 Installation

# npm
npm install tsndef

# yarn
yarn add tsndef

# pnpm
pnpm add tsndef

🚀 Usage

Creating NDEF Messages

Basic URI Record

import { createNDEFRecordWellKnownURI, NDEFMessage } from 'tsndef'

// Create a simple URI record
const message = new NDEFMessage()
  .add(createNDEFRecordWellKnownURI({
    payload: 'https://example.com'
  }))

// Convert to bytes for NFC writing
const bytes = await message.toBytes()

JSON Data Record

import { createNDEFRecordMediaApplicationJson, NDEFMessage } from 'tsndef'

const jsonMessage = new NDEFMessage()
  .add(createNDEFRecordMediaApplicationJson({
    payload: {
      productId: 12345,
      name: 'Awesome Product',
      price: 29.99,
      inStock: true
    }
  }))

Text Records

import { createNDEFRecordMediaTextPlain, NDEFMessage } from 'tsndef'

const textMessage = new NDEFMessage()
  .add(createNDEFRecordMediaTextPlain({
    payload: 'Hello, NFC World! 🌍'
  }))

Complex Multi-Record Message

import {
  createNDEFRecordMediaApplicationJson,
  createNDEFRecordMediaTextPlain,
  createNDEFRecordWellKnownURI,
  NDEFMessage
} from 'tsndef'

const complexMessage = new NDEFMessage()
  .add(createNDEFRecordWellKnownURI({
    payload: 'https://myapp.com/product/123'
  }))
  .add(createNDEFRecordMediaApplicationJson({
    payload: {
      action: 'view_product',
      productId: 123,
      timestamp: Date.now()
    }
  }))
  .add(createNDEFRecordMediaTextPlain({
    payload: 'Scan this tag to view product details'
  }))

// Convert to bytes for NFC tag writing
const nfcBytes = await complexMessage.toBytes()

Reading NDEF Messages

Basic Parsing

import { parseNDEFMessage } from 'tsndef'

// Parse bytes received from NFC tag
const nfcBytes = new Uint8Array([/* raw bytes from NFC tag */])
const message = parseNDEFMessage(nfcBytes)

console.log(`Found ${message.length} records`)

Safe Parsing (No Exceptions)

import { safeParseNDEFMessage } from 'tsndef'

const result = safeParseNDEFMessage(nfcBytes)

if (result.success) {
  console.log('Parsed successfully:', result.message)
}
else {
  console.error('Parsing failed:', result.error)
}

Type-Safe Record Processing

const message = parseNDEFMessage(nfcBytes)

for (const record of message.records) {
  switch (record.tnf) {
    case 'well-known':
      if (record.type === 'U') {
        console.log('Found URI:', await record.payload()) // Full URI string
      }
      break

    case 'media':
      if (record.type === 'application/json') {
        const data = await record.payload() // Parsed JSON object
        console.log('JSON data:', data)
      }
      else if (record.type === 'text/plain') {
        console.log('Text content:', await record.payload())
      }
      break

    default:
      console.log('Unknown record type:', record.type)
      console.log('Raw payload:', await record.rawPayload())
  }
}

Supported Record Types

Well-Known Records

  • URI Records: createNDEFRecordWellKnownURI()
    • Supports all standard URI prefixes (http, https, tel, mailto, etc.)
    • Automatic prefix optimization for smaller tag sizes

Media Records

  • JSON: createNDEFRecordMediaApplicationJson()
  • Plain Text: createNDEFRecordMediaTextPlain()
  • HTML: createNDEFRecordMediaTextHTML()
  • Images: createNDEFRecordMediaImagePNG(), createNDEFRecordMediaImageJPEG()
  • Video: createNDEFRecordMediaVideoMP4()
  • Audio: createNDEFRecordMediaAudioMPEG()

⚠️ Important: Type Inference and Immutability

Type Inference Limitations

When working with tsndef, it's important to understand how TypeScript's type inference works with our immutable-style API. Adding or removing records after initial variable assignment may interfere with correct type inference.

❌ Problematic Pattern

// Initial assignment with inferred type
const message = new NDEFMessage()
  .add(createNDEFRecordWellKnownURI({ payload: 'https://example.com' }))

// TypeScript infers: NDEFMessage<[NDEFRecordWellKnownURI<https://example.com>]>

// Later modifications lose precise type information
message = message.remove()
// TypeScript now still sees: NDEFMessage<[NDEFRecordWellKnownURI<https://example.com>]> - lost precise typing!

// Type information is no longer accurate
const firstPayload = await message.records[0].payload() // Valid access to typescript, but results in runtime error as the record was removed

✅ Recommended Patterns

Option 1: Build the complete message in one chain

const message = new NDEFMessage()
  .add(createNDEFRecordWellKnownURI({ payload: 'https://example.com' }))
  .add(createNDEFRecordMediaApplicationJson({ payload: { id: 123 } }))
  .add(createNDEFRecordMediaTextPlain({ payload: 'Description' }))

// TypeScript maintains precise type: NDEFMessage<[NDEFRecordWellKnownURI, NDEFRecordMediaApplicationJson, NDEFRecordMediaTextPlain]>
const uriRecord = message.records[0] // Type: NDEFRecordWellKnownURI ✓
const jsonRecord = message.records[1] // Type: NDEFRecordMediaApplicationJson ✓

Option 2: Reassign after modifications

// Create new instances to maintain type safety
let message = new NDEFMessage()
  .add(createNDEFRecordWellKnownURI({ payload: 'https://example.com' }))

message = message
  .add(createNDEFRecordMediaApplicationJson({ payload: { id: 123 } }))

Why This Matters

Maintaining precise type information allows you to:

  • Get accurate autocompletion when accessing record properties
  • Catch type errors at compile time
  • Leverage TypeScript's powerful type system for safer NFC operations
  • Ensure your code is more maintainable and less prone to runtime errors

🧪 Testing

# Run tests
pnpm test

# Run tests in watch mode
pnpm test --watch

# Run linting
pnpm lint

# Fix linting issues
pnpm lint:fix

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

📄 License

MIT License - see the LICENSE file for details.