Package Exports
- @threadify/sdk
Readme
@threadify/sdk
Threadify gives you infrastructure to observe, validate, and react to distributed workflows—instrument once, then get visibility into what happened and why across your business processes.
Why Threadify?
When an order fails, a payment gets stuck, or a delivery goes missing, you need to know what happened and why. Threadify gives you:
- Observe: See every step of your distributed workflows in real-time
- Validate: Automatically catch issues like timeouts, invalid transitions, and missing steps
- React: Get notified when workflows violate rules so you can fix problems fast
- Trace: Link workflows to external systems (Stripe, Shopify, etc.) for complete visibility
- Audit: Every step is recorded with full context for compliance and debugging
Installation
npm install @threadify/sdkQuick Start
import { Threadify } from '@threadify/sdk';
// Connect with your API key
const connection = await Threadify.connect('your-api-key');
// Start tracking a workflow
const thread = await connection.start();
// Record each step with full context
await thread.step('order_placed')
.addContext({ orderId: 'ORD-12345', amount: 99.99, customer: 'john@example.com' })
.success();
await thread.step('payment_processed')
.addContext({ paymentId: 'PAY-67890', method: 'credit_card' })
.success();
await thread.step('order_shipped')
.addContext({ trackingNumber: 'TRACK-123', carrier: 'FedEx' })
.success();
// That's it! Threadify handles the rest.Real-World Use Cases
E-commerce Order Fulfillment
Observe every step from order placement to delivery. Validate that payments complete within timeout limits and shipments follow the correct sequence. React when orders get stuck or payments fail, with instant notifications to your team.
Payment Processing
Observe payment flows across multiple providers (Stripe, PayPal, etc.). Validate that transactions follow compliance rules and complete within SLA timeframes. React to failed payments or suspicious patterns with automated alerts.
Multi-Party Workflows
Observe coordination between merchants, logistics providers, and customers. Validate that each party completes their steps correctly and on time. React when handoffs fail or deadlines are missed.
System Integration
Observe workflows that span multiple systems (your app, Stripe, Shopify, shipping APIs). Validate that data flows correctly between systems. React when integrations break or data gets out of sync.
Common Scenarios
Track a Simple Workflow
import { Threadify } from '@threadify/sdk';
const connection = await Threadify.connect('your-api-key');
const thread = await connection.start();
// Each step is automatically validated and tracked
await thread.step('order_received')
.addContext({ orderId: 'ORD-123', total: 299.99 })
.success();
await thread.step('inventory_checked')
.addContext({ inStock: true, warehouse: 'US-EAST' })
.success();
await thread.step('payment_captured')
.addContext({ paymentId: 'ch_abc123', amount: 299.99 })
.success();Link to External Systems
// Connect your workflow to Stripe, Shopify, etc.
await thread.step('process_payment')
.addContext({ amount: 299.99, currency: 'USD' })
.addRefs({
stripe_payment_id: 'pi_abc123',
shopify_order_id: '12345',
customer_email: 'customer@example.com'
})
.success();
// Now you can trace from Stripe back to your workflow instantlyHandle Failures Gracefully
try {
await processPayment(orderId);
await thread.step('payment_processed')
.addContext({ orderId, status: 'success' })
.success();
} catch (error) {
// Threadify tracks failures too
await thread.step('payment_processed')
.addContext({ orderId, error: error.message })
.failed('Payment gateway timeout');
// You'll get notified automatically if this violates your workflow rules
}Work with Contracts (Predefined Workflows)
// Use a contract to enforce your workflow structure
const thread = await connection.start('order_fulfillment', 'merchant');
// Contract ensures you follow the right steps in the right order
await thread.step('order_placed')
.addContext({ orderId: 'ORD-123' })
.success();
// Threadify validates this is a valid next step
await thread.step('payment_authorized')
.addContext({ authCode: 'AUTH-456' })
.success();Core Concepts
1. Connection
A WebSocket connection to the Threadify Engine. Manages authentication and message routing.
2. Thread
A workflow execution instance. Can be contract-based (with validation rules) or non-contract (free-form).
3. Step
An atomic unit of work within a thread. Steps have:
- Name: Identifies the step type
- Context: Business data associated with the step
- Status:
in_progress,success,failed,error,skipped - Idempotency: Automatic deduplication based on name + context
4. Contract
A YAML-defined workflow specification that enforces:
- Entry point validation
- Step existence checks
- Required business context fields
- Role-based access control
- Step transitions (future)
API Reference
Getting Started
Threadify.connect(apiKey, serviceName, options)
Connect to Threadify and start tracking your workflows.
Parameters:
apiKey(string, required): Your API key from the Threadify dashboardserviceName(string, optional): Your service name (e.g., 'payment-service', 'order-service')options(object, optional):debug(boolean): Enable debug logging for troubleshooting (default: false)
Returns: Promise<Connection>
Example:
// Simple connection (most common)
const connection = await Threadify.connect('your-api-key');
// With service name
const connection = await Threadify.connect('your-api-key', 'payment-service');
// With debug logging
const connection = await Threadify.connect('your-api-key', 'payment-service', {
debug: true
});Working with Threads
connection.start(...args)
Starts a new thread.
Signatures:
start()- Non-contract workflowstart(role)- Non-contract with specific rolestart(contractName, role)- Contract-based workflow
Parameters:
contractName(string, optional): Contract name or "name:version"role(string, optional): The role you want to play in this thread (e.g., 'merchant', 'logistics', 'customer')
Returns: Promise<ThreadInstance>
Examples:
// Non-contract
const thread1 = await connection.start();
// Non-contract with role
const thread2 = await connection.start('merchant');
// Contract-based
const thread3 = await connection.start('product_delivery', 'merchant');
// Contract with version
const thread4 = await connection.start('product_delivery:v2', 'merchant');connection.join(tokenOrThreadId, role)
Joins an existing thread using an invitation token or direct join.
Parameters:
tokenOrThreadId(string, required): JWT invitation token (for external parties) OR threadId (for internal services within the same company)role(string, optional): Role for direct join (only available for internal services within your organization)
Returns: Promise<ThreadInstance>
Examples:
// Token-based join (external party)
const thread = await connection.join('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...');
// Direct join (internal service within same company)
const thread = await connection.join('thread-uuid-123', 'logistics');connection.onViolation(stepName, handler)
Register a global handler for validation violation notifications.
Parameters:
stepName(string, required): Name of the step to listen forhandler(function, required): Handler function that receives aNotificationobject
Returns: Connection (for chaining)
Example:
connection.onViolation('payment_processing', (notification) => {
console.error(`Violation: ${notification.message}`);
console.error(`Details:`, notification.details);
notification.ack(); // Acknowledge the notification
});Note: Handlers are global and will receive notifications for ALL threads. Use notification.threadId to filter if needed.
connection.onCompleted(stepName, handler)
Register a global handler for step completion notifications.
Parameters:
stepName(string, required): Name of the step to listen forhandler(function, required): Handler function that receives aNotificationobject
Returns: Connection (for chaining)
Example:
connection.onCompleted('payment_processing', (notification) => {
console.log(`Payment completed for thread ${notification.threadId}`);
notification.ack();
});connection.onFailed(stepName, handler)
Register a global handler for step failure notifications.
Parameters:
stepName(string, required): Name of the step to listen forhandler(function, required): Handler function that receives aNotificationobject
Returns: Connection (for chaining)
Example:
connection.onFailed('payment_processing', (notification) => {
console.error(`Payment failed: ${notification.message}`);
notification.ack();
});connection.close()
Closes the WebSocket connection gracefully.
Returns: Promise<void>
Example:
await connection.close();ThreadInstance Class
Represents a specific thread execution.
thread.step(stepName, serviceName)
Creates a new step in this thread.
Parameters:
stepName(string, required): Name of the stepserviceName(string, optional): Service executing the step
Returns: ThreadStep
Example:
const step = thread.step('validate_payment', 'payment-service');
await step
.addContext({ amount: 100.00, currency: 'USD' })
.addRefs({ stripe_payment_id: 'pi_123', order_id: 'ORD-456' })
.success();Note: Use addRefs() on the step to link external systems, or use thread.addRefs() to add references at the thread level.
thread.linkThread(threadId, relationship)
Link this thread to another thread to create relationships between workflows (e.g., parent-child, related processes).
Parameters:
threadId(string, required): UUID of the thread to link torelationship(string, optional): Type of relationship (default: 'parent'). Examples: 'parent', 'child', 'related', 'continuation'
Returns: Promise<Object> - Response from server
Example:
// Link a child workflow to its parent
const parentThread = await connection.start();
await parentThread.step('order_placed').success();
const childThread = await connection.start();
await childThread.linkThread(parentThread.getThreadId(), 'parent');
await childThread.step('payment_processing').success();
// Now you can trace the relationship between these workflowsUse Cases:
- Parent-Child: Link sub-workflows to main workflows (e.g., payment processing as child of order fulfillment)
- Related: Connect related workflows (e.g., refund workflow related to original order)
- Continuation: Link workflow that continues from another (e.g., delivery workflow continuing from fulfillment)
thread.inviteParty(options)
Creates an invitation token for external parties to join this thread.
Parameters:
options(object):role(string, required): Role for the invited partypermissions(string, optional): Comma-separated permissions (default: "read,write")expiresIn(string, optional): Expiry duration (default: "24h")
Returns: Promise<string> - JWT invitation token
Example:
const token = await thread.inviteParty({
role: 'logistics',
permissions: 'read,write',
expiresIn: '48h'
});
console.log('Share this token:', token);Note: This method uses WebSocket communication. The invitation token can be used with connection.join(token) to allow other parties to join the thread.
thread.getThreadId()
Returns the thread's unique identifier.
Returns: string
thread.getContractId()
Returns the contract ID if this is a contract-based thread.
Returns: string | null
thread.waitFor(stepName, options)
Wait for a notification for a specific step on this thread (blocking). This is useful for synchronous workflows where you need to wait for validation results before proceeding.
Parameters:
stepName(string, required): Name of the step to wait foroptions(object, optional):timeout(number): Timeout in milliseconds (default: 5000)statuses(array): Only resolve for these statuses (e.g.,['success', 'failed'])
Returns: Promise<Notification>
Example:
// Record a step
await thread.step('payment_authorization')
.addContext({ amount: '500.00' })
.stop('success');
// Wait for validation notification (auto-ACKs on resolution)
const result = await thread.waitFor('payment_authorization', {
timeout: 10000,
statuses: ['success', 'failed']
});
if (result.isPassed()) {
console.log('Validation passed!');
} else {
console.error('Validation failed:', result.message);
}Note: waitFor() automatically ACKs the notification when the promise resolves. This is ideal for synchronous flows where you need the validation result before continuing.
thread.close()
Closes this thread instance and rejects any pending waitFor() promises.
Returns: Promise<void>
ThreadStep Class
Represents a step execution with fluent API.
step.addContext(contextData, isPrivate)
Adds business context data to the step.
Parameters:
contextData(object, required): Key-value pairs of context dataisPrivate(boolean, optional): Whether context is private (default: false)
Returns: ThreadStep (for chaining)
Example:
step.addContext({
order_id: 'ORD-123',
customer_id: 'CUST-456',
amount: '99.99'
});Note: All values are automatically converted to strings as required by the server schema.
step.addRefs(refsData)
Adds references to external systems.
Parameters:
refsData(object, required): Key-value pairs of external references
Returns: ThreadStep (for chaining)
Example:
step.addRefs({
stripe_payment_id: 'pi_123abc',
shopify_order_id: '987654'
});step.idempotencyKey(key)
Sets a manual idempotency key (overrides auto-generated key).
Parameters:
key(string, required): Custom idempotency key
Returns: ThreadStep (for chaining)
Example:
step.idempotencyKey('order-123-payment-attempt-1');Default Behavior: If not set, the SDK automatically generates an idempotency key using FNV-1a hash of stepName + context.
step.stop(status, message, finalContext)
Completes the step and sends it to the server.
Parameters:
status(string, optional): Final status -'success','failed','error','skipped'(default: 'success')message(string, optional): Completion messagefinalContext(object, optional): Additional context to add before completion
Returns: Promise<ThreadStep>
Example:
await step.stop('success', 'Payment processed', {
transaction_id: 'txn_789'
});step.success(message, result)
Convenience method to complete step with success status.
Parameters:
message(string, optional): Success message (default: 'Step completed successfully')result(object, optional): Result data
Returns: Promise<StepResult> - Clean response object with step details (no internal SDK details exposed)
Response Format:
{
stepName: string,
threadId: string,
status: 'success',
idempotencyKey: string,
timestamp: string,
duplicate?: boolean // Present if step was already recorded
}Example:
const result = await step.success('Order validated', {
validation_id: 'val_123'
});
console.log(result.stepName); // 'order_placed'
console.log(result.status); // 'success'step.failed(message, error)
Convenience method to complete step with failed status.
Parameters:
message(string, optional): Failure message (default: 'Step failed')error(object, optional): Error data
Returns: Promise<StepResult> - Clean response object with step details
Example:
await step.failed('Insufficient inventory', {
requested: '10',
available: '5'
});step.error(message, error)
Convenience method to complete step with error status.
Parameters:
message(string, optional): Error message (default: 'Step failed with error')error(object, optional): Error details
Returns: Promise<StepResult> - Clean response object with step details
Example:
await step.error('Payment gateway timeout', {
gateway: 'stripe',
error_code: 'TIMEOUT'
});Getter Methods
step.getStepName()- Returns step namestep.getStatus()- Returns current statusstep.getContext()- Returns copy of context datastep.getEventData()- Returns complete event data (for debugging)
Notification Class
Represents a validation notification received from the server.
notification.ack()
Acknowledge a notification (mark as read/processed). This tells the server that the notification was successfully received and processed.
Returns: void
Example:
connection.onViolation('payment_processing', (notification) => {
console.error('Violation:', notification.message);
notification.ack(); // Send ACK to server
});Note: For waitFor(), ACK is sent automatically when the promise resolves. For event handlers (onViolation, onCompleted, onFailed), you must call ack() manually.
Notification Properties
Core Fields:
notificationId(string) - Unique notification IDthreadId(string) - Thread this notification belongs tostepName(string) - Name of the stepstepStatus(string) - Step status (success,failed,error)status(string) - Validation status (passed,violated)severity(string) - Severity level (critical,warning,info)message(string) - Human-readable messageviolationType(string) - Type of violation (if applicable)details(object) - Additional details about the notification
Notification Helper Methods
Validation Status:
notification.isViolated()- Returnstrueif validation failednotification.isPassed()- Returnstrueif validation passed
Step Status:
notification.isSuccess()- Returnstrueif step succeedednotification.isFailed()- Returnstrueif step failednotification.isError()- Returnstrueif step had an error
Severity:
notification.isCritical()- Returnstrueif severity is criticalnotification.isWarning()- Returnstrueif severity is warningnotification.isInfo()- Returnstrueif severity is info
Example:
connection.onViolation('payment_processing', (notification) => {
if (notification.isCritical()) {
// Alert ops team
alertOpsTeam(notification);
} else if (notification.isWarning()) {
// Log warning
console.warn(notification.message);
}
notification.ack();
});Advanced Configuration
Custom WebSocket URLs (Self-Hosted Only)
If you're running Threadify on your own infrastructure, you can specify custom endpoints:
const connection = await Threadify.connect('your-api-key', 'my-service', {
wsUrl: 'ws://localhost:8081/threads',
graphqlUrl: 'http://localhost:8081/graphql'
});Note: Production users of Threadify's hosted service don't need to configure URLs - everything is handled automatically.
Reusable Configuration
For multiple connections with the same settings:
const threadify = Threadify.create({
apiKey: 'api-key-123',
serviceName: 'order-service'
});
// Create multiple connections
const conn1 = await threadify.connect();
const conn2 = await threadify.connect();Advanced Usage
Fluent API Chaining
await thread.step('process_payment')
.addContext({ amount: '100.00', currency: 'USD' })
.addRefs({ stripe_id: 'pi_123' })
.idempotencyKey('payment-order-123')
.success('Payment completed');Error Handling with Idempotency
The SDK automatically handles duplicate step submissions:
try {
await thread.step('order_placed')
.addContext({ order_id: 'ORD-123' })
.stop('success');
} catch (error) {
if (error.isDuplicate) {
console.log('Step already recorded (idempotent)');
// Continue execution - this is expected behavior
} else {
throw error; // Actual error
}
}Multi-Party Workflows
// Merchant service starts thread
const merchantConn = await Threadify.connect('api-key', 'merchant-service');
const thread = await merchantConn.start('product_delivery', 'merchant-service');
await thread.step('order_placed')
.addContext({ order_id: 'ORD-123' })
.success();
// Create invitation for logistics
const token = await thread.inviteParty({ role: 'logistics' });
// Logistics service joins
const logisticsConn = await Threadify.connect('api-key', 'logistics-service');
const logisticsThread = await logisticsConn.join(token);
await logisticsThread.step('shipment_created')
.addContext({ tracking_number: 'TRACK-456' })
.success();Contract Validation
When using contracts, the server validates:
- Entry Point: First step must be a contract entry point
- Step Existence: Step name must exist in contract
- Required Context: All required business context fields must be present
- Role Validation: Service role must match step's allowed roles
Example Contract (YAML):
name: product_delivery
version: "1.0"
entry_points:
- order_placed
steps:
order_placed:
roles: [merchant]
business_context:
required: [order_id, product_id, quantity]
optional: [customer_notes]SDK Usage:
// ✅ Valid - entry point with required context
await thread.step('order_placed')
.addContext({
order_id: 'ORD-123',
product_id: 'PROD-456',
quantity: '2'
})
.success();
// ❌ Invalid - missing required context field 'quantity'
await thread.step('order_placed')
.addContext({
order_id: 'ORD-123',
product_id: 'PROD-456'
})
.success(); // Server will reject with validation errorError Handling
Common Errors
1. Connection Errors
try {
const connection = await Threadify.connect('invalid-key');
} catch (error) {
console.error('Connection failed:', error.message);
// "WebSocket error: ..." or "Connection timeout"
}2. Validation Errors
try {
await thread.step('invalid_step').success();
} catch (error) {
console.error('Validation failed:', error.message);
// "Step 'invalid_step' does not exist in contract"
}3. Idempotency (Not an Error)
// First call - succeeds
await thread.step('order_placed')
.addContext({ order_id: 'ORD-123' })
.success();
// Second call with same context - silently ignored (idempotent)
await thread.step('order_placed')
.addContext({ order_id: 'ORD-123' })
.success(); // No error thrown, returns immediatelyExamples
Example 1: Simple Order Processing
import { Threadify } from '@threadify/sdk';
async function processOrder(orderId) {
const connection = await Threadify.connect('api-key', 'order-service');
try {
const thread = await connection.start();
// Validate order
await thread.step('validate_order')
.addContext({ order_id: orderId })
.success('Order validated');
// Process payment
await thread.step('process_payment')
.addContext({ order_id: orderId, amount: '99.99' })
.addRefs({ stripe_payment_id: 'pi_123' })
.success('Payment processed');
// Ship order
await thread.step('ship_order')
.addContext({ order_id: orderId })
.success('Order shipped');
console.log('Order processed:', thread.getThreadId());
} finally {
await connection.close();
}
}Example 2: Contract-Based Workflow with Error Handling
import { Threadify } from '@threadify/sdk';
async function deliverProduct(orderData) {
const connection = await Threadify.connect('api-key', 'merchant-service');
try {
const thread = await connection.start('product_delivery', 'merchant-service');
// Entry point step
await thread.step('order_placed')
.addContext({
order_id: orderData.orderId,
product_id: orderData.productId,
quantity: String(orderData.quantity)
})
.success();
// Inventory check
const inventoryAvailable = await checkInventory(orderData.productId);
if (!inventoryAvailable) {
await thread.step('order_cancelled')
.addContext({ reason: 'insufficient_inventory' })
.failed('Not enough inventory');
return;
}
// Continue workflow...
await thread.step('payment_processed')
.addContext({ amount: orderData.amount })
.success();
} catch (error) {
console.error('Workflow error:', error);
throw error;
} finally {
await connection.close();
}
}Example 3: Multi-Service Collaboration
// Merchant Service
async function merchantFlow() {
const conn = await Threadify.connect('api-key', 'merchant-service');
const thread = await conn.start('product_delivery', 'merchant-service');
await thread.step('order_placed')
.addContext({ order_id: 'ORD-123', product_id: 'PROD-456' })
.success();
// Invite logistics partner
const token = await thread.inviteParty({ role: 'logistics' });
console.log('Invitation token:', token);
return { threadId: thread.getThreadId(), token };
}
// Logistics Service
async function logisticsFlow(invitationToken) {
const conn = await Threadify.connect('api-key', 'logistics-service');
const thread = await conn.join(invitationToken);
await thread.step('shipment_created')
.addContext({ tracking_number: 'TRACK-789' })
.success();
await thread.step('in_transit')
.addContext({ location: 'warehouse' })
.success();
await conn.close();
}Best Practices
- Always close connections: Use
try/finallyto ensureconnection.close()is called - Use idempotency keys for critical steps: Prevents duplicate processing in case of retries
- Add meaningful context: Include all relevant business data for audit and debugging
- Handle validation errors: Contract-based workflows will reject invalid steps
- Use external refs: Link to external systems (payment IDs, order IDs, etc.)
- Leverage fluent API: Chain methods for cleaner, more readable code
Troubleshooting
WebSocket Connection Issues
Problem: Connection timeout or fails to connect
Solution:
- Verify the WebSocket URL is correct
- Check that the Threadify Engine is running
- Ensure API key is valid
- Check firewall/network settings
Validation Errors
Problem: Steps are rejected with validation errors
Solution:
- Verify step name exists in contract
- Ensure all required business context fields are provided
- Check that the service role matches the step's allowed roles
- Confirm the first step is an entry point
Idempotency Issues
Problem: Steps are silently ignored
Solution:
- This is expected behavior for duplicate submissions
- Check if the step was already recorded with the same context
- Use manual idempotency keys if you need different behavior
API Endpoint Reference
The SDK communicates with these WebSocket actions:
connect- Establish connectionstartThread- Create new threadjoinThread- Join existing threadrecordThreadEvent- Submit step eventinviteParty- Create invitation token (not yet implemented on server)closeConnection- Close connection
Data Retrieval API
The SDK provides a powerful GraphQL-based data retrieval system for accessing archived thread data.
Connection Methods
connection.getThread(threadId)
Get a thread by ID with access to all its data.
Parameters:
threadId(string, required): Thread ID
Returns: Promise<ArchivedThread>
Example:
const thread = await connection.getThread('thread-uuid-123');
console.log(thread.id, thread.status, thread.contractName);connection.getThreadsByRef({ refKey, refValue })
Find threads by external reference.
Parameters:
refKey(string, required): Reference key (e.g., "orderId")refValue(string, required): Reference value (e.g., "ORDER-12345")
Returns: Promise<Array<ArchivedThread>>
Example:
const threads = await connection.getThreadsByRef({
refKey: 'orderId',
refValue: 'ORDER-12345'
});
threads.forEach(thread => {
console.log(`Thread ${thread.id} for order ORDER-12345`);
});ArchivedThread Methods
thread.steps(filters)
Get all steps for this thread, optionally filtered.
Parameters:
filters(object, optional):stepName(string): Filter by step nameidempotencyKey(string): Filter by idempotency key
Returns: Promise<Array<ArchivedStep>>
Example:
// Get all steps
const allSteps = await thread.steps();
// Get specific step by name
const orderSteps = await thread.steps({ stepName: 'order_placed' });
// Get exact step instance
const exactStep = await thread.steps({
stepName: 'order_placed',
idempotencyKey: 'order-123'
});thread.getStep(stepIdentifier)
Get a specific step by name or "name:idempotencyKey".
Parameters:
stepIdentifier(string, required): Step name or "stepName:idempKey"
Returns: Promise<ArchivedStep>
Example:
// Get by name (returns first match)
const step = await thread.getStep('order_placed');
// Get exact instance
const exactStep = await thread.getStep('order_placed:order-123');thread.validationResults(options)
Get validation results for this thread.
Parameters:
options(object, optional):limit(number): Maximum results to returnstepName(string): Filter by step namevalidationType(string): Filter by validation type
Returns: Promise<Array<ValidationResult>>
Example:
const validations = await thread.validationResults({ limit: 10 });
validations.forEach(val => {
console.log(`${val.stepName}: ${val.overallStatus}`);
console.log(` Critical: ${val.criticalCount}, Warnings: ${val.warningCount}`);
});thread.getCompleteData(options) ⭐ NEW
Get complete thread picture with all nested data in a single GraphQL query. This is the most efficient way to retrieve all thread data.
Parameters:
options(object, optional):stepHistoryLimit(number): Limit for step history per step (default: 50)validationLimit(number): Limit for validation results (default: 10)stepName(string): Filter steps by nameidempotencyKey(string): Filter steps by idempotency key
Returns: Promise<Object> with structure:
{
id, contractId, contractVersion, contractName,
ownerId, companyId, status, lastHash, refs,
startedAt, completedAt, error,
steps: [{
threadId, stepName, idempotencyKey, status,
retryCount, firstSeenAt, lastUpdatedAt,
latestStepID, previousStep,
history: [{ attempt, timestamp, status, context, duration, error }]
}],
validationResults: [{
validationId, threadId, stepId, stepName,
idempotencyKey, timestamp, overallStatus,
hasCriticalViolation, criticalCount, warningCount,
validations: [{ type, message, field, expected, actual, rule }]
}]
}Example:
// Get everything in one query
const completeData = await thread.getCompleteData({
stepHistoryLimit: 50,
validationLimit: 10
});
console.log('Thread:', completeData.id);
console.log('Steps:', completeData.steps.length);
completeData.steps.forEach(step => {
console.log(` ${step.stepName}: ${step.status}`);
console.log(` History: ${step.history.length} attempts`);
});
console.log('Validations:', completeData.validationResults.length);Benefits:
- ✅ Single network request (much faster)
- ✅ Atomic data snapshot
- ✅ Reduced server load
- ✅ Perfect for dashboards and audit trails
ArchivedStep Methods
step.history(options)
Get execution history for this step.
Parameters:
options(object, optional):limit(number): Maximum records (default: 100)offset(number): Pagination offset (default: 0)startAt(string): ISO timestamp to filter fromendAt(string): ISO timestamp to filter toactivityType(string): Filter by activity typeactor(string): Filter by actor
Returns: Promise<Array<StepHistory>>
Example:
const step = await thread.getStep('order_placed');
// Get all history
const history = await step.history({ limit: 100 });
// Get filtered history
const recentHistory = await step.history({
limit: 10,
activityType: 'step_recorded',
startAt: '2026-01-01T00:00:00Z'
});
history.forEach(record => {
console.log(`Attempt ${record.attempt}: ${record.status} at ${record.timestamp}`);
console.log(` Duration: ${record.duration}ms`);
});Data Retrieval Examples
Example 1: Complete Thread Audit Trail
import { Threadify } from '@threadify/sdk';
const connection = await Threadify.connect('api-key', 'audit-service');
// Get complete thread picture in one query
const thread = await connection.getThread('thread-uuid');
const completeData = await thread.getCompleteData({
stepHistoryLimit: 100,
validationLimit: 50
});
// Generate audit report
console.log('=== Thread Audit Report ===');
console.log(`Thread ID: ${completeData.id}`);
console.log(`Contract: ${completeData.contractName} v${completeData.contractVersion}`);
console.log(`Status: ${completeData.status}`);
console.log(`Duration: ${new Date(completeData.completedAt) - new Date(completeData.startedAt)}ms`);
console.log('\n=== Steps ===');
completeData.steps.forEach(step => {
console.log(`\n${step.stepName}:${step.idempotencyKey}`);
console.log(` Status: ${step.status}`);
console.log(` Retries: ${step.retryCount}`);
console.log(` History:`);
step.history.forEach(h => {
console.log(` ${h.timestamp}: ${h.status} (${h.duration}ms)`);
});
});
console.log('\n=== Validations ===');
completeData.validationResults.forEach(val => {
if (val.hasCriticalViolation) {
console.log(`❌ ${val.stepName}: ${val.criticalCount} critical issues`);
}
});Example 2: Find Threads by Order ID
// Find all threads for a specific order
const threads = await connection.getThreadsByRef({
refKey: 'orderId',
refValue: 'ORDER-12345'
});
console.log(`Found ${threads.length} threads for order ORDER-12345`);
for (const thread of threads) {
const data = await thread.getCompleteData();
console.log(`Thread ${data.id}: ${data.status}`);
console.log(` Steps: ${data.steps.length}`);
console.log(` Started: ${data.startedAt}`);
}Example 3: Step-Level Analysis
const thread = await connection.getThread('thread-uuid');
const step = await thread.getStep('payment_processing');
// Get detailed history
const history = await step.history({ limit: 50 });
console.log(`Payment Processing - ${history.length} attempts`);
const failures = history.filter(h => h.status === 'failed');
console.log(`Failed attempts: ${failures.length}`);
failures.forEach(f => {
console.log(` ${f.timestamp}: ${f.error}`);
});Support
For issues, questions, or contributions:
- GitHub: [ThreadifyEngine Repository]
- Documentation: This file
- Examples: See
/tests/e2e-data-retrieval.test.js
License
[Your License Here]