1.4.0 ⢠Published 8 months ago
mdfind-node v1.4.0
mdfind-node š
Supercharged macOS file and metadata search using
mdfind
for Node.js! āØ
A powerful Node.js wrapper for macOS's Spotlight search (mdfind
), bringing system-level search capabilities to your JavaScript applications.
⨠Features
- š Full Spotlight Integration - Access all macOS Spotlight search capabilities
- š Smart Query Builder - Type-safe, fluent API for building complex searches
- š Live Search Support - Real-time file monitoring and updates
- š¦ Batch Operations - Run multiple searches in parallel or sequence
- š Rich Metadata - Access EXIF, XMP, and system metadata
- šŖ Type-Safe - Full TypeScript support with detailed types
- š ļø Configurable - Extensive options for fine-tuned control
- š³ Type Trees - Support for content type hierarchies
- šØ Specialized Methods - Purpose-built methods for common file types
š„ Documentation
- Quick Start Guide - Get started with basic usage
- Query Builder - Learn about building complex queries
- Metadata - Working with file metadata
- Content Types - Understanding file type hierarchies
- Batch Operations - Processing multiple operations
- Advanced Topics - Performance, security, testing, and more
š„ Installation
# Using pnpm (recommended)
pnpm add mdfind-node
# Using npm
npm install mdfind-node
# Using yarn
yarn add mdfind-node
# Using bun
bun add mdfind-node
š Quick Start
import { QueryBuilder } from 'mdfind-node'
// Find recent Markdown files
const docs = await new QueryBuilder()
.isMarkdown()
.modifiedAfter(new Date('2024-01-01'))
.inDirectory('~/Documents')
.execute()
// Find high-resolution photos
const photos = await new QueryBuilder()
.contentType('public.image')
.minImageDimensions(3000, 2000)
.hasGPS()
.inDirectory('~/Pictures')
.execute()
// Find large media files
const videos = await new QueryBuilder()
.isAudiovisual()
.largerThan(100 * 1024 * 1024) // > 100MB
.inDirectory('~/Movies')
.execute()
šÆ Key Features
š Smart Query Builder
The QueryBuilder provides a fluent, type-safe API for building complex searches:
import { QueryBuilder } from 'mdfind-node'
// Find documents by author
const authorDocs = await new QueryBuilder()
.contentType('com.adobe.pdf')
.author('John Doe')
.createdAfter(new Date('2024-01-01'))
.inDirectory('~/Documents')
.execute()
// Find photos from a specific camera
const cameraPhotos = await new QueryBuilder()
.contentType('public.image')
.takenWith('iPhone 14 Pro')
.hasGPS()
.inDirectory('~/Pictures')
.execute()
// Find code files with specific content
const codeFiles = await new QueryBuilder()
.isText()
.extension('ts')
.containing('QueryBuilder')
.modifiedAfter(new Date('2024-01-01'))
.execute()
// Combine conditions with OR
const mediaFiles = await new QueryBuilder()
.useOperator('||')
.extension('mp4')
.extension('mov')
.extension('avi')
.largerThan(50 * 1024 * 1024)
.execute()
š Live Search
import { QueryBuilder, mdfindLive } from 'mdfind-node'
// Create a query to watch for new PDFs
const query = new QueryBuilder().isPDF().inDirectory('~/Downloads').toString()
const search = mdfindLive(
query,
{ reprint: true },
{
onResult: paths => console.log('New matches:', paths),
onError: error => console.error('Search error:', error),
onEnd: () => console.log('Search ended')
}
)
š¦ Batch Operations
import { QueryBuilder } from 'mdfind-node'
import { batchSearch } from 'mdfind-node'
// Create multiple queries
const searches = [
{
query: new QueryBuilder().contentType('public.image').minImageDimensions(1920, 1080).toString(),
options: { onlyInDirectory: '~/Pictures' }
},
{
query: new QueryBuilder()
.contentType('com.adobe.pdf')
.modifiedAfter(new Date('2024-01-01'))
.toString(),
options: { onlyInDirectory: '~/Documents' }
}
]
// Run them in parallel
const results = await batchSearch(searches)
// Or use utility functions for common patterns
import { mdfindMultiDirectory, mdfindMultiQuery } from 'mdfind-node'
// Search same query across multiple directories
const directoryResults = await mdfindMultiDirectory(
new QueryBuilder().contentType('public.image').toString(),
['~/Pictures', '~/Documents']
)
// Search multiple queries in one directory
const queryResults = await mdfindMultiQuery(
[
new QueryBuilder().contentType('public.image').toString(),
new QueryBuilder().contentType('com.adobe.pdf').toString()
],
'~/Documents'
)
š Extended Metadata
import { getExtendedMetadata } from 'mdfind-node'
const metadata = await getExtendedMetadata('photo.jpg')
console.log('Basic:', metadata.basic)
console.log('EXIF:', metadata.exif)
console.log('XMP:', metadata.xmp)
šÆ Specialized Search Methods
The QueryBuilder includes specialized methods for common file types:
import { QueryBuilder } from 'mdfind-node'
// Find text files
const textFiles = await new QueryBuilder().isText().modifiedAfter(new Date('2024-01-01')).execute()
// Find media files
const mediaFiles = await new QueryBuilder()
.isAudiovisual()
.largerThan(10 * 1024 * 1024)
.execute()
// Find applications
const apps = await new QueryBuilder().isApplication().inDirectory('/Applications').execute()
// Find configuration files
const configs = await new QueryBuilder().isPlist().inDirectory('~/Library/Preferences').execute()
// Find development files
const devFiles = await new QueryBuilder()
.useOperator('||')
.isText()
.isMarkdown()
.isJSON()
.isYAML()
.inDirectory(process.cwd())
.execute()
š Attribute Discovery
Spotlight attributes can be complex to work with. This library provides utilities to help you discover and understand available attributes:
import { discoverAttributes, searchAttributes, getContentTypes } from 'mdfind-node'
// Get all available attributes for a specific file
const fileAttributes = discoverAttributes('path/to/file.jpg')
// Search for attributes by keyword
const imageAttrs = searchAttributes('image')
console.log(imageAttrs)
// [
// {
// name: 'kMDItemPixelHeight',
// description: 'Height of the image in pixels',
// type: 'number',
// example: 1080,
// category: 'image'
// },
// ...
// ]
// Get all known content types
const contentTypes = getContentTypes()
console.log(contentTypes['public.image']) // 'Image files (JPEG, PNG, etc.)'
// Get attributes by category
const locationAttrs = getAttributesByCategory('location')
Content Type Trees
Files in macOS are organized in a type hierarchy. For example:
public.item
āāā public.content
āāā public.text
ā āāā public.plain-text
ā āāā net.daringfireball.markdown
āāā public.audiovisual-content
āāā public.audio
āāā public.movie
Common Content Types
Basic Types:
public.item
- Base type for all itemspublic.content
- Base type for all contentpublic.text
- Text-based contentpublic.composite-content
- Content with multiple parts
Documents:
public.plain-text
- Plain text filespublic.rtf
- Rich Text Format documentscom.adobe.pdf
- Adobe PDF Documentnet.daringfireball.markdown
- Markdown Document
Media:
public.image
- Image files (JPEG, PNG, etc.)public.audio
- Audio files (MP3, WAV, etc.)public.movie
- Video files (MP4, MOV, etc.)public.audiovisual-content
- Audio/Visual content
Code:
public.source-code
- Source Code Filepublic.shell-script
- Shell Scriptpublic.json
- JSON Filepublic.yaml
- YAML File
System:
com.apple.bundle
- Generic Bundlecom.apple.application
- Generic Applicationcom.apple.property-list
- Property List (plist)
Common Attributes
kMDItemContentType
- The type of contentkMDItemContentTypeTree
- The content type hierarchykMDItemDisplayName
- The display name of the filekMDItemFSName
- The filename on diskkMDItemContentCreationDate
- When the file was createdkMDItemContentModificationDate
- When the file was last modifiedkMDItemPixelHeight
- Height of the image in pixelskMDItemPixelWidth
- Width of the image in pixelskMDItemLatitude
- GPS latitude where photo/video was takenkMDItemLongitude
- GPS longitude where photo/video was taken
š©āš» Development
# Install dependencies
pnpm install
# Development build with watch mode
pnpm dev
# Production build
pnpm build
# Run tests
pnpm test
# Run examples
pnpm examples:basic
pnpm examples:advanced
pnpm examples:query-builder
pnpm examples:live-search
pnpm examples:metadata
pnpm examples:batch
pnpm examples:discover
pnpm examples:content-types
š License
ISC Ā© Matthew Herod