A type-safe TypeScript client library for interacting with dealnews/data-mapper-api.
- ✅ Fully Type-Safe - Complete TypeScript support with generics
- ✅ Zero Dependencies - Uses native
fetchAPI - ✅ 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
npm install @dealnews/data-mapper-clientYou 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.
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);
}
};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);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
});const created = await resource.create({
name: 'New Object',
// ... other fields
});
// Returns the complete object with server-generated fieldsconst object = await resource.get(42);
// Throws NotFoundError if object doesn't existconst updated = await resource.update(42, {
name: 'Updated Name',
// Only include fields you want to change
});
// Returns the complete updated objectawait resource.delete(42);
// Returns void on successThe 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();- 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] } }
'asc'- Ascending order'desc'- Descending order
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);
}
}DataMapperError- Base error classNotFoundError- 404 errorsValidationError- 400 errorsApiError- Other HTTP errors
All errors include:
message- Error descriptionstatusCode- HTTP status coderesponseBody- Raw response from server
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);const client = new DataMapperClient({
baseUrl: 'https://api.example.com',
headers: {
'Authorization': 'Bearer your-token-here',
'X-Custom-Header': 'value',
},
});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,
});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++;
}// 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();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',
});- Node.js >= 18.0.0 (for native
fetchsupport) - TypeScript >= 5.0 (if using TypeScript)
# 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 formatContributions are welcome! Please feel free to submit a Pull Request.
BSD 3-Clause License - See LICENSE file for details
- data-mapper-api - The PHP server implementation
- HTTP API Documentation