@crudmates/masq
Flexible field masking and relation selection for REST APIs and data filtering. Transform and filter complex nested objects with intuitive string-based field specifications.
Features
- 🎯 Field Selection: Select specific fields with simple syntax:
id,name,email
- 🌟 Nested Objects: Handle deep nesting:
user(profile(avatar,settings))
- 🔄 Aliasing: Rename fields during selection:
model<models>(make<makes>)
- ⭐ Wildcards: Include all fields with exclusions:
*,-password,-secret
- 🔗 Relation Joins: Generate join descriptors for ORM/query builders
- 📦 Zero Dependencies: Lightweight and self-contained
- 🛡️ Type Safe: Full TypeScript support with strict mode compliance
- ✅ Well Tested: 98%+ test coverage with comprehensive edge case handling
Installation
npm install @crudmates/masq
Quick Start
import { applyMask, parseRelationsSpec } from '@crudmates/masq';
const user = {
id: 1,
name: 'John Doe',
email: 'john@example.com',
password: 'secret123',
profile: {
avatar: 'avatar.jpg',
bio: 'Software developer',
settings: {
theme: 'dark',
notifications: true,
},
},
};
// Simple field selection
const basic = applyMask(user, 'id,name,email');
// Result: { id: 1, name: 'John Doe', email: 'john@example.com' }
// Nested field selection
const nested = applyMask(user, 'id,name,profile(avatar,settings(theme))');
// Result: { id: 1, name: 'John Doe', profile: { avatar: 'avatar.jpg', settings: { theme: 'dark' } } }
// Wildcard with exclusions
const filtered = applyMask(user, '*,-password');
// Result: All fields except password
// Field aliasing
const aliased = applyMask(user, 'id,profile<userProfile>(avatar)');
// Result: { id: 1, userProfile: { avatar: 'avatar.jpg' } }
API Reference
applyMask<T>(data: T, maskValue?: string | object): T
Apply a field mask to data, supporting both string specifications and pre-parsed objects.
Parameters:
data
: The object or array to filtermaskValue
: Field mask as string or parsed object (optional)
Returns: Filtered data with the same type
// String mask
applyMask(user, 'id,name,profile(avatar)');
// Pre-parsed object mask
applyMask(user, {
id: true,
name: true,
profile: { avatar: true },
});
// Works with arrays
applyMask([user1, user2], 'id,name');
// Type-safe generics
const result: User = applyMask<User>(user, 'id,name');
parseFieldsSpec(fieldsSpec: string): object
Parse a field specification string into an object structure.
Parameters:
fieldsSpec
: Field specification string
Returns: Parsed mask object
parseFieldsSpec('id,name,profile(avatar)');
// Result: { id: true, name: true, profile: { avatar: true } }
parseFieldsSpec('model<models>(make<makes>)');
// Result: { model: { __alias: 'models', make: { __alias: 'makes' } } }
parseFieldsSpec('*,-password');
// Result: { '*': true, password: false }
applyFieldMask(obj: any, mask: any): any
Apply a pre-parsed field mask to an object or array.
Parameters:
obj
: The object or array to filtermask
: Pre-parsed mask object
Returns: Filtered object/array
const mask = { id: true, name: true, profile: { avatar: true } };
applyFieldMask(user, mask);
isValidMask(maskObj: any, allowed: any): boolean
Validate a mask object against a set of allowed fields. Useful for ensuring that a user-provided mask only includes permitted fields and structure.
Parameters:
maskObj
: The mask object to validate (as parsed byparseFieldsSpec
or similar)allowed
: The allowed fields structure (object with allowed keys and nested objects)
Returns: true
if the mask is valid, false
otherwise
const allowed = {
id: true,
name: true,
profile: {
avatar: true,
bio: true,
},
};
isValidMask({ id: true, profile: { avatar: true } }, allowed); // true
isValidMask({ id: true, secret: true }, allowed); // false
isValidMask({ profile: { avatar: true, extra: true } }, allowed); // false
parseRelationsSpec(relationsStr: string, baseAlias: string): RelationJoin[]
Parse relation strings into join descriptors for ORM/query builders.
Parameters:
relationsStr
: Relation specification stringbaseAlias
: Base table alias (defaults to 'car')
Returns: Array of RelationJoin
objects with path
and alias
properties
parseRelationsSpec('model(make,category)', 'car');
// Result: [
// { path: 'car.model', alias: 'model' },
// { path: 'model.make', alias: 'make' },
// { path: 'model.category', alias: 'category' }
// ]
// Use with SQL query builders
const joins = parseRelationsSpec('user(profile,posts(comments))');
joins.forEach(({ path, alias }) => {
query.leftJoin(path, alias);
});
Field Specification Syntax
Basic Selection
'id,name,email'; // Select specific fields
'id'; // Single field
Nested Objects
'user(profile(avatar,bio))'; // Nested field selection
'user(profile(settings(theme)))'; // Deep nesting
'user(profile(*),posts(id,title))'; // Mixed nested selection
Wildcards
'*'; // Select all fields
'*,-password'; // All fields except password
'*,-password,-secret'; // Multiple exclusions
'user(*,-internal)'; // Wildcard in nested objects
Field Aliasing
'model<models>'; // Rename field: models
'user<customer>(profile<info>)'; // Nested aliasing
'posts<articles>(author<writer>)'; // Multiple aliases
Complex Examples
// Real-world API response filtering
'id,name,*,-password,profile<userProfile>(avatar,settings<prefs>(theme))';
// E-commerce product selection
'id,name,price,category(name,parent),reviews<ratings>(score,comment)';
// User with posts and comments
'user(id,name,profile(avatar),posts(id,title,comments(id,content,author(name))))';
Advanced Usage
Working with Arrays
const users = [
{ id: 1, name: 'John', password: 'secret1' },
{ id: 2, name: 'Jane', password: 'secret2' },
];
const filtered = applyMask(users, 'id,name');
// Result: [{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }]
Type Safety with Generics
interface User {
id: number;
name: string;
email: string;
}
const user: User = { id: 1, name: 'John', email: 'john@example.com' };
const result = applyMask<User>(user, 'id,name');
// result is typed as User
Integration with ORMs
// Sequelize example
const joins = parseRelationsSpec('user(profile,posts(comments))');
joins.forEach(({ path, alias }) => {
query.include.push({
model: getModelFromPath(path),
as: alias,
});
});
// TypeORM example
const joins = parseRelationsSpec('order(customer,items(product))');
joins.forEach(({ path, alias }) => {
queryBuilder.leftJoinAndSelect(path, alias);
});
Database Query Optimization
// Use with query builders to optimize database queries
const fields = req.query.fields; // e.g., "user(profile(avatar),posts(id,title))"
const relations = parseRelationsSpec(fields);
// Optimize joins based on requested fields
relations.forEach(({ path, alias }) => {
queryBuilder.leftJoinAndSelect(path, alias);
});
const result = applyMask(await queryBuilder.getMany(), fields);
REST API Query Parameters
// Express.js middleware example
app.get('/api/users', (req, res) => {
const fields = req.query.fields; // e.g., "id,name,profile(avatar)"
const users = await User.findAll();
const filtered = applyMask(users, fields);
res.json(filtered);
});
Error Handling
The library gracefully handles malformed input:
// Invalid syntax returns original data
applyMask(user, 'invalid(((syntax'); // Returns user unchanged
// Empty or undefined masks
applyMask(user, ''); // Returns user unchanged
applyMask(user, undefined); // Returns user unchanged
applyMask(user, null); // Returns user unchanged
// Non-existent fields are ignored
applyMask(user, 'id,nonExistent'); // Returns { id: 1 }
Performance
- Lightweight: Zero dependencies, ~3KB minified
- Fast: Optimized parsing and filtering algorithms
- Memory efficient: No unnecessary object cloning
- Tree-shakable: Import only what you need
License
This project is licensed under the MIT License.