CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-xrm-webapi-client

Comprehensive JavaScript framework for Microsoft Dynamics CRM/365 WebAPI integration with promise-based CRUD operations, batch processing, and 200+ pre-implemented actions

Overview
Eval results
Files

batch-processing.mddocs/

Batch Processing

Transaction-based batch operations allowing multiple requests to be sent together with rollback capabilities. Batches can contain GET requests directly and change sets for transactional operations, providing efficient bulk processing with automatic error handling.

Capabilities

Batch Class

Container for multiple requests that can be processed as a single operation.

/**
 * Batch request container for sending multiple operations together
 */
class Batch implements BatchParameters {
    /** Batch identifier name */
    name?: string;
    /** Change sets containing transactional operations */
    changeSets?: Array<ChangeSet>;
    /** GET requests (cannot be in change sets) */
    requests?: Array<BatchRequest>;
    /** Headers to apply to the entire batch */
    headers?: Array<Header>;
    /** Override async setting for batch */
    async?: boolean;
    /** Internal flag for URL length handling */
    isOverLengthGet?: boolean;

    constructor(parameters: BatchParameters);
}

interface BatchParameters extends BaseParameters {
    name?: string;
    changeSets?: Array<ChangeSet>;
    requests?: Array<BatchRequest>;
    isOverLengthGet?: boolean;
}

ChangeSet Class

Transactional container where all operations succeed or fail together.

/**
 * Transactional container for batch operations - all requests succeed or fail together
 */
class ChangeSet implements ChangeSetParameters {
    /** Change set identifier name */
    name?: string;
    /** Array of batch requests in this change set */
    requests?: Array<BatchRequest>;

    constructor(parameters: ChangeSetParameters);
}

interface ChangeSetParameters {
    name?: string;
    requests?: Array<BatchRequest>;
}

BatchRequest Class

Individual request within a batch or change set.

/**
 * Individual request that can be included in batches or change sets
 */
class BatchRequest implements BatchRequestParameters {
    /** HTTP method (GET, POST, PATCH, DELETE) */
    method: string;
    /** Request URL (optional, can be auto-generated) */
    url?: string;
    /** Request payload as JSON string */
    payload?: string;
    /** Headers specific to this request */
    headers?: Array<Header>;
    /** Content ID for referencing in other requests */
    contentId?: string;

    constructor(params: BatchRequestParameters);
}

interface BatchRequestParameters {
    method: string;
    url?: string;
    payload?: string;
    headers?: Array<Header>;
    contentId?: string;
}

Send Batch Operation

Executes a batch request and returns comprehensive response information.

/**
 * Send a batch request to CRM
 * @param batch - Batch object containing requests and change sets
 * @returns Promise resolving to batch response with results and error information
 */
function SendBatch(batch: Batch): Promise<BatchResponse> | BatchResponse;

Batch Response Classes

/**
 * Response from a batch operation containing all individual responses
 */
class BatchResponse implements BatchResponseParameters {
    /** Batch identifier name */
    name?: string;
    /** Responses from change sets */
    changeSetResponses?: Array<ChangeSetResponse>;
    /** Responses from direct batch requests (GET operations) */
    batchResponses?: Array<Response>;
    /** Whether any requests in the batch failed */
    isFaulted?: boolean;
    /** Collection of all error responses */
    errors?: Array<string>;
    /** Original XMLHttpRequest object */
    xhr?: XMLHttpRequest;

    constructor(parameters: BatchResponseParameters);
}

interface BatchResponseParameters {
    name?: string;
    changeSetResponses?: Array<ChangeSetResponse>;
    batchResponses?: Array<Response>;
    isFaulted?: boolean;
    errors?: Array<string>;
    xhr?: XMLHttpRequest;
}

/**
 * Response from a change set containing individual request responses
 */
class ChangeSetResponse {
    /** Change set identifier name */
    name?: string;
    /** Individual responses for each request in the change set */
    responses?: Array<Response>;
}

/**
 * Individual response from a batch request
 */
class Response implements ResponseParameters {
    /** Raw response data */
    rawData?: string;
    /** Content ID if specified in request */
    contentId?: string;
    /** Parsed response payload */
    payload?: object;
    /** HTTP status code */
    status?: string;
    /** Response headers as key-value object */
    headers?: any;

    constructor(parameters: ResponseParameters);
}

interface ResponseParameters {
    rawData?: string;
    contentId?: string;
    payload?: object;
    status?: string;
    headers?: any;
}

Usage Examples

Basic Batch with Change Set

// Create multiple related records in a transaction
const batch = new WebApiClient.Batch({
    changeSets: [
        new WebApiClient.ChangeSet({
            requests: [
                WebApiClient.Create({
                    entityName: "account",
                    entity: { name: "Parent Account" },
                    headers: [{ key: "Prefer", value: "return=representation" }],
                    asBatch: true
                }),
                WebApiClient.Create({
                    entityName: "contact",
                    entity: {
                        firstname: "John",
                        lastname: "Doe",
                        "parentcustomerid_account@odata.bind": "/accounts($1)" // Reference to first request
                    },
                    asBatch: true
                })
            ]
        })
    ]
});

const batchResponse = await WebApiClient.SendBatch(batch);

if (batchResponse.isFaulted) {
    console.log("Batch failed:", batchResponse.errors);
} else {
    console.log("All operations succeeded");
    console.log(batchResponse.changeSetResponses[0].responses);
}

Mixed Batch with GET and Change Operations

// Combine read operations with transactional writes
const batch = new WebApiClient.Batch({
    // GET requests go directly in batch (not in change sets)
    requests: [
        WebApiClient.Retrieve({
            entityName: "systemuser",
            queryParams: "?$select=fullname,systemuserid&$filter=isdisabled eq false",
            asBatch: true
        })
    ],
    // Change operations go in change sets for transactions
    changeSets: [
        new WebApiClient.ChangeSet({
            requests: [
                WebApiClient.Update({
                    entityName: "account",
                    entityId: "12345678-1234-1234-1234-123456789abc",
                    entity: { name: "Updated Account" },
                    asBatch: true
                }),
                WebApiClient.Delete({
                    entityName: "contact",
                    entityId: "87654321-4321-4321-4321-cba987654321",
                    asBatch: true
                })
            ]
        })
    ]
});

const result = await WebApiClient.SendBatch(batch);

// Access GET request results
const userResults = result.batchResponses[0].payload;

// Access change set results
const changeResults = result.changeSetResponses[0].responses;

Batch with Actions and Functions

// Execute multiple CRM actions in a transaction
const batch = new WebApiClient.Batch({
    changeSets: [
        new WebApiClient.ChangeSet({
            requests: [
                WebApiClient.Execute(
                    WebApiClient.Requests.QualifyLeadRequest.with({
                        entityId: "lead-guid-here",
                        payload: {
                            CreateAccount: true,
                            CreateContact: true,
                            CreateOpportunity: true,
                            Status: 3
                        }
                    }),
                    { asBatch: true }
                ),
                WebApiClient.Execute(
                    WebApiClient.Requests.SendEmailRequest.with({
                        payload: {
                            TemplateId: "template-guid-here",
                            RegardingId: "opportunity-guid-here",
                            RegardingType: "opportunity"
                        }
                    }),
                    { asBatch: true }
                )
            ]
        })
    ]
});

const actionResults = await WebApiClient.SendBatch(batch);

Large FetchXML Automatic Batch

// Long FetchXML queries are automatically converted to batch requests
const longFetchXml = `
    <fetch mapping='logical'>
        <entity name='account'>
            <attribute name='name'/>
            <attribute name='accountid'/>
            <!-- Very long fetch query that exceeds URL limits -->
            <filter type='and'>
                <condition attribute='name' operator='like' value='%very long search term%'/>
                <!-- Many more conditions... -->
            </filter>
            <link-entity name='contact' from='parentcustomerid' to='accountid'>
                <!-- Many attributes and conditions -->
            </link-entity>
        </entity>
    </fetch>`;

// This will automatically be sent as a batch if URL length exceeds limits
const results = await WebApiClient.Retrieve({
    entityName: "account",
    fetchXml: longFetchXml
});

Error Handling and Response Processing

const batch = new WebApiClient.Batch({
    changeSets: [
        new WebApiClient.ChangeSet({
            name: "AccountOperations",
            requests: [
                WebApiClient.Create({
                    entityName: "account",
                    entity: { name: "Test Account 1" },
                    asBatch: true
                }),
                WebApiClient.Create({
                    entityName: "account",
                    entity: { name: "Test Account 2" },
                    asBatch: true
                }),
                WebApiClient.Update({
                    entityName: "account",
                    entityId: "existing-account-guid",
                    entity: { name: "Updated Name" },
                    asBatch: true
                })
            ]
        })
    ]
});

try {
    const response = await WebApiClient.SendBatch(batch);

    if (response.isFaulted) {
        console.log("Batch contained errors:");
        response.errors.forEach((error, index) => {
            console.log(`Error ${index + 1}:`, error);
        });

        // Check individual change set responses
        response.changeSetResponses.forEach((changeSetResp, csIndex) => {
            console.log(`Change set ${csIndex} (${changeSetResp.name}):`);
            changeSetResp.responses.forEach((resp, reqIndex) => {
                if (resp.status !== "200" && resp.status !== "201") {
                    console.log(`  Request ${reqIndex} failed:`, resp.status, resp.payload);
                } else {
                    console.log(`  Request ${reqIndex} succeeded:`, resp.payload);
                }
            });
        });
    } else {
        console.log("All batch operations succeeded");

        // Process successful results
        const changeSetResults = response.changeSetResponses[0].responses;
        changeSetResults.forEach((result, index) => {
            console.log(`Operation ${index} result:`, {
                status: result.status,
                entityId: result.headers["OData-EntityId"],
                data: result.payload
            });
        });
    }
} catch (networkError) {
    console.error("Network or request error:", networkError);
}

Content ID References

// Use content IDs to reference results from previous requests in the same batch
const batch = new WebApiClient.Batch({
    changeSets: [
        new WebApiClient.ChangeSet({
            requests: [
                // Create account with content ID
                new WebApiClient.BatchRequest({
                    method: "POST",
                    url: WebApiClient.GetApiUrl() + WebApiClient.GetSetName("account"),
                    payload: JSON.stringify({ name: "Parent Account" }),
                    headers: [
                        { key: "Content-Type", value: "application/json" },
                        { key: "Prefer", value: "return=representation" }
                    ],
                    contentId: "1"
                }),
                // Create contact referencing the account
                new WebApiClient.BatchRequest({
                    method: "POST",
                    url: WebApiClient.GetApiUrl() + WebApiClient.GetSetName("contact"),
                    payload: JSON.stringify({
                        firstname: "John",
                        lastname: "Doe",
                        "parentcustomerid_account@odata.bind": "$1" // References content ID 1
                    }),
                    headers: [
                        { key: "Content-Type", value: "application/json" }
                    ],
                    contentId: "2"
                })
            ]
        })
    ]
});

const result = await WebApiClient.SendBatch(batch);

Best Practices

  1. Use Change Sets for Transactional Operations: Put related create, update, delete operations in change sets to ensure data consistency.

  2. Keep GET Requests Outside Change Sets: Retrieve operations must be placed directly in the batch requests array.

  3. Limit Batch Size: Keep batches under 100 requests for optimal performance.

  4. Handle Errors Properly: Always check the isFaulted property and process errors appropriately.

  5. Use Content IDs for Dependencies: When requests depend on results from previous requests in the same batch, use content IDs for references.

  6. Set Appropriate Headers: Include necessary headers like Prefer: return=representation when you need response data.

  7. Monitor Performance: Large batches may take longer to process and could timeout in some scenarios.

Install with Tessl CLI

npx tessl i tessl/npm-xrm-webapi-client

docs

batch-processing.md

configuration-utilities.md

crm-actions-functions.md

crud-operations.md

entity-associations.md

index.md

tile.json