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

@cariva/asr-sdk-node

v1.4.0

Published

Node.js SDK for Cariva ASR

Readme

@cariva/asr-sdk-node

npm version License Downloads Bundle Size TypeScript Node.js

Professional medical transcription SDK for Node.js server applications

🚀 Quick Start📖 API Reference💡 Examples🆘 Support

Overview

A professional Node.js SDK for medical speech recognition in server environments. Purpose-built for healthcare workflows with specialized medical vocabulary, file system integration, and secure processing.

Features

  • Medical-First Design - SOAP notes, clinical records, and nursing documentation
  • Server-Side Processing - File system integration with batch processing capabilities
  • High Performance - Optimized audio compression and server-side processing
  • Universal Audio Support - All major audio formats with automatic optimization
  • Production Ready - Built for scalable server environments and enterprise workflows
  • TypeScript Ready - Full type safety and comprehensive error handling

Installation

Package Manager

# npm
npm install @cariva/asr-sdk-node

# yarn
yarn add @cariva/asr-sdk-node

# pnpm
pnpm add @cariva/asr-sdk-node

# bun
bun add @cariva/asr-sdk-node

Requirements

  • Node.js: >= 20.0.0
  • FFmpeg: Optional (for advanced audio processing)

🚀 Quick Start

Basic Usage

import { ASR, type AsrResponseMap } from '@cariva/asr-sdk-node';

// Initialize with your credentials
const asr = new ASR({
  key: process.env.CARIVA_API_KEY!, // Use environment variables
  secret: process.env.CARIVA_API_SECRET!,
  mode: 'doc-ipd-soap',
  lang: 'en',
  specialty: 'gen-practitioner', // Default specialty
});

// Process audio file from file system
const processAudioFile = async (audioPath: string) => {
  try {
    // Process with comprehensive callbacks
    const result = await asr.process(audioPath, {
      onProcessing: step => {
        console.log(`Processing: ${step}`);
        // Steps: "Creating transcription task..." -> "Uploading audio file..." -> "Processing audio..."
      },
    });

    // Type-safe access to structured medical data
    console.log('Medical data:', result);
    return result;
  } catch (error) {
    console.error('ASR error:', error);
    throw error;
  }
};

// Example usage
await processAudioFile('./consultation-recording.wav');

Batch Processing

import fs from 'fs';
import path from 'path';

const processBatch = async (audioDir: string) => {
  const audioFiles = fs
    .readdirSync(audioDir)
    .filter(file => /\.(mp3|wav)$/i.test(file))
    .map(file => path.join(audioDir, file));

  const results = [];

  for (const filePath of audioFiles) {
    console.log(`Processing: ${path.basename(filePath)}`);

    try {
      const result = await asr.process(filePath);
      results.push({ file: filePath, result });
    } catch (error) {
      console.error(`Failed to process ${filePath}:`, error.message);
    }
  }

  return results;
};

Server Integration

import express from 'express';
import multer from 'multer';
import { ASR } from '@cariva/asr-sdk-node';

const app = express();
const upload = multer({ dest: 'uploads/' });

// Initialize ASR for different specialties
const asrInstances = {
  cardiology: new ASR({
    key: process.env.CARIVA_API_KEY!,
    secret: process.env.CARIVA_API_SECRET!,
    mode: 'doc-ipd-soap',
    specialty: 'med-cardio',
  }),
  general: new ASR({
    key: process.env.CARIVA_API_KEY!,
    secret: process.env.CARIVA_API_SECRET!,
    mode: 'doc-ipd-soap',
    specialty: 'gen-practitioner',
  }),
};

app.post('/transcribe/:specialty', upload.single('audio'), async (req, res) => {
  try {
    const { specialty } = req.params;
    const asr = asrInstances[specialty] || asrInstances.general;

    const result = await asr.process(req.file.path);
    res.json({ success: true, data: result });
  } catch (error) {
    res.status(500).json({ success: false, error: error.message });
  }
});

Medical Modes

The SDK provides specialized transcription modes optimized for different healthcare workflows:

Best for: Physician documentation, clinical assessments

const result: {
  fastTranscription: string; // Raw speech-to-text transcription
  subjective?: string; // Patient's reported symptoms and history
  objective?: string; // Clinical findings and observations
  assessment?: string; // Medical diagnosis and impressions
  plan?: string; // Treatment plan and follow-up
} = await asr.process('./consultation.wav');

Use cases: Hospital rounds, consultation notes, diagnostic evaluations

Best for: Outpatient consultations, comprehensive examinations

const result: {
  chiefComplaint: string;
  diagnosis: string;
  familyHistory: string;
  fastTranscription: string;
  pastMedicalHistory: string;
  physicalExamination: {
    generalAppearance: string;
    heent: string;
    cardiovascular: string;
    respiratory: string;
    gastrointestinal: string;
    neurological: string;
    other: string;
    vitalSign: string;
  };
  plan: string;
  presentIllness: string;
  rawNote: string;
  treatment: string;
  investigation: string;
} = await asr.process('./opd-consultation.wav');

Use cases: Outpatient visits, specialist consultations, routine check-ups

Best for: Beginning of shift assessments, patient handoffs

const result: {
  focus: string;
  assessment: string;
  intervention: string;
  fastTranscription: string;
  rawNote: string;
  vitalSigns: {
    heartRate: string;
    respiratoryRate: string;
    bloodPressure: string;
    bodyTemperature: string;
    oxygenSaturation: string;
  };
} = await asr.process('./nursenote-faie-start.wav');

Use cases: Shift handoffs, initial patient assessments, care planning

Best for: End of shift summaries, patient status updates

const result: {
  evaluation: string;
  fastTranscription: string;
  rawNote: string;
  vitalSigns: {
    heartRate: string;
    respiratoryRate: string;
    bloodPressure: string;
    bodyTemperature: string;
    oxygenSaturation: string;
  };
} = await asr.process('./nursenote-faie-end.wav');

Use cases: Shift summaries, patient status reports, care continuity

Best for: Beginning of shift assessments, patient handoffs

const result: {
  fastTranscription: string;
  rawNote: string;
  assessment: {
    objective: string;
    subjective: string;
  };
  diagnosis: string;
  planning: string;
  intervention: string;
} = await asr.process('./nursenote-adpie-start.wav');

Use cases: Shift handoffs, initial patient assessments, care planning

Best for: End of shift summaries, patient status updates

const result: {
  fastTranscription: string;
  rawNote: string;
  evaluation: string;
} = await asr.process('./nursenote-adpie-end.wav');

Use cases: Shift summaries, patient status reports, care continuity

Medical Specialties

Improve transcription accuracy by up to 35% with specialty-specific medical vocabulary:

const cardiologyASR = new ASR({
  key: process.env.CARIVA_API_KEY!,
  secret: process.env.CARIVA_API_SECRET!,
  mode: 'doc-ipd-soap',
  specialty: 'med-cardio', // Optimized for cardiology terminology
});

Primary Care & General Medicine

| Specialty | Code | Best For | | ------------------------ | ------------------ | ------------------------------------ | | General Practitioner | gen-practitioner | Family medicine, routine checkups | | General Medicine | gen-med | Internal medicine, hospitalist notes | | Emergency Medicine | emergency | Trauma, urgent care, triage | | General Pediatrics | gen-ped | Children's healthcare |

Cardiovascular & Respiratory

| Specialty | Code | Best For | | --------------- | --------------- | ------------------------------------- | | Cardiology | med-cardio | Cardiac procedures, heart conditions | | Pulmonology | med-pulmonary | Respiratory conditions, lung function |

Gastroenterology & Specialties

| Specialty | Code | Best For | | -------------------- | ------------ | ------------------------------------------ | | Gastroenterology | med-gastro | GI procedures, digestive disorders | | Neurology | med-neuro | Neurological assessments, brain conditions | | Nephrology | med-nephro | Kidney conditions, dialysis | | Oncology | med-onco | Cancer treatment, oncology consultations |

Surgical & Other Specialties

| Specialty | Code | Best For | | --------------------------- | ---------------- | ------------------------------------------ | | General Surgery | surgery | Surgical procedures, operative notes | | Orthopedic | orthopedic | Bone, joint, and muscle conditions | | Dermatology | med-skin | Skin conditions, dermatological procedures | | Ophthalmology | ophthalmology | Eye conditions, vision care | | Otolaryngology | otolaryngology | ENT procedures, hearing, speech | | Obstetrics & Gynecology | ob-gyn | Women's health, pregnancy, childbirth |

💡 Pro Tip: Choose the most specific specialty for your use case to maximize transcription accuracy.

📖 API Reference

Constructor Options

interface ASRConfig {
  key: string; // Required: Your Cariva API key
  secret: string; // Required: Your Cariva API secret
  mode: Mode; // Required: Transcription mode
  lang?: string; // Optional: Language (default: 'en')
  specialty?: MedicalSpecialty; // Optional: Medical specialty (default: 'gen-practitioner')
  timeout?: number; // Optional: Request timeout in ms (default: 300000)
}

// Usage
const asr = new ASR({
  key: process.env.CARIVA_API_KEY!,
  secret: process.env.CARIVA_API_SECRET!,
  mode: 'doc-ipd-soap',
  lang: 'en',
  specialty: 'med-cardio',
  timeout: 300000, // 5 minutes
});

Process Method

async process(
  filePath: string,               // Path to audio file
  options?: {
    onProcessing?: (step: string) => void;  // Processing callback
  }
): Promise<AsrResponseMap[Mode]['data']>

Processing Options

interface ProcessOptions {
  onProcessing?: (step: string) => void; // Optional: Processing step callback
}

// Processing steps you'll receive:
// - "Creating transcription task..."
// - "Uploading audio file..."
// - "Processing audio..."

Response Types

// Fully typed responses based on selected mode
type AsrResponseMap = {
  'doc-ipd-soap': {
    data: {
      fastTranscription: string;
      subjective?: string;
      objective?: string;
      assessment?: string;
      plan?: string;
    };
  };
  'doc-opd-clinicalrecord': {
    data: {
      chiefComplaint: string;
      diagnosis: string;
      familyHistory: string;
      fastTranscription: string;
      pastMedicalHistory: string;
      physicalExamination: {
        generalAppearance: string;
        heent: string;
        cardiovascular: string;
        respiratory: string;
        gastrointestinal: string;
        neurological: string;
        other: string;
        vitalSign: string;
      };
      plan: string;
      presentIllness: string;
      rawNote: string;
      treatment: string;
      investigation: string;
    };
  };
  // ... other modes
};

Instance Methods

// Main processing method
async process(file: string | File | Blob, options?: NodeProcessOptions): Promise<AsrResponseMap[Mode]['data']>

// Cancel ongoing transcription
async cancelASRTask(): Promise<void>

// Rate transcription quality (1-5 scale with optional comment and categories)
async rateASRTask(score: number, comment?: string, categories?: string[]): Promise<void>

// Initialize ASR instance
async create(): Promise<this>

// Clear task state
clearTask(): void

// Getters for current state
readonly taskId: string          // Current task ID (empty until processing starts)
readonly status: string          // Current processing status
readonly isProcessing: boolean   // Whether currently processing

Audio Support

Supported Formats

  • Audio: WAV, MP3, MP4, M4A, OGG, AAC, FLAC
  • File System: Direct file path processing
  • Compression: Server-side optimization using node-lame

File Constraints

  • Server-side audio compression reduces file size automatically
  • Real-time progress tracking during upload

Audio Processing

// The SDK automatically handles:
// - Format validation
// - Audio compression (MP3 encoding)
// - Duration detection
// - Progress tracking

const result = await asr.process('./consultation.wav', {
  onProcessing: step => {
    console.log(`Processing: ${step}`);
    // Steps: "Creating transcription task..." -> "Uploading audio file..." -> "Processing audio..."
  },
});

Server Features

File System Integration

import fs from 'fs';
import path from 'path';

// Process multiple files
const audioDir = './recordings/';
const files = fs
  .readdirSync(audioDir)
  .filter(file => file.endsWith('.mp3') || file.endsWith('.wav') || file.endsWith('.m4a'));

for (const file of files) {
  const filePath = path.join(audioDir, file);
  console.log(`Processing: ${file}`);

  try {
    const result = await asr.process(filePath, {
      onProcessing: step => console.log(`${file}: ${step}`),
    });

    // Save results
    const outputPath = path.join('./results/', `${path.parse(file).name}.json`);
    fs.writeFileSync(outputPath, JSON.stringify(result, null, 2));
  } catch (error) {
    console.error(`Failed to process ${file}:`, error.message);
  }
}

File Metadata

import fs from 'fs';

// Get file information before processing
const audioPath = './consultation.wav';
const stats = fs.statSync(audioPath);

console.log(`File size: ${(stats.size / 1024 / 1024).toFixed(2)} MB`);
console.log(`Processing: ${audioPath}`);

const result = await asr.process(audioPath);

Usage Examples

ES Modules

import { ASR } from '@cariva/asr-sdk-node';

const asr = new ASR({
  key: process.env.CARIVA_API_KEY!,
  secret: process.env.CARIVA_API_SECRET!,
  mode: 'doc-ipd-soap',
  specialty: 'med-cardio',
});

CommonJS

const { ASR } = require('@cariva/asr-sdk-node');

const asr = new ASR({
  key: process.env.CARIVA_API_KEY,
  secret: process.env.CARIVA_API_SECRET,
  mode: 'doc-ipd-soap',
  specialty: 'gen-practitioner',
});

TypeScript

import { ASR, type AsrResponseMap } from '@cariva/asr-sdk-node';

const asr = new ASR({
  key: process.env.CARIVA_API_KEY!,
  secret: process.env.CARIVA_API_SECRET!,
  mode: 'doc-ipd-soap',
  specialty: 'med-cardio',
});

// Fully typed result
const result: AsrResponseMap['doc-ipd-soap']['data'] = await asr.process('./audio.wav');

Progress Tracking

Monitor the transcription process with detailed callbacks:

const result = await asr.process('./consultation.wav', {
  onProcessing: step => {
    // Processing steps
    console.log(`Step: ${step}`);
    // Possible steps:
    // - "Creating transcription task..."
    // - "Uploading audio file..."
    // - "Processing audio..."
  },
});

Error Handling

import { AsrError } from '@cariva/asr-sdk-node';

try {
  const result = await asr.process('./audio.wav');
} catch (error) {
  if (error instanceof AsrError) {
    console.error(`ASR Error [${error.code}]:`, error.message);
    console.error('Status:', error.status);
    console.error('Details:', error.details);

    // Handle specific error types
    switch (error.code) {
      case 'NETWORK_ERROR':
        console.error('Check your internet connection');
        break;
      default:
        console.error('Unexpected ASR error occurred');
    }
  } else {
    console.error('Unexpected error:', error);
  }
}

Common Error Codes

  • CREATE_TASK_FAILED - Failed to create transcription task

  • UPLOAD_FAILED - Audio upload failed

  • UPLOAD_ABORTED - Upload was cancelled or aborted

  • TRANSCRIPTION_FAILED - Audio processing failed

  • GET_STATUS_FAILED - Failed to retrieve task status

  • PROCESS_FAILED - General processing failure

  • PROCESSING_TIMED_OUT - General processing timeout

  • NO_RESULT_DATA - No transcription data available

  • RATING_FAILED - Failed to submit rating

  • NO_TASK_ID - No task ID available for operation

  • NO_API_CLIENT - API client not initialized

  • NO_UPLOAD_METHOD - No upload method available

  • NETWORK_ERROR - Connection or network issues

  • CONSENT_REQUIRED - Your account has not accepted the latest Terms and Conditions and/or Privacy Policy. Please log in at http://space.cariva.co.th to review and accept them to continue.

  • INVALID_FILE_TYPE - Unsupported audio format

  • ENOENT - File not found

  • VALIDATION_ERROR - Invalid input parameters

  • PROCESSING_ERROR - Audio processing error

  • INVALID_SCORE - Invalid rating score (must be 1-5)

Cancellation

Cancel long-running transcription tasks:

// Start transcription
const transcriptionPromise = asr.process('./long-audio.wav');

// Cancel after 5 minutes using instance method
const timeoutId = setTimeout(
  async () => {
    await asr.cancelASRTask();
    console.log('Transcription cancelled due to timeout');
  },
  5 * 60 * 1000
);

try {
  const result = await transcriptionPromise;
  clearTimeout(timeoutId);
  console.log('Transcription completed:', result);
} catch (error) {
  clearTimeout(timeoutId);
  console.error('Transcription failed or was cancelled:', error.message);
}

Rating Transcriptions

Rate transcription quality to help improve the service:

// Rate transcription quality (1-5 scale)
await asr.rateASRTask(5, 'Excellent medical transcription accuracy!');

// Rate with just a score
await asr.rateASRTask(4);

// Rate with detailed feedback
await asr.rateASRTask(3, 'Good overall, but missed some medical terminology');

// Rate with comment and categories
await asr.rateASRTask(4, 'Good transcription', ['quality-improvement', 'accuracy']);

// Rate with categories only
await asr.rateASRTask(5, undefined, ['positive-feedback', 'excellent-quality']);

// Rate with multiple categories
await asr.rateASRTask(4, 'Good overall', ['medical-terminology', 'structure', 'completeness']);

Rating Guidelines

  • 5 Stars: Perfect transcription, no errors
  • 4 Stars: Minor errors, easily corrected
  • 3 Stars: Some errors, but mostly accurate
  • 2 Stars: Multiple errors, needs significant correction
  • 1 Star: Poor quality, major errors throughout

Complete Example

Medical Transcription Service

import { ASR } from '@cariva/asr-sdk-node';
import fs from 'fs/promises';
import path from 'path';

class MedicalTranscriptionService {
  private asrInstances: Record<string, ASR> = {};

  constructor() {
    // Initialize different ASR instances for different specialties
    this.asrInstances = {
      cardiology: new ASR({
        key: process.env.CARIVA_API_KEY!,
        secret: process.env.CARIVA_API_SECRET!,
        mode: 'doc-ipd-soap',
        specialty: 'med-cardio',
      }),
      pulmonology: new ASR({
        key: process.env.CARIVA_API_KEY!,
        secret: process.env.CARIVA_API_SECRET!,
        mode: 'doc-ipd-soap',
        specialty: 'med-pulmonary',
      }),
      general: new ASR({
        key: process.env.CARIVA_API_KEY!,
        secret: process.env.CARIVA_API_SECRET!,
        mode: 'doc-ipd-soap',
        specialty: 'gen-practitioner',
      }),
    };
  }

  async processConsultation(audioPath: string, specialty: string = 'general') {
    const asr = this.asrInstances[specialty] || this.asrInstances.general;

    console.log(`Processing consultation audio: ${path.basename(audioPath)}`);
    console.log(`Using specialty: ${specialty}`);

    const result = await asr.process(audioPath, {
      onProcessing: step => {
        console.log(`📋 ${step}`);
      },
    });

    // Generate comprehensive report
    const report = {
      timestamp: new Date().toISOString(),
      audioFile: path.basename(audioPath),
      specialty: specialty,
      transcription: result,
    };

    // Save to file
    const reportPath = `./reports/${path.parse(audioPath).name}-${specialty}-report.json`;
    await fs.writeFile(reportPath, JSON.stringify(report, null, 2));

    console.log(`✅ Report saved: ${reportPath}`);
    return report;
  }

  async processBatch(audioDir: string, specialty: string = 'general') {
    const files = await fs.readdir(audioDir);
    const audioFiles = files.filter(file => /\.(wav|mp3|m4a)$/i.test(file));

    console.log(`📁 Found ${audioFiles.length} audio files to process`);

    const results = [];
    for (const file of audioFiles) {
      const filePath = path.join(audioDir, file);
      try {
        const result = await this.processConsultation(filePath, specialty);
        results.push(result);
      } catch (error) {
        console.error(`❌ Failed to process ${file}:`, error.message);
      }
    }

    return results;
  }
}

// Usage
const service = new MedicalTranscriptionService();

// Process single file
await service.processConsultation('./recordings/cardiology-consult.wav', 'cardiology');

// Process batch
await service.processBatch('./recordings/', 'general');

Express.js API Server

import express from 'express';
import multer from 'multer';
import { ASR } from '@cariva/asr-sdk-node';

const app = express();
const upload = multer({ dest: 'uploads/' });

// Configure ASR instances for different specialties
const asrConfig = {
  key: process.env.CARIVA_API_KEY!,
  secret: process.env.CARIVA_API_SECRET!,
  mode: 'doc-ipd-soap' as const,
};

const asrInstances = {
  cardiology: new ASR({ ...asrConfig, specialty: 'med-cardio' }),
  pulmonology: new ASR({ ...asrConfig, specialty: 'med-pulmonary' }),
  general: new ASR({ ...asrConfig, specialty: 'gen-practitioner' }),
};

app.post('/api/transcribe/:specialty?', upload.single('audio'), async (req, res) => {
  try {
    const specialty = req.params.specialty || 'general';
    const asr = asrInstances[specialty] || asrInstances.general;

    if (!req.file) {
      return res.status(400).json({
        success: false,
        error: 'No audio file provided',
      });
    }

    console.log(`Processing ${req.file.originalname} with ${specialty} specialty`);

    const result = await asr.process(req.file.path, {
      onProcessing: step => {
        console.log(`[${req.file.originalname}] ${step}`);
      },
    });

    // Clean up uploaded file
    await fs.unlink(req.file.path);

    res.json({
      success: true,
      data: {
        filename: req.file.originalname,
        specialty: specialty,
        transcription: result,
        processedAt: new Date().toISOString(),
      },
    });
  } catch (error) {
    console.error('Transcription error:', error);
    res.status(500).json({
      success: false,
      error: error.message,
    });
  }
});

app.listen(3000, () => {
  console.log('🎙️  Medical transcription API server running on port 3000');
});

Node.js Compatibility

| Node.js Version | Support Status | Notes | | --------------- | ---------------- | ----------------------------- | | 20.x | ✅ Full Support | Recommended LTS (minimum) | | 21.x | ✅ Full Support | Current release | | 22.x | ✅ Full Support | Latest release | | 18.x | ❌ Not Supported | Upgrade to Node 20+ required | | 16.x | ❌ Not Supported | End of life, upgrade required |

Requirements

Runtime Requirements

  • Node.js: 20+ (ES2020+ support with modules)
  • File System: For handling file uploads and processing
  • Network: For API requests to Cariva services
  • Memory: Sufficient RAM for audio file processing

Development Requirements

  • TypeScript: 4.9+ (for type checking)
  • Build Tools: Compatible with ES modules and CommonJS

Production Requirements

  • Process Management: PM2, Docker, or similar for production deployment
  • Error Monitoring: Recommended for production applications
  • Log Management: Structured logging for troubleshooting

💡 Examples

Additional examples and integrations:

  • Express.js Integration - Complete REST API server
  • Microservice Architecture - Docker containerized service
  • Batch Processing Scripts - Command-line batch processors
  • Database Integration - Store results in MongoDB/PostgreSQL
  • Queue Processing - Redis/Bull queue integration

Related Packages

| Package | Purpose | | ------------------------------------------------------------------------------------ | ------------------------------ | | @cariva/asr-sdk-browser | Browser client-side processing |

🆘 Support

For technical support and inquiries, please visit our Contact Page.

Contributing

We welcome contributions! When reporting issues, please include:

  • Node.js version and OS
  • File size and type
  • Error messages and stack traces
  • Steps to reproduce

License

This project is licensed under the Apache License 2.0.

Copyright 2025 Cariva

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.