Skip to content

dealnews/data-mapper-js-client

Repository files navigation

DataMapper API Client

A type-safe TypeScript client library for interacting with dealnews/data-mapper-api.

npm version License: BSD-3-Clause

Features

  • Fully Type-Safe - Complete TypeScript support with generics
  • Zero Dependencies - Uses native fetch API
  • Fluent Search API - Intuitive query builder with method chaining
  • Comprehensive Error Handling - Custom error classes for different scenarios
  • Dual Module Support - Works with both ESM and CommonJS
  • Functional Patterns - Immutable operations and pure functions
  • Well Tested - 99%+ test coverage

Installation

Node.js / npm

npm install @dealnews/data-mapper-client

Browser (via CDN)

You can use the library directly in the browser via a CDN:

<!DOCTYPE html>
<html>
<head>
  <title>DataMapper API Example</title>
</head>
<body>
  <script type="module">
    // Import from CDN (uses native ES modules)
    import { DataMapperClient } from 'https://cdn.jsdelivr.net/npm/@dealnews/data-mapper-client/dist/index.js';
    
    // Or from unpkg
    // import { DataMapperClient } from 'https://unpkg.com/@dealnews/data-mapper-client/dist/index.js';
    
    const client = new DataMapperClient({
      baseUrl: 'https://api.example.com',
    });
    
    const users = client.resource('User');
    
    // Fetch and display data
    users.search()
      .filter({ active: true })
      .limit(10)
      .execute()
      .then(results => {
        console.log('Users:', results);
        document.getElementById('results').textContent = JSON.stringify(results, null, 2);
      })
      .catch(error => {
        console.error('Error:', error);
      });
  </script>
  
  <h1>DataMapper API Results</h1>
  <pre id="results">Loading...</pre>
</body>
</html>

For a complete working example, see examples/browser-example.html.

Browser with Bundler (Vite, Webpack, etc.)

npm install @dealnews/data-mapper-client
// src/app.ts
import { DataMapperClient } from '@dealnews/data-mapper-client';

const client = new DataMapperClient({
  baseUrl: import.meta.env.VITE_API_URL, // or process.env for Webpack
});

const users = client.resource('User');

// Works with async/await
const fetchUsers = async () => {
  try {
    const results = await users.search()
      .filter({ active: true })
      .limit(10)
      .execute();
    return results;
  } catch (error) {
    console.error('Failed to fetch users:', error);
  }
};

Quick Start

import { DataMapperClient } from '@dealnews/data-mapper-client';

// Define your object type
interface User {
  user_id: number;
  name: string;
  email: string;
  active: boolean;
  created_at: string;
}

// Initialize the client
const client = new DataMapperClient({
  baseUrl: 'https://api.example.com',
  prefix: '/api/', // optional, defaults to '/api/'
});

// Create a resource handler
const users = client.resource<User>('User');

// Perform CRUD operations
const user = await users.create({
  name: 'John Doe',
  email: 'john@example.com',
});

const retrieved = await users.get(user.user_id);
const updated = await users.update(user.user_id, { active: false });
await users.delete(user.user_id);

API Reference

Client Initialization

const client = new DataMapperClient({
  baseUrl: string,           // Required: Base URL of your API
  prefix?: string,           // Optional: URL prefix (default: '/api/')
  headers?: Record<string, string>, // Optional: Additional headers
  fetch?: typeof fetch,      // Optional: Custom fetch implementation
});

CRUD Operations

Create

const created = await resource.create({
  name: 'New Object',
  // ... other fields
});
// Returns the complete object with server-generated fields

Read

const object = await resource.get(42);
// Throws NotFoundError if object doesn't exist

Update

const updated = await resource.update(42, {
  name: 'Updated Name',
  // Only include fields you want to change
});
// Returns the complete updated object

Delete

await resource.delete(42);
// Returns void on success

Search Operations

The library provides a fluent API for building complex search queries:

const results = await users.search()
  .filter({ active: true })
  .filter({ role: ['admin', 'moderator'] }) // IN clause
  .filter({ age: { '>=': 18 } }) // Comparison operators
  .filter({ created_at: { between: ['2021-01-01', '2021-12-31'] } })
  .sort({ created_at: 'desc' })
  .sort({ name: 'asc' })
  .start(50)   // Pagination offset
  .limit(25)   // Results per page
  .execute();

Filter Operators

  • Equality: { field: value }
  • IN Clause: { field: [value1, value2, ...] }
  • Greater Than: { field: { '>': value } }
  • Greater Than or Equal: { field: { '>=': value } }
  • Less Than: { field: { '<': value } }
  • Less Than or Equal: { field: { '<=': value } }
  • Between: { field: { between: [min, max] } }

Sort Directions

  • 'asc' - Ascending order
  • 'desc' - Descending order

Error Handling

The library provides custom error classes for better error handling:

import { NotFoundError, ValidationError, ApiError } from '@dealnews/data-mapper-client';

try {
  const user = await users.get(999);
} catch (error) {
  if (error instanceof NotFoundError) {
    console.log('User not found');
  } else if (error instanceof ValidationError) {
    console.log('Invalid request:', error.responseBody);
  } else if (error instanceof ApiError) {
    console.log(`API error ${error.statusCode}:`, error.message);
  }
}

Error Classes

  • DataMapperError - Base error class
  • NotFoundError - 404 errors
  • ValidationError - 400 errors
  • ApiError - Other HTTP errors

All errors include:

  • message - Error description
  • statusCode - HTTP status code
  • responseBody - Raw response from server

Advanced Usage

Browser-Specific: CORS and Authentication

When using the library in a browser, ensure your API server has proper CORS headers configured:

// Browser example with authentication and error handling
const client = new DataMapperClient({
  baseUrl: 'https://api.example.com',
  headers: {
    'Authorization': `Bearer ${localStorage.getItem('authToken')}`,
  },
});

const users = client.resource('User');

// Example: Fetch and render users in the DOM
async function loadUsers() {
  try {
    const results = await users.search()
      .filter({ active: true })
      .sort({ name: 'asc' })
      .limit(50)
      .execute();
    
    const list = document.getElementById('user-list');
    list.innerHTML = results
      .map(user => `<li>${user.name} (${user.email})</li>`)
      .join('');
  } catch (error) {
    if (error instanceof NotFoundError) {
      console.error('No users found');
    } else {
      console.error('Failed to load users:', error.message);
    }
  }
}

// Call on page load
document.addEventListener('DOMContentLoaded', loadUsers);

Custom Headers (Authentication)

const client = new DataMapperClient({
  baseUrl: 'https://api.example.com',
  headers: {
    'Authorization': 'Bearer your-token-here',
    'X-Custom-Header': 'value',
  },
});

Custom Fetch Implementation

Useful for Node.js environments or testing:

import fetch from 'node-fetch';

const client = new DataMapperClient({
  baseUrl: 'https://api.example.com',
  fetch: fetch as unknown as typeof globalThis.fetch,
});

Pagination Example

const pageSize = 50;
let page = 0;
let hasMore = true;

while (hasMore) {
  const results = await users.search()
    .filter({ active: true })
    .start(page * pageSize)
    .limit(pageSize)
    .execute();

  // Process results...

  hasMore = results.length === pageSize;
  page++;
}

Complex Search Example

// Find active premium users created in the last year
// who have more than 100 posts, sorted by activity
const premiumUsers = await users.search()
  .filter({
    active: true,
    subscription: 'premium',
    created_at: {
      '>=': new Date(Date.now() - 365 * 24 * 60 * 60 * 1000).toISOString(),
    },
    post_count: { '>': 100 },
  })
  .sort({
    last_activity: 'desc',
    created_at: 'desc',
  })
  .limit(100)
  .execute();

TypeScript Support

The library is written in TypeScript and provides full type safety:

interface Product {
  product_id: number;
  name: string;
  price: number;
  in_stock: boolean;
}

const products = client.resource<Product>('Product');

// TypeScript knows the return type
const product: Product = await products.get(1);

// TypeScript validates your data
await products.create({
  name: 'Widget',
  price: 29.99,
  in_stock: true,
  // @ts-expect-error - unknown field
  invalid_field: 'error',
});

Requirements

  • Node.js >= 18.0.0 (for native fetch support)
  • TypeScript >= 5.0 (if using TypeScript)

Development

# Install dependencies
npm install

# Run tests
npm test

# Run tests with coverage
npm run test:coverage

# Build the library
npm run build

# Lint code
npm run lint

# Format code
npm run format

Contributing

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

License

BSD 3-Clause License - See LICENSE file for details

Related

About

Typescript/Javascript client for the Data Mapper API

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors