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

@ariadng/sheets

v0.1.1

Published

A pragmatic TypeScript client for Google Sheets API v4

Readme

Sheets

A pragmatic TypeScript client for Google Sheets API v4 with stellar performance, that provides essential features without over-engineering. Start simple and add complexity only when needed.

Features

  • Progressive Enhancement - Start with core features, add more as needed
  • Modular Packages - Core (~3KB), Plus (~5KB), Advanced (~2KB)
  • Smart Retry Logic - Exponential backoff with jitter
  • Optional Caching - Reduce API calls with configurable TTL
  • Type Safety - Full TypeScript support with type utilities
  • Rate Limiting - Adaptive or token bucket strategies
  • Metrics Collection - Track performance and errors
  • Batch Operations - Efficient bulk reads/writes

Installation

# Core package only
npm install @ariadng/sheets

# With specific packages
npm install @ariadng/sheets

Quick Start

Basic Usage

import { GoogleSheetsCore, createServiceAccountAuth } from '@ariadng/sheets/core';

// Setup with service account
const auth = await createServiceAccountAuth('./service-account-key.json');
const sheets = new GoogleSheetsCore({ auth });

// Simple read
const data = await sheets.read('spreadsheetId', 'Sheet1!A1:B10');
console.log(data); // [[value1, value2], [value3, value4], ...]

// Simple write
await sheets.write('spreadsheetId', 'Sheet1!A1:B2', [
  ['Name', 'Score'],
  ['Alice', 100]
]);

With Caching

import { withCache } from '@ariadng/sheets/plus';

const cachedSheets = withCache(sheets, {
  ttlSeconds: 300 // 5-minute cache
});

// First read hits API
const data1 = await cachedSheets.read(id, 'A1:B10');

// Second read uses cache (within 5 minutes)
const data2 = await cachedSheets.read(id, 'A1:B10'); // Much faster!

With Rate Limiting

import { withAdaptiveRateLimit } from '@ariadng/sheets/advanced';

const rateLimitedSheets = withAdaptiveRateLimit(sheets);

// Automatically handles rate limits
for (let i = 0; i < 1000; i++) {
  await rateLimitedSheets.read(id, `A${i}`);
  // Automatically slows down if hitting limits
}

Authentication

Service Account

import { createServiceAccountAuth } from '@ariadng/sheets/core';

// From file
const auth = await createServiceAccountAuth('./service-account.json');

// From object
const auth = await createServiceAccountAuth({
  client_email: '[email protected]',
  private_key: '-----BEGIN PRIVATE KEY-----...',
  // ... other fields
});

OAuth2

import { createOAuth2Client, generateAuthUrl, getTokenFromCode } from '@ariadng/sheets/core';

const client = await createOAuth2Client({
  client_id: 'your-client-id',
  client_secret: 'your-client-secret',
  redirect_uris: ['http://localhost:3000/callback']
});

// Generate auth URL
const authUrl = generateAuthUrl(client);
console.log('Visit:', authUrl);

// After user authorizes, exchange code for token
const tokens = await getTokenFromCode(client, authorizationCode);

Core Features

CRUD Operations

// Read
const values = await sheets.read(spreadsheetId, 'Sheet1!A1:C10');

// Write (replaces existing data)
await sheets.write(spreadsheetId, 'Sheet1!A1:C3', [
  ['Header1', 'Header2', 'Header3'],
  ['Value1', 'Value2', 'Value3']
]);

// Append (adds to end)
await sheets.append(spreadsheetId, 'Sheet1!A:C', [
  ['New1', 'New2', 'New3']
]);

// Clear
await sheets.clear(spreadsheetId, 'Sheet1!A1:C10');

Batch Operations

// Batch read multiple ranges
const results = await sheets.batchRead(spreadsheetId, [
  'Sheet1!A1:B10',
  'Sheet2!C1:D5'
]);

// Batch write multiple ranges
await sheets.batchWrite(spreadsheetId, [
  { range: 'Sheet1!A1:B2', values: [['A', 'B'], ['C', 'D']] },
  { range: 'Sheet2!A1:B2', values: [['E', 'F'], ['G', 'H']] }
]);

Plus Package Features

Type Utilities

import { A1, TypedSheets, Parsers, Serializers } from '@ariadng/sheets/plus';

// A1 notation utilities
const col = A1.columnToIndex('C'); // 2
const letter = A1.indexToColumn(2); // 'C'
const range = A1.build('Sheet1', 'A', 1, 'C', 10); // 'Sheet1!A1:C10'

// Type-safe operations
interface User {
  name: string;
  email: string;
  age: number;
}

const typed = new TypedSheets<User[]>(sheets);
const users = await typed.read(
  spreadsheetId,
  'Users!A1:C100',
  Parsers.rowsToObjects<User>
);

// Data transformation
const objects = [
  { name: 'Alice', age: 30 },
  { name: 'Bob', age: 25 }
];
const rows = Serializers.objectsToRows(objects);
// [['name', 'age'], ['Alice', 30], ['Bob', 25]]

Batch Operations Manager

import { BatchOperations } from '@ariadng/sheets/plus';

const batchOps = new BatchOperations(sheets);

// Automatically chunks large batches
const operations = Array.from({ length: 500 }, (_, i) => ({
  range: `Sheet1!A${i}:B${i}`,
  values: [[`Row${i}`, `Value${i}`]]
}));

await batchOps.batchWrite(spreadsheetId, operations); // Handles chunking

Simple Cache

import { SimpleCache } from '@ariadng/sheets/plus';

const cache = new SimpleCache({
  ttlSeconds: 60,
  maxEntries: 100
});

cache.set('key', data);
const cached = cache.get('key');
cache.invalidate('pattern*'); // Wildcard invalidation
cache.clear(); // Clear all

Advanced Package Features

Adaptive Rate Limiting

import { AdaptiveRateLimiter } from '@ariadng/sheets/advanced';

const limiter = new AdaptiveRateLimiter();

// Automatically adjusts delay based on success/failure
await limiter.execute(async () => {
  return sheets.read(id, range);
});

// Get current stats
const stats = limiter.getStats();
console.log(stats); // { requestsInWindow: 45, successRate: 0.98, baseDelay: 0 }

Metrics Collection

import { withMetrics } from '@ariadng/sheets/advanced';

const metricsSheets = withMetrics(sheets);

// Use normally
await metricsSheets.read(id, range);
await metricsSheets.write(id, range, values);

// Get metrics
const metrics = metricsSheets.metrics.getMetrics();
console.log(metrics);
// {
//   totalRequests: 150,
//   successfulRequests: 148,
//   failedRequests: 2,
//   averageLatency: 123.5,
//   rateLimitHits: 1,
//   errorsByCode: Map { 429 => 1, 500 => 1 }
// }

const summary = metricsSheets.metrics.getSummary();
console.log(summary);
// {
//   successRate: 0.987,
//   requestsPerSecond: 2.5,
//   uptimeSeconds: 60
// }

Error Handling

import { GoogleSheetsError } from '@ariadng/sheets/core';

try {
  await sheets.read(spreadsheetId, range);
} catch (error) {
  if (error instanceof GoogleSheetsError) {
    console.error(`API Error ${error.code}: ${error.message}`);

    if (error.isRetryable) {
      console.log('This error is retryable');
    }

    if (error.isPermissionError()) {
      console.log('Share the sheet with service account');
    }

    if (error.isRateLimitError()) {
      console.log('Hit rate limit, slow down');
    }

    // User-friendly message
    console.log(error.getUserMessage());
  }
}

Common Patterns

Progressive Enhancement

import { GoogleSheetsCore } from '@ariadng/sheets/core';
import { withCache } from '@ariadng/sheets/plus';
import { withAdaptiveRateLimit, withMetrics } from '@ariadng/sheets/advanced';

// Start simple
let sheets = new GoogleSheetsCore({ auth });

// Add features as needed
sheets = withCache(sheets, { ttlSeconds: 300 });
sheets = withAdaptiveRateLimit(sheets);
sheets = withMetrics(sheets);

// Now have all features!

Data Processing Pipeline

import { Parsers, Serializers } from '@ariadng/sheets/plus';

// Read raw data
const raw = await sheets.read(id, 'Data!A1:Z1000');

// Parse to objects
const records = Parsers.rowsToObjects(raw);

// Process data
const processed = records
  .filter(r => r.status === 'active')
  .map(r => ({ ...r, processed: true }));

// Convert back to rows
const rows = Serializers.objectsToRows(processed);

// Write back
await sheets.write(id, 'Processed!A1', rows);

Efficient Batch Processing

import { BatchOperations } from '@ariadng/sheets/plus';

const batch = new BatchOperations(sheets);

// Mixed operations
const results = await batch.executeBatch(spreadsheetId, {
  reads: ['Summary!A1:Z1', 'Config!A1:B10'],
  writes: [
    { range: 'Output!A1:B100', values: outputData }
  ],
  clears: ['Temp!A:Z']
});

// All executed efficiently in parallel when possible

API Reference

Core Package

GoogleSheetsCore

  • constructor(config: GoogleSheetsConfig)
  • read(spreadsheetId: string, range: string): Promise<any[][]>
  • write(spreadsheetId: string, range: string, values: any[][]): Promise<UpdateValuesResponse>
  • append(spreadsheetId: string, range: string, values: any[][]): Promise<AppendValuesResponse>
  • clear(spreadsheetId: string, range: string): Promise<ClearValuesResponse>
  • batchRead(spreadsheetId: string, ranges: string[]): Promise<ValueRange[]>
  • batchWrite(spreadsheetId: string, data: BatchWriteData[]): Promise<BatchUpdateValuesResponse>
  • getSpreadsheet(spreadsheetId: string): Promise<Spreadsheet>
  • getApi(): sheets_v4.Sheets

Plus Package

A1 Utilities

  • A1.columnToIndex(column: string): number
  • A1.indexToColumn(index: number): string
  • A1.parse(notation: string): A1Components
  • A1.build(sheet?, startCol, startRow, endCol?, endRow?): string
  • A1.getDimensions(notation: string): { rows: number, columns: number }
  • A1.offset(notation: string, rowOffset: number, colOffset: number): string

Parsers

  • Parsers.rowsToObjects<T>(data: any[][]): T[]
  • Parsers.asNumbers(data: any[][]): number[][]
  • Parsers.asStrings(data: any[][]): string[][]
  • Parsers.asMap<V>(data: any[][]): Map<string, V>
  • Parsers.column<T>(data: any[][], columnIndex: number): T[]

Serializers

  • Serializers.objectsToRows<T>(objects: T[], headers?: string[]): any[][]
  • Serializers.mapToRows<K,V>(map: Map<K,V>): any[][]
  • Serializers.arrayToColumn<T>(array: T[]): any[][]
  • Serializers.transpose(data: any[][]): any[][]

Performance

| Operation | Base Latency | With Cache | With Rate Limit | |-----------|-------------|------------|-----------------| | Single Read | 100-200ms | 0-1ms (hit) | +0-50ms | | Batch Read (10) | 150-250ms | 0-1ms (hit) | +0-50ms | | Single Write | 150-300ms | N/A | +0-50ms | | Batch Write (100) | 300-500ms | N/A | +0-100ms |

Best Practices

  1. Use batch operations when reading/writing multiple ranges
  2. Enable caching for frequently read, rarely changed data
  3. Add rate limiting for bulk operations or scripts
  4. Use type utilities for better type safety and code clarity
  5. Handle errors gracefully with proper retry logic
  6. Monitor with metrics in production environments

Requirements

  • Node.js 14+
  • Google Sheets API enabled in Google Cloud Console
  • Service account or OAuth2 credentials

License

MIT

Contributing

Contributions are welcome! Please read our contributing guidelines before submitting PRs.

Support

For issues and feature requests, please use the GitHub issues page.