ZSchema Example
This example demonstrates how to use the ZSchema system for data validation, type safety, and special data handling in ZoopFlow.
What You'll Learn
- How to define and use ZSchema types
- How to work with schema tags for special data handling
- How to validate data against schemas
- How to use the schema registry
- How to implement tag processing middleware
Basic ZSchema Usage
Here's a basic example of defining and using a ZSchema:
import { defineZSchema, ZSchemaTag, ZSchemaValidator } from '@zoopflow/core/zschema';
// Define a schema for order data
const OrderSchema = defineZSchema({
name: 'Order',
description: 'Customer order with products and payment info',
tags: [ZSchemaTag.SENSITIVE], // Indicates this contains sensitive data
validator: {
schema: {
type: 'object',
properties: {
id: { type: 'string', format: 'uuid' },
customerEmail: { type: 'string', format: 'email' },
products: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
quantity: { type: 'integer', minimum: 1 },
price: { type: 'number', minimum: 0 }
},
required: ['id', 'quantity']
}
},
totalAmount: { type: 'number', minimum: 0 },
paymentMethod: {
type: 'object',
properties: {
type: { type: 'string', enum: ['credit_card', 'paypal', 'bank_transfer'] },
details: { type: 'object' }
},
required: ['type']
},
status: { type: 'string', enum: ['pending', 'paid', 'shipped', 'delivered', 'cancelled'] },
createdAt: { type: 'string', format: 'date-time' }
},
required: ['id', 'customerEmail', 'products', 'totalAmount', 'status']
}
},
example: {
id: '123e4567-e89b-12d3-a456-426614174000',
customerEmail: 'customer@example.com',
products: [
{ id: 'prod-1', name: 'Product 1', quantity: 2, price: 24.99 }
],
totalAmount: 49.98,
paymentMethod: {
type: 'credit_card',
details: { /* Masked for security */ }
},
status: 'pending',
createdAt: '2023-05-01T12:00:00Z'
}
});
// Create a validator
const validator = new ZSchemaValidator();
// Sample data to validate
const orderData = {
id: '123e4567-e89b-12d3-a456-426614174000',
customerEmail: 'customer@example.com',
products: [
{ id: 'prod-1', name: 'Widget A', quantity: 2, price: 24.99 }
],
totalAmount: 49.98,
paymentMethod: {
type: 'credit_card',
details: {
lastFour: '1234',
expiryMonth: 12,
expiryYear: 2025
}
},
status: 'pending',
createdAt: '2023-05-01T12:00:00Z'
};
// Validate the data
async function validateOrder() {
const result = await validator.validate(OrderSchema, orderData);
if (result.valid) {
console.log('Order is valid!');
// result.data is now typed as the Order type
const order = result.data;
processOrder(order);
} else {
console.error('Validation errors:', result.errors);
}
}
function processOrder(order) {
console.log(`Processing order ${order.id} with total ${order.totalAmount}`);
// Further processing...
}
validateOrder();Working with Schema Tags
ZSchema tags enable special processing for different types of data:
import {
defineZSchema,
ZSchemaTag,
getTagProcessingMiddleware
} from '@zoopflow/core/zschema';
// Define a schema with PII (Personally Identifiable Information)
const CustomerSchema = defineZSchema({
name: 'Customer',
tags: [ZSchemaTag.PII], // This will auto-mask PII in logs
validator: {
schema: {
type: 'object',
properties: {
id: { type: 'string' },
fullName: { type: 'string' },
email: { type: 'string', format: 'email' },
phoneNumber: { type: 'string' },
address: { type: 'string' }
},
required: ['id', 'fullName', 'email']
}
}
});
// Define a schema for payment data with multiple tags
const PaymentDataSchema = defineZSchema({
name: 'PaymentData',
tags: [ZSchemaTag.SENSITIVE, ZSchemaTag.ENCRYPTED, ZSchemaTag.PII],
validator: {
schema: {
type: 'object',
properties: {
cardNumber: { type: 'string' },
cardholderName: { type: 'string' },
expiryMonth: { type: 'integer', minimum: 1, maximum: 12 },
expiryYear: { type: 'integer', minimum: 2023 },
cvv: { type: 'string', pattern: '^\\d{3,4}$' }
},
required: ['cardNumber', 'cardholderName', 'expiryMonth', 'expiryYear', 'cvv']
}
}
});
// Get the tag processing middleware
const middleware = getTagProcessingMiddleware();
// Example: Processing data based on tags
async function processCustomerData(customerData) {
// Process for storage - this would encrypt/mask sensitive data
const processedData = await middleware.processForWrite(customerData, 'Customer');
// Store the data...
console.log('Storing processed data:', processedData);
// Later, when retrieving the data...
const storedData = processedData; // This would normally come from a database
// Process for reading - this would decrypt/unmask data
const readableData = await middleware.processForRead(storedData, 'Customer');
console.log('Processed for display:', readableData);
return readableData;
}
// Test with sample data
const customer = {
id: 'cust-12345',
fullName: 'Jane Smith',
email: 'jane.smith@example.com',
phoneNumber: '555-123-4567',
address: '123 Main St, Anytown, USA'
};
processCustomerData(customer);Schema Registry
The schema registry provides centralized access to all schemas:
import {
defineZSchema,
ZSchemaTag,
getZSchemaRegistry
} from '@zoopflow/core/zschema';
// Define schemas
const ProductSchema = defineZSchema({
name: 'Product',
validator: {
schema: {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
price: { type: 'number', minimum: 0 },
category: { type: 'string' }
},
required: ['id', 'name', 'price']
}
}
});
const InventoryItemSchema = defineZSchema({
name: 'InventoryItem',
tags: [ZSchemaTag.IMMUTABLE], // Inventory records shouldn't change
validator: {
schema: {
type: 'object',
properties: {
productId: { type: 'string' },
warehouseId: { type: 'string' },
quantity: { type: 'integer', minimum: 0 },
lastUpdated: { type: 'string', format: 'date-time' }
},
required: ['productId', 'warehouseId', 'quantity']
}
}
});
// Get the registry instance
const registry = getZSchemaRegistry();
// Register the schemas
registry.registerType(ProductSchema);
registry.registerType(InventoryItemSchema);
// Using the registry to find schemas
function getSchemasByDomain() {
// Get all schemas
const allSchemas = registry.getAllTypes();
console.log(`Total schemas: ${allSchemas.length}`);
// Get schemas with a specific tag
const immutableSchemas = registry.getTypesByTag(ZSchemaTag.IMMUTABLE);
console.log(`Immutable schemas: ${immutableSchemas.length}`);
// Look up a specific schema
const productSchema = registry.getType('Product');
if (productSchema) {
console.log(`Found Product schema: ${productSchema.description || productSchema.name}`);
}
return { allSchemas, immutableSchemas, productSchema };
}
getSchemasByDomain();Custom Tag Handlers
You can create custom tag handlers for specialized data processing:
import {
defineZSchema,
ZSchemaTag,
getTagProcessingMiddleware,
TagProcessingDirection
} from '@zoopflow/core/zschema';
// Define a schema with custom tags
const AuditLogSchema = defineZSchema({
name: 'AuditLog',
tags: [ZSchemaTag.IMMUTABLE, ZSchemaTag.CUSTOM],
customTags: ['retention-policy', 'compliance-required'],
validator: {
schema: {
type: 'object',
properties: {
id: { type: 'string' },
userId: { type: 'string' },
action: { type: 'string' },
resource: { type: 'string' },
timestamp: { type: 'string', format: 'date-time' },
details: { type: 'object' }
},
required: ['id', 'userId', 'action', 'timestamp']
}
}
});
// Get the middleware instance
const middleware = getTagProcessingMiddleware();
// Create a custom tag handler for retention policy
const retentionPolicyHandler = {
tag: 'retention-policy',
canProcess: (value, context) => {
// This handler can process any object
return typeof value === 'object' && value !== null;
},
process: async (value, context, direction) => {
if (direction === TagProcessingDirection.WRITE) {
// Add retention policy metadata when writing
return {
modified: true,
value: {
...value,
_retention: {
policy: 'retain-7-years',
expiresAt: new Date(Date.now() + 7 * 365 * 24 * 60 * 60 * 1000).toISOString()
}
}
};
} else if (direction === TagProcessingDirection.READ) {
// Check if the record is expired when reading
const now = new Date();
const expiryDate = value._retention?.expiresAt ? new Date(value._retention.expiresAt) : null;
if (expiryDate && now > expiryDate) {
console.warn(`Reading expired record: ${value.id}`);
// You could implement redaction here
}
// Return the value as is
return { modified: false, value };
}
return { modified: false, value };
}
};
// Register the custom handler
middleware.registerHandler(retentionPolicyHandler);
// Example audit log
const auditLog = {
id: 'log-12345',
userId: 'user-789',
action: 'DELETE',
resource: 'product-456',
timestamp: new Date().toISOString(),
details: {
reason: 'Product discontinued',
requestIp: '192.168.1.1'
}
};
// Test the custom handler
async function processAuditLog() {
// Process for writing to storage
const processed = await middleware.processForWrite(auditLog, 'AuditLog');
console.log('Processed audit log with retention policy:', processed);
// Later, when reading...
const retrieved = processed; // Would normally come from storage
const read = await middleware.processForRead(retrieved, 'AuditLog');
console.log('Retrieved audit log:', read);
return { processed, read };
}
processAuditLog();How to Run This Example
To run this example:
# From the project root
npm install
npx ts-node examples/zschema/index.tsNext Steps
- Create schemas for your domain model
- Implement custom tag handlers for your specific needs
- Integrate ZSchema with your API endpoints for automatic validation
- Use schema tags to handle sensitive data appropriately
- Combine ZSchema with Flow and Step definitions