Skip to content

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.

License

Notifications You must be signed in to change notification settings

schplitt/tsndef

Repository files navigation

tsndef β€” Type-safe TypeScript NDEF (NFC) library

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.

  • βœ… Type-safe records (URI, JSON, text, media…)
  • πŸš€ Zero deps, tree-shakable ESM
  • πŸ§ͺ Well-tested parsing/serialization with robust errors

✨ 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.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

❓ FAQ

Which record types are supported?

tsndef supports the most common NDEF record types:

  • Well-Known: URI records with automatic prefix optimization
  • Media: JSON, plain text, HTML, images (PNG/JPEG), video (MP4), audio (MPEG)

All record types include full TypeScript type definitions for compile-time safety.

Is it safe to parse unknown records?

Yes! Use safeParseNDEFMessage() for error-safe parsing that won't throw exceptions. Unknown record types are preserved with their raw payload accessible via rawPayload(), so you never lose data.

How is this different from other NDEF libraries?

tsndef is the only NDEF library that provides:

  • Full compile-time type safety with TypeScript
  • Intelligent type inference that tracks exact record types
  • Zero dependencies and tree-shakable architecture
  • Modern ES modules with immutable-style API
  • Comprehensive error handling with both throwing and safe parsing options

πŸ§ͺ 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.

About

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.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •