supply-schema-redis
v1.6.2
Published
A complete Redis client package for connecting to Redis cache and managing supply, entity, and event data with full CRUD operations, search, and pagination support
Maintainers
Readme
Supply Redis Connector
A complete TypeScript package for connecting to Redis cache and managing supply, entity, and event data. This package provides full-featured clients for storing, retrieving, and searching supply objects, entities, events, and meter readings with comprehensive CRUD operations.
Features
- 🔌 Full Redis Integration - Complete clients with connection management
- 📦 CRUD Operations - Create, Read, Update, Delete supplies, entities, events, and meters
- 🔍 Advanced Search - Filter data by multiple criteria with case-insensitive matching
- 📊 Pagination Support - Efficient listing with cursor-based pagination
- ✅ Type Safety - Full TypeScript support with comprehensive types
- 🛡️ Validation - Built-in validation for all data objects
- ⚡ Performance - Uses pipelining and SCAN for efficient operations
- 🎯 Easy to Use - Simple, intuitive API
- 📅 Event Scheduling - Flexible trigger-based event scheduling with meter and date support
- 🏷️ Entity Management - Track any type of entity (vehicles, appliances, properties, items, and more)
- 📈 Meter Tracking - Record odometer readings, hour meters, and any other meter types
Installation
npm install supply-schema-redisor
yarn add supply-schema-redisQuick Start
Supply Management
import { SupplyRedisClient, Supply } from 'supply-schema-redis';
// Create and connect client
const client = new SupplyRedisClient({
host: 'localhost',
port: 6379,
});
await client.connect();
// Create a supply object
const supply: Supply = {
name: "Premium Ball Bearing",
description: "High-precision stainless steel ball bearing",
partNumber: "BB-1234-SS",
sku: "SKU-BB-001",
manufacturer: "Acme Manufacturing",
costs: [
{ costInCents: 1250, supplier: "Supplier A" },
{ costInCents: 1180, supplier: "Supplier B" }
]
};
// Save the supply
const guid = "550e8400-e29b-41d4-a716-446655440000";
await client.saveSupply(guid, supply);
// Retrieve the supply
const retrieved = await client.getSupply(guid);
console.log(retrieved);
// Search for supplies
const results = await client.searchSupplies({
manufacturer: "Acme",
maxCost: 2000
});
// Disconnect when done
await client.disconnect();Event Management with Flexible Triggers
import { EventRedisClient, Event } from 'supply-schema-redis';
const eventClient = new EventRedisClient({
host: 'localhost',
port: 6379,
});
await eventClient.connect();
// Meter interval event (oil change every 5,000 miles)
const oilChangeEvent: Event = {
name: 'Oil Change',
eventType: 'preventative',
description: 'Regular engine oil change every 5,000 miles',
trigger: {
type: 'meter_interval',
interval: 5000,
unit: 'miles',
},
};
// Days interval event (tire rotation every 180 days)
const tireRotationEvent: Event = {
name: 'Tire Rotation',
eventType: 'preventative',
description: 'Rotate tires every 6 months',
trigger: {
type: 'days_interval',
intervalDays: 180,
},
};
// One-time date trigger
const inspectionEvent: Event = {
name: 'Annual Inspection',
eventType: 'preventative',
description: 'State inspection due',
trigger: {
type: 'date_absolute',
date: new Date('2024-12-31'),
},
};
// Save events
await eventClient.saveEvent('oil-change-001', oilChangeEvent);
await eventClient.saveEvent('tire-rotation-001', tireRotationEvent);
await eventClient.saveEvent('inspection-001', inspectionEvent);
// Search events by trigger type
const meterEvents = await eventClient.searchEvents({
triggerType: 'meter_interval'
});
await eventClient.disconnect();Entity Management
import { EntityRedisClient, Entity } from 'supply-schema-redis';
const entityClient = new EntityRedisClient({
host: 'localhost',
port: 6379,
});
await entityClient.connect();
// Create a vehicle entity
const vehicle: Entity = {
name: 'Company Truck #1',
description: 'Ford F-150 pickup truck',
entityType: 'vehicle',
purchaseDate: new Date('2023-01-15'),
events: ['oil-change-001', 'tire-rotation-001'] // Associated events
};
// Save entity
await entityClient.saveEntity('vehicle-001', vehicle);
// Add event to entity
await entityClient.addEventToEntity('vehicle-001', 'inspection-001');
await entityClient.disconnect();API Reference
SupplyRedisClient
Constructor
new SupplyRedisClient(options?: SupplyRedisClientOptions)Options:
host- Redis host (default: 'localhost')port- Redis port (default: 6379)password- Optional password for authenticationdb- Database number to use (default: 0)connectTimeout- Connection timeout in milliseconds- Plus all other ioredis options
Connection Management
connect(): Promise<void>
Establishes connection to Redis.
await client.connect();disconnect(): Promise<void>
Closes the Redis connection.
await client.disconnect();isClientConnected(): boolean
Checks if the client is connected to Redis.
if (client.isClientConnected()) {
console.log('Connected!');
}CRUD Operations
saveSupply(guid: string, supply: Supply): Promise<void>
Saves a supply object to Redis.
const supply: Supply = {
name: "Widget",
description: "A useful widget",
partNumber: "W-123",
sku: "SKU-W-001",
manufacturer: "WidgetCo",
costs: [{ costInCents: 500, supplier: "Supplier X" }]
};
await client.saveSupply("my-guid-123", supply);getSupply(guid: string): Promise<Supply | null>
Retrieves a supply object from Redis. Returns null if not found.
const supply = await client.getSupply("my-guid-123");
if (supply) {
console.log(supply.name);
}updateSupply(guid: string, updates: Partial<Supply>): Promise<void>
Updates specific fields of a supply object.
await client.updateSupply("my-guid-123", {
description: "An updated description",
costs: [{ costInCents: 450, supplier: "Supplier X" }]
});deleteSupply(guid: string): Promise<boolean>
Deletes a supply object. Returns true if deleted, false if it didn't exist.
const deleted = await client.deleteSupply("my-guid-123");
console.log(`Deleted: ${deleted}`);supplyExists(guid: string): Promise<boolean>
Checks if a supply exists in Redis.
const exists = await client.supplyExists("my-guid-123");Listing and Pagination
listSupplies(options?: ListSuppliesOptions): Promise<ListSuppliesResult>
Lists supplies with cursor-based pagination.
// First page
let result = await client.listSupplies({ count: 50 });
console.log(result.supplies);
// Next page
while (result.cursor !== '0') {
result = await client.listSupplies({
cursor: result.cursor,
count: 50
});
console.log(result.supplies);
}Options:
cursor- Cursor for pagination (default: '0')count- Number of keys per batch (default: 100)pattern- Pattern to match (default: 'supply:*')
Returns:
cursor- Next cursor ('0' means no more results)supplies- Array of{ guid, data }objects
getAllSupplies(limit?: number): Promise<Array<{ guid: string; data: Supply }>>
Gets all supplies. Use with caution on large datasets.
const allSupplies = await client.getAllSupplies();
// Or with a limit
const limitedSupplies = await client.getAllSupplies(100);countSupplies(): Promise<number>
Counts total number of supplies in Redis.
const count = await client.countSupplies();
console.log(`Total supplies: ${count}`);Search and Filtering
searchSupplies(options: SearchOptions): Promise<Array<{ guid: string; data: Supply }>>
Searches supplies based on filter criteria.
const results = await client.searchSupplies({
manufacturer: "Acme", // Partial match, case-insensitive
partNumber: "BB-1234", // Partial match, case-insensitive
sku: "SKU-BB", // Partial match, case-insensitive
name: "bearing", // Partial match, case-insensitive
minCost: 1000, // Minimum cost in cents
maxCost: 5000, // Maximum cost in cents
limit: 50 // Limit results
});Search Options:
manufacturer- Filter by manufacturer (case-insensitive partial match)partNumber- Filter by part number (case-insensitive partial match)sku- Filter by SKU (case-insensitive partial match)name- Filter by name (case-insensitive partial match)minCost- Minimum cost in centsmaxCost- Maximum cost in centslimit- Maximum number of results
getLowestCost(guid: string): Promise<{ costInCents: number; supplier: string } | null>
Gets the lowest cost for a supply item.
const lowestCost = await client.getLowestCost("my-guid-123");
if (lowestCost) {
console.log(`Best price: $${lowestCost.costInCents / 100} from ${lowestCost.supplier}`);
}Bulk Operations
deleteAllSupplies(pattern?: string): Promise<number>
Deletes all supplies matching a pattern. Use with caution!
// Delete all supplies
const deletedCount = await client.deleteAllSupplies();
// Delete supplies matching a pattern
const deletedCount = await client.deleteAllSupplies('supply:test-*');Advanced
getRedisClient(): Redis
Gets the underlying ioredis client for advanced operations.
const redisClient = client.getRedisClient();
await redisClient.set('custom-key', 'value');Data Types
Supply
interface Supply {
name: string;
description: string;
partNumber: string;
sku: string;
manufacturer: string;
costs: Cost[];
}Cost
interface Cost {
costInCents: number; // Cost amount in cents
supplier: string; // Name of the supplier
}Utility Classes
SupplyRedisSchema
Low-level utilities for working with supply objects.
import { SupplyRedisSchema } from 'supply-schema-redis';
// Generate Redis key
const key = SupplyRedisSchema.getKey(guid);
// Convert to Redis hash format
const hash = SupplyRedisSchema.toRedisHash(supply);
// Convert from Redis hash
const supply = SupplyRedisSchema.fromRedisHash(hash);
// Validate supply
if (SupplyRedisSchema.isValidSupply(supply)) {
console.log('Valid!');
}
// Validate cost
if (SupplyRedisSchema.isValidCost(cost)) {
console.log('Valid!');
}Redis Data Structure
Supply objects are stored using Redis Hash data type.
Key Format
supply:<GUID>Example
HSET supply:550e8400-e29b-41d4-a716-446655440000 \
name "Premium Ball Bearing" \
description "High-precision stainless steel ball bearing" \
partNumber "BB-1234-SS" \
sku "SKU-BB-001" \
manufacturer "Acme Manufacturing" \
costs '[{"costInCents": 1250, "supplier": "Supplier A"}]'Complete Example
import { SupplyRedisClient, Supply } from 'supply-schema-redis';
async function main() {
// Initialize client
const client = new SupplyRedisClient({
host: 'localhost',
port: 6379,
});
try {
// Connect
await client.connect();
console.log('Connected to Redis');
// Create some supplies
const supplies: Array<{ guid: string; data: Supply }> = [
{
guid: 'guid-1',
data: {
name: 'Ball Bearing',
description: 'Precision ball bearing',
partNumber: 'BB-001',
sku: 'SKU-001',
manufacturer: 'BearingCo',
costs: [
{ costInCents: 1200, supplier: 'Supplier A' },
{ costInCents: 1150, supplier: 'Supplier B' },
],
},
},
{
guid: 'guid-2',
data: {
name: 'Steel Rod',
description: 'Stainless steel rod',
partNumber: 'SR-001',
sku: 'SKU-002',
manufacturer: 'SteelCo',
costs: [
{ costInCents: 2500, supplier: 'Supplier A' },
],
},
},
];
// Save supplies
for (const { guid, data } of supplies) {
await client.saveSupply(guid, data);
console.log(`Saved supply: ${guid}`);
}
// Count total supplies
const count = await client.countSupplies();
console.log(`Total supplies: ${count}`);
// Search for supplies
const bearingResults = await client.searchSupplies({
name: 'bearing',
});
console.log(`Found ${bearingResults.length} bearings`);
// Get lowest cost
const lowestCost = await client.getLowestCost('guid-1');
console.log(`Best price: $${lowestCost!.costInCents / 100}`);
// List all supplies with pagination
let cursor = '0';
do {
const result = await client.listSupplies({ cursor, count: 10 });
console.log(`Page with ${result.supplies.length} supplies`);
cursor = result.cursor;
} while (cursor !== '0');
// Update a supply
await client.updateSupply('guid-1', {
description: 'Updated description',
});
// Delete a supply
const deleted = await client.deleteSupply('guid-2');
console.log(`Deleted: ${deleted}`);
} finally {
// Always disconnect
await client.disconnect();
console.log('Disconnected from Redis');
}
}
main().catch(console.error);Error Handling
All methods throw descriptive errors when operations fail:
try {
await client.getSupply('invalid-guid');
} catch (error) {
console.error('Error:', error.message);
}Common errors:
- Connection errors: "Redis client is not connected. Call connect() first."
- Validation errors: "Invalid supply object: failed validation"
- Invalid GUID: "Invalid GUID: must be a non-empty string"
Best Practices
- Always connect before operations: Call
connect()before any data operations - Disconnect when done: Call
disconnect()to close connections properly - Use pagination: For large datasets, use
listSupplies(),listEvents(), etc. instead ofgetAll*()methods - Validate data: The client validates data automatically, but validate on input too
- Handle errors: Wrap operations in try-catch blocks
- Connection pooling: Reuse client instances instead of creating new ones
- Cost in cents: Always store costs in cents to avoid floating-point issues
- Use UUIDs for GUIDs: Generate GUIDs using UUID v4 for uniqueness
- Event trigger planning: Use meter-based triggers for usage-based maintenance (odometer, hours) and date/days triggers for time-based maintenance
- Entity-event relationships: Link events to entities to track maintenance history and schedules
- Meter tracking: Record meter readings regularly to enable accurate meter-based event scheduling
Data Types
Supply Data
interface Supply {
name: string;
description: string;
partNumber: string;
sku: string;
manufacturer: string;
costs: Cost[];
}
interface Cost {
costInCents: number; // Cost amount in cents
supplier: string; // Name of the supplier
}Event Data with Flexible Triggers
interface Event {
name: string;
eventType: 'preventative' | 'consumable_replacement';
description: string;
trigger: EventTrigger; // Flexible trigger configuration
}
// One-time trigger at specific meter value
interface MeterAbsoluteTrigger {
type: 'meter_absolute';
value: number; // Target meter value
unit: string; // 'miles', 'km', 'hours', etc.
}
// Recurring trigger every N meter units
interface MeterIntervalTrigger {
type: 'meter_interval';
interval: number; // Interval in meter units
unit: string; // 'miles', 'km', 'hours', etc.
}
// Recurring trigger every N days
interface DaysIntervalTrigger {
type: 'days_interval';
intervalDays: number; // Number of days between occurrences
}
// One-time trigger at specific date
interface DateAbsoluteTrigger {
type: 'date_absolute';
date: Date; // Target date
}
type EventTrigger = MeterAbsoluteTrigger | MeterIntervalTrigger | DaysIntervalTrigger | DateAbsoluteTrigger;Entity Data
interface Entity {
name: string;
description: string;
entityType: string; // Can be any string (e.g., 'vehicle', 'appliance', 'property', 'item', etc.)
purchaseDate: Date;
events: string[]; // Array of event GUIDs
}Meter Data
interface Meter {
meterType: string; // Can be any string (e.g., 'odometer', 'hours', etc.)
value: number;
entityId: string;
date: Date;
}Event Trigger Examples
Meter-Based Triggers
// Recurring: Oil change every 5,000 miles
const oilChangeEvent: Event = {
name: 'Oil Change',
eventType: 'preventative',
description: 'Regular engine oil change every 5,000 miles',
trigger: {
type: 'meter_interval',
interval: 5000,
unit: 'miles',
},
};
// One-time: Major service at 100,000 miles
const majorServiceEvent: Event = {
name: 'Major Service',
eventType: 'preventative',
description: '100k mile major service',
trigger: {
type: 'meter_absolute',
value: 100000,
unit: 'miles',
},
};Date and Days-Based Triggers
// Recurring: Tire rotation every 180 days
const tireRotationEvent: Event = {
name: 'Tire Rotation',
eventType: 'preventative',
description: 'Rotate tires every 6 months',
trigger: {
type: 'days_interval',
intervalDays: 180,
},
};
// One-time: Annual inspection on specific date
const inspectionEvent: Event = {
name: 'Annual Inspection',
eventType: 'preventative',
description: 'State inspection due',
trigger: {
type: 'date_absolute',
date: new Date('2024-12-31'),
},
};TypeScript Support
This package is written in TypeScript and includes full type definitions. All types are exported:
import {
// Supply types
Supply,
Cost,
SupplyRedisHash,
SupplyRedisClient,
SupplyRedisClientOptions,
ListSuppliesOptions,
ListSuppliesResult,
SearchOptions,
SupplyRedisSchema,
// Event types
Event,
EventTrigger,
MeterAbsoluteTrigger,
MeterIntervalTrigger,
DaysIntervalTrigger,
DateAbsoluteTrigger,
EventRedisHash,
EventRedisClient,
EventRedisClientOptions,
ListEventsOptions,
ListEventsResult,
EventSearchOptions,
EventRedisSchema,
// Entity types
Entity,
EntityRedisHash,
EntityRedisClient,
EntityRedisClientOptions,
ListEntitiesOptions,
ListEntitiesResult,
EntitySearchOptions,
EntityRedisSchema,
// Meter types
Meter,
MeterRedisHash,
MeterRedisClient,
MeterRedisClientOptions,
ListMetersOptions,
ListMetersResult,
MeterSearchOptions,
MeterRedisSchema,
} from 'supply-schema-redis';License
MIT
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
