Examples
ZSchema System

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.ts

Next 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