import { ITGlueClient } from '../client'; import { QueryUtilOptions, QueryParams, RequestBody, BaseListResponse, BaseItemResponse, FlexibleAssetResource, FlexibleAssetTraits } from '../types'; /** * FlexibleAssets resource module for IT Glue API * * Provides methods to interact with the /flexible_assets endpoint and traits. * Flexible assets are custom data structures that allow you to track any type of information * not covered by standard IT Glue resources. They are defined by flexible asset types and * can contain custom fields (traits) such as text, numbers, dates, tags, and relationships. * Common examples include passwords, licenses, contracts, and custom inventory items. * * ## Related Resources * Flexible assets are commonly used with: * - {@link FlexibleAssetTypes} - Define the structure and fields for flexible assets * - {@link FlexibleAssetFields} - Individual field definitions within flexible asset types * - {@link Organizations} - Parent organizations that own flexible assets * - {@link Contacts} - People associated with or responsible for flexible assets * - {@link Configurations} - IT assets related to flexible assets * - {@link Documents} - Documentation related to flexible assets * - {@link Passwords} - Credentials stored as flexible assets * - {@link RelatedItems} - Create relationships between flexible assets and other resources * - {@link Tags} - Categorize and label flexible assets for better organization * - {@link Attachments} - Store files and documents related to flexible assets * * @see {@link FlexibleAssetTypes#list} for retrieving available flexible asset types * @see {@link FlexibleAssetFields#list} for retrieving fields for a flexible asset type * @see {@link Organizations#list} for retrieving flexible assets by organization * @see {@link RelatedItems#list} for managing relationships with flexible assets * * @example * import { ITGlueClient } from '../client'; * import { FlexibleAssets } from './resources/flexible-assets'; * * const client = new ITGlueClient({ apiKey: 'your-api-key' }); * const flexibleAssets = new FlexibleAssets(client); * * // List flexible assets * const list = await flexibleAssets.list(); * * // Get a single flexible asset * const asset = await flexibleAssets.get('123'); * * // Create a flexible asset * const created = await flexibleAssets.create({ * data: { * type: 'flexible_assets', * attributes: { name: 'Database License' } * } * }); * * // Update a flexible asset * const updated = await flexibleAssets.update('123', { * data: { * type: 'flexible_assets', * attributes: { name: 'Updated License' } * } * }); * * // Delete a flexible asset * await flexibleAssets.delete('123'); * * // Get traits for a flexible asset * const traits = await flexibleAssets.getTraits('123'); * * // Update traits for a flexible asset * const updatedTraits = await flexibleAssets.updateTraits('123', { * data: { * type: 'traits', * attributes: { 'license-key': 'ABC123XYZ' } * } * }); * * @category Assets */ export declare class FlexibleAssets { private client; private basePath; private paginationUtil; /** * Create a FlexibleAssets resource instance * @param {ITGlueClient} client - ITGlueClient instance */ constructor(client: ITGlueClient); /** * List all flexible assets * @param {QueryUtilOptions} [options] - Optional query parameters (filter, sort, page, etc.) * @param {boolean} [allPages=false] - If true, fetches all pages automatically * @returns {Promise>} List of flexible assets and pagination metadata * @example * // Basic usage - get first page of results * const results = await client.flexibleAssets.list(); * console.log(`Found ${results.data.length} flexible assets`); * console.log('Total pages:', results.meta.pagination.total_pages); * * @example * // Advanced usage with pagination and sorting * const results = await client.flexibleAssets.list({ * page: { number: 2, size: 50 }, * sort: '-updated_at', // Sort by most recently updated * include: ['flexible_asset_type', 'organization'] // Include related data * }); * * @example * // Filtering results by organization and type * const filtered = await client.flexibleAssets.list({ * filter: { * name: 'License', * organization_id: '123', * flexible_asset_type_id: '456' * }, * sort: 'name' * }); * * @example * // Get all results across multiple pages * const allResults = await client.flexibleAssets.list({}, true); // allPages = true * console.log(`Retrieved all ${allResults.data.length} flexible assets`); * * @example * // Manual pagination handling for large datasets * async function getAllFlexibleAssets() { * let page = 1; * let allResults = []; * let hasMore = true; * * while (hasMore) { * const response = await client.flexibleAssets.list({ * page: { number: page, size: 100 } * }); * * allResults = [...allResults, ...response.data]; * hasMore = response.meta.pagination.total_pages > page; * page++; * } * * return allResults; * } * * @example * // Error handling for list operations * try { * const results = await client.flexibleAssets.list({ * filter: { invalid_field: 'value' } * }); * } catch (error) { * if (error.response?.status === 400) { * console.log('Invalid filter parameters:', error.response.data.errors); * } else if (error.response?.status === 401) { * console.log('Authentication failed - check your API key'); * } else { * console.log('Request failed:', error.message); * } * } */ list(options?: QueryUtilOptions, allPages?: boolean): Promise>; /** * Get a single flexible asset by ID * @param {string} id - Flexible asset ID * @param {QueryParams} [params] - Optional query parameters * @returns {Promise>} Flexible asset resource * @example * // Basic usage - get by ID * const flexibleAsset = await client.flexibleAssets.get('123'); * console.log('Flexible asset name:', flexibleAsset.data.attributes.name); * * @example * // Get with related data included * const flexibleAssetWithRelated = await client.flexibleAssets.get('123', { * include: ['flexible_asset_type', 'organization', 'traits'] * }); * * // Access included data * const included = flexibleAssetWithRelated.included || []; * const assetType = included.find(item => item.type === 'flexible_asset_types'); * * @example * // Error handling for get operations * try { * const flexibleAsset = await client.flexibleAssets.get('invalid-id'); * } catch (error) { * if (error.response?.status === 404) { * console.log('Flexible asset not found'); * } else if (error.response?.status === 403) { * console.log('Access denied - insufficient permissions'); * } else { * console.log('Error retrieving flexible asset:', error.message); * } * } * * @example * // Safe get with existence check * async function safeGetFlexibleAsset(id) { * try { * const flexibleAsset = await client.flexibleAssets.get(id); * return flexibleAsset.data; * } catch (error) { * if (error.response?.status === 404) { * return null; // Flexible asset doesn't exist * } * throw error; // Re-throw other errors * } * } */ get(id: string, params?: QueryParams): Promise>; /** * Create a new flexible asset * @param {RequestBody} data - Flexible asset data (must be formatted according to JSON:API spec) * @returns {Promise>} Created flexible asset resource * @example * // Basic creation with required fields * const newFlexibleAsset = await client.flexibleAssets.create({ * data: { * type: 'flexible_assets', * attributes: { * name: 'Database License', * traits: { * 'license-key': 'ABC123XYZ789', * 'expiration-date': '2024-12-31' * } * }, * relationships: { * organization: { * data: { type: 'organizations', id: '123' } * }, * flexible_asset_type: { * data: { type: 'flexible_asset_types', id: '456' } * } * } * } * }); * * console.log('Created flexible asset with ID:', newFlexibleAsset.data.id); * * @example * // Creation with all fields and complex traits * const newFlexibleAsset = await client.flexibleAssets.create({ * data: { * type: 'flexible_assets', * attributes: { * name: 'Microsoft Office 365 License', * traits: { * 'license-key': 'M365-ABC123-XYZ789', * 'seat-count': 100, * 'expiration-date': '2025-12-31', * 'cost-per-month': 1500.00, * 'tags': ['production', 'critical', 'subscription'], * 'primary-contact': { id: '789' }, * 'related-configurations': [{ id: '101' }, { id: '102' }], * 'notes': 'Enterprise license with advanced security features' * } * }, * relationships: { * organization: { * data: { type: 'organizations', id: '123' } * }, * flexible_asset_type: { * data: { type: 'flexible_asset_types', id: '456' } * } * } * } * }); * * @example * // Bulk creation with error handling * async function createMultipleFlexibleAssets(items) { * const results = []; * const errors = []; * * for (const item of items) { * try { * const created = await client.flexibleAssets.create({ * data: { * type: 'flexible_assets', * attributes: item, * relationships: { * organization: { * data: { type: 'organizations', id: item.organizationId } * }, * flexible_asset_type: { * data: { type: 'flexible_asset_types', id: item.typeId } * } * } * } * }); * results.push(created.data); * } catch (error) { * errors.push({ item, error: error.message }); * } * } * * return { results, errors }; * } * * @example * // Error handling for validation failures * try { * const newFlexibleAsset = await client.flexibleAssets.create({ * data: { * type: 'flexible_assets', * attributes: { * // Missing required name field * traits: { 'license-key': 'ABC123' } * } * } * }); * } catch (error) { * if (error.response?.status === 422) { * console.log('Validation errors:'); * error.response.data.errors.forEach(err => { * console.log(`- ${err.detail} (${err.source?.pointer})`); * }); * } else if (error.response?.status === 403) { * console.log('Permission denied - cannot create flexible asset'); * } else { * console.log('Creation failed:', error.message); * } * } */ create(data: RequestBody): Promise>; /** * Update a flexible asset by ID * @param {string} id - Flexible asset ID * @param {RequestBody} data - Updated flexible asset data (must be formatted according to JSON:API spec) * @returns {Promise>} Updated flexible asset resource * @example * // Basic update - modify specific fields * const updatedFlexibleAsset = await client.flexibleAssets.update('123', { * data: { * type: 'flexible_assets', * attributes: { * name: 'Updated Database License', * traits: { * 'seat-count': 75, * 'notes': 'Upgraded license for additional users' * } * } * } * }); * * console.log('Updated flexible asset:', updatedFlexibleAsset.data.attributes.name); * * @example * // Partial update with complex trait changes * const updatedFlexibleAsset = await client.flexibleAssets.update('123', { * data: { * type: 'flexible_assets', * attributes: { * traits: { * 'expiration-date': '2025-12-31', * 'tags': ['production', 'critical', 'renewed'], * 'cost-per-month': 1750.00, * 'primary-contact': { id: '456' } * // Only include traits you want to change * } * } * } * }); * * @example * // Conditional update based on current state * async function conditionalUpdateFlexibleAsset(id, updates) { * try { * // First, get current state * const current = await client.flexibleAssets.get(id); * * // Check if update is needed * const currentTraits = current.data.attributes.traits || {}; * const needsUpdate = Object.keys(updates.traits || {}).some( * key => currentTraits[key] !== updates.traits[key] * ); * * if (!needsUpdate) { * console.log('Flexible asset is already up to date'); * return current; * } * * // Perform update * return await client.flexibleAssets.update(id, { * data: { * type: 'flexible_assets', * attributes: updates * } * }); * } catch (error) { * console.error('Update failed:', error.message); * throw error; * } * } * * @example * // Error handling for update operations * try { * const updated = await client.flexibleAssets.update('123', { * data: { * type: 'flexible_assets', * attributes: { * traits: { * 'invalid-trait': 'value' * } * } * } * }); * } catch (error) { * if (error.response?.status === 404) { * console.log('Flexible asset not found'); * } else if (error.response?.status === 422) { * console.log('Validation failed:', error.response.data.errors); * } else if (error.response?.status === 409) { * console.log('Conflict - flexible asset may have been modified by another user'); * } else { * console.log('Update failed:', error.message); * } * } */ update(id: string, data: RequestBody): Promise>; /** * Delete a flexible asset by ID * @param {string} id - Flexible asset ID * @returns {Promise} * @example * // Basic deletion * await client.flexibleAssets.delete('123'); * console.log('Flexible asset deleted successfully'); * * @example * // Safe deletion with confirmation * async function safeDeleteFlexibleAsset(id) { * try { * // First verify the flexible asset exists * const flexibleAsset = await client.flexibleAssets.get(id); * console.log(`Deleting flexible asset: ${flexibleAsset.data.attributes.name}`); * * // Perform deletion * await client.flexibleAssets.delete(id); * console.log('Flexible asset deleted successfully'); * return true; * } catch (error) { * if (error.response?.status === 404) { * console.log('Flexible asset not found - may already be deleted'); * return false; * } * throw error; * } * } * * @example * // Bulk deletion with error handling * async function deleteMultipleFlexibleAssets(ids) { * const results = []; * * for (const id of ids) { * try { * await client.flexibleAssets.delete(id); * results.push({ id, status: 'deleted' }); * } catch (error) { * results.push({ * id, * status: 'error', * error: error.response?.status === 404 ? 'not_found' : error.message * }); * } * } * * return results; * } * * @example * // Error handling for delete operations * try { * await client.flexibleAssets.delete('123'); * } catch (error) { * if (error.response?.status === 404) { * console.log('Flexible asset not found - may already be deleted'); * } else if (error.response?.status === 403) { * console.log('Permission denied - cannot delete flexible asset'); * } else if (error.response?.status === 409) { * console.log('Cannot delete - flexible asset is referenced by other resources'); * } else { * console.log('Deletion failed:', error.message); * } * } */ delete(id: string): Promise; /** * Get traits for a flexible asset * * Retrieves the trait values (custom field data) for a specific flexible asset. * Traits contain the actual data values for the custom fields defined by the * flexible asset type, such as license keys, passwords, dates, and relationships. * * @param {string} id - Flexible asset ID * @param {QueryParams} [params] - Optional query parameters * @returns {Promise>} Traits for the flexible asset * @example * // Basic usage - get flexible asset traits * const traits = await client.flexibleAssets.getTraits('123'); * console.log('Trait values:', traits.data.attributes); * * @example * // Get traits with related flexible asset data included * const traitsWithAsset = await client.flexibleAssets.getTraits('123', { * include: ['flexible_asset'] * }); * * // Access trait values and asset information * const traitValues = traitsWithAsset.data.attributes; * console.log('License key:', traitValues['license-key']); * console.log('Expiration date:', traitValues['expiration-date']); * * // Access included asset data * const included = traitsWithAsset.included || []; * const assetInfo = included.find(item => item.type === 'flexible_assets'); * if (assetInfo) { * console.log('Asset name:', assetInfo.attributes.name); * } * * @example * // Advanced trait analysis and validation * async function analyzeAssetTraits(assetId) { * try { * const traits = await client.flexibleAssets.getTraits(assetId); * const traitValues = traits.data.attributes; * * // Analyze trait completeness * const analysis = { * totalTraits: Object.keys(traitValues).length, * populatedTraits: 0, * emptyTraits: [], * expiringItems: [], * criticalFields: [], * relationships: [] * }; * * for (const [key, value] of Object.entries(traitValues)) { * if (value !== null && value !== undefined && value !== '') { * analysis.populatedTraits++; * * // Check for expiration dates * if (key.includes('expir') && typeof value === 'string') { * const expirationDate = new Date(value); * const thirtyDaysFromNow = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); * * if (expirationDate <= thirtyDaysFromNow) { * analysis.expiringItems.push({ * field: key, * expirationDate: value, * daysUntilExpiry: Math.ceil((expirationDate - new Date()) / (24 * 60 * 60 * 1000)) * }); * } * } * * // Identify critical fields (license keys, passwords, etc.) * if (key.includes('key') || key.includes('password') || key.includes('secret')) { * analysis.criticalFields.push({ * field: key, * hasValue: !!value, * valueLength: typeof value === 'string' ? value.length : 0 * }); * } * * // Identify relationships * if (typeof value === 'object' && value.id) { * analysis.relationships.push({ * field: key, * relatedId: value.id, * relatedType: value.type || 'unknown' * }); * } * } else { * analysis.emptyTraits.push(key); * } * } * * analysis.completeness = Math.round((analysis.populatedTraits / analysis.totalTraits) * 100); * * console.log('Trait Analysis:', analysis); * return analysis; * * } catch (error) { * console.error('Trait analysis failed:', error.message); * throw error; * } * } * * @example * // Bulk trait retrieval for multiple assets * async function getBulkTraits(assetIds) { * const results = []; * const errors = []; * * // Process in batches to avoid overwhelming the API * const batchSize = 10; * for (let i = 0; i < assetIds.length; i += batchSize) { * const batch = assetIds.slice(i, i + batchSize); * * const batchPromises = batch.map(async (id) => { * try { * const traits = await client.flexibleAssets.getTraits(id); * return { * assetId: id, * traits: traits.data.attributes, * status: 'success' * }; * } catch (error) { * return { * assetId: id, * error: error.message, * status: 'error' * }; * } * }); * * const batchResults = await Promise.all(batchPromises); * * batchResults.forEach(result => { * if (result.status === 'success') { * results.push(result); * } else { * errors.push(result); * } * }); * * // Small delay between batches * if (i + batchSize < assetIds.length) { * await new Promise(resolve => setTimeout(resolve, 100)); * } * } * * console.log(`Retrieved traits for ${results.length} assets, ${errors.length} errors`); * return { results, errors }; * } * * @example * // Extract and validate specific trait types * async function extractSpecificTraits(assetId, traitNames) { * try { * const traits = await client.flexibleAssets.getTraits(assetId); * const traitValues = traits.data.attributes; * * const result = {}; * const missing = []; * const invalid = []; * * traitNames.forEach(name => { * const value = traitValues[name]; * * if (value === null || value === undefined) { * missing.push(name); * result[name] = null; * } else { * result[name] = value; * * // Validate specific trait types * if (name.includes('email') && typeof value === 'string') { * const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; * if (!emailRegex.test(value)) { * invalid.push({ field: name, reason: 'Invalid email format' }); * } * } * * if (name.includes('url') && typeof value === 'string') { * try { * new URL(value); * } catch { * invalid.push({ field: name, reason: 'Invalid URL format' }); * } * } * * if (name.includes('date') && typeof value === 'string') { * const date = new Date(value); * if (isNaN(date.getTime())) { * invalid.push({ field: name, reason: 'Invalid date format' }); * } * } * } * }); * * return { * traits: result, * validation: { * missing, * invalid, * isValid: missing.length === 0 && invalid.length === 0 * } * }; * * } catch (error) { * console.error('Failed to extract traits:', error.message); * return null; * } * } * * @example * // Real-world scenario: License compliance audit * async function auditLicenseCompliance(organizationId) { * try { * // Get all license-type flexible assets for the organization * const licenses = await client.flexibleAssets.list({ * filter: { * organization_id: organizationId, * flexible_asset_type_name: 'Software License' * } * }, true); * * const auditResults = { * totalLicenses: licenses.data.length, * compliant: [], * expiringSoon: [], * expired: [], * missingInfo: [], * overAllocated: [] * }; * * const today = new Date(); * const thirtyDaysFromNow = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); * * for (const license of licenses.data) { * try { * const traits = await client.flexibleAssets.getTraits(license.id); * const traitValues = traits.data.attributes; * * const licenseInfo = { * id: license.id, * name: license.attributes.name, * licenseKey: traitValues['license-key'], * expirationDate: traitValues['expiration-date'], * seatCount: traitValues['seat-count'], * usedSeats: traitValues['used-seats'], * cost: traitValues['annual-cost'] * }; * * // Check for missing critical information * const missingFields = []; * if (!licenseInfo.licenseKey) missingFields.push('license-key'); * if (!licenseInfo.expirationDate) missingFields.push('expiration-date'); * if (!licenseInfo.seatCount) missingFields.push('seat-count'); * * if (missingFields.length > 0) { * auditResults.missingInfo.push({ * ...licenseInfo, * missingFields * }); * continue; * } * * // Check expiration status * const expirationDate = new Date(licenseInfo.expirationDate); * if (expirationDate <= today) { * auditResults.expired.push(licenseInfo); * } else if (expirationDate <= thirtyDaysFromNow) { * auditResults.expiringSoon.push({ * ...licenseInfo, * daysUntilExpiry: Math.ceil((expirationDate - today) / (24 * 60 * 60 * 1000)) * }); * } else { * auditResults.compliant.push(licenseInfo); * } * * // Check for over-allocation * if (licenseInfo.usedSeats && licenseInfo.seatCount && * licenseInfo.usedSeats > licenseInfo.seatCount) { * auditResults.overAllocated.push({ * ...licenseInfo, * overageCount: licenseInfo.usedSeats - licenseInfo.seatCount * }); * } * * } catch (traitError) { * console.error(`Failed to get traits for license ${license.id}:`, traitError.message); * } * } * * // Generate compliance summary * const summary = { * complianceRate: Math.round((auditResults.compliant.length / auditResults.totalLicenses) * 100), * issuesFound: auditResults.expiringSoon.length + auditResults.expired.length + * auditResults.missingInfo.length + auditResults.overAllocated.length, * totalValue: auditResults.compliant.concat(auditResults.expiringSoon) * .reduce((sum, license) => sum + (license.cost || 0), 0) * }; * * console.log('License Compliance Audit Results:', { ...auditResults, summary }); * return { ...auditResults, summary }; * * } catch (error) { * console.error('License compliance audit failed:', error.message); * throw error; * } * } * * @example * // Error handling for trait retrieval * try { * const traits = await client.flexibleAssets.getTraits('invalid-id'); * } catch (error) { * if (error.response?.status === 404) { * console.log('Flexible asset not found'); * } else if (error.response?.status === 403) { * console.log('Access denied to flexible asset traits'); * } else { * console.log('Error retrieving traits:', error.message); * } * } */ getTraits(id: string, params?: QueryParams): Promise>; /** * Update traits for a flexible asset * * Updates the trait values (custom field data) for an existing flexible asset. * This is used to update the custom fields defined by the flexible asset type. * Different trait types require different value formats (strings, numbers, objects for relationships, arrays for tags). * * @param {string} id - Flexible asset ID * @param {RequestBody} data - Traits data (must be formatted according to JSON:API spec) * @returns {Promise>} Updated traits resource * @throws {Error} When flexible asset not found (404) or validation fails (422) * @example * // Update string and number traits * const updatedTraits = await client.flexibleAssets.updateTraits('123', { * data: { * type: 'traits', * attributes: { * 'license-key': 'NEW123XYZ789', * 'seat-count': 100, * 'notes': 'Updated license information' * } * } * }); * * @example * // Update complex traits (relationships, tags, dates) * const complexTraits = await client.flexibleAssets.updateTraits('123', { * data: { * type: 'traits', * attributes: { * 'expiration-date': '2025-12-31', * 'tags': ['production', 'critical', 'updated'], * 'primary-contact': { id: '456' }, * 'related-configurations': [{ id: '789' }, { id: '101' }], * 'cost-per-month': 2000.00 * } * } * }); * * @example * // Partial trait updates (only update specific traits) * const partialUpdate = await client.flexibleAssets.updateTraits('123', { * data: { * type: 'traits', * attributes: { * 'seat-count': 150, // Only update seat count * 'last-reviewed': new Date().toISOString().split('T')[0] // Update review date * } * } * }); * * @example * // Conditional trait updates based on current values * async function conditionalTraitUpdate(id, newTraits) { * try { * // Get current traits * const current = await client.flexibleAssets.getTraits(id); * const currentTraits = current.data.attributes; * * // Check if any traits need updating * const needsUpdate = Object.keys(newTraits).some( * key => currentTraits[key] !== newTraits[key] * ); * * if (!needsUpdate) { * console.log('Traits are already up to date'); * return current; * } * * // Perform update * return await client.flexibleAssets.updateTraits(id, { * data: { * type: 'traits', * attributes: newTraits * } * }); * } catch (error) { * console.error('Trait update failed:', error.message); * throw error; * } * } * * @example * // Error handling for trait updates * try { * const updatedTraits = await client.flexibleAssets.updateTraits('123', { * data: { * type: 'traits', * attributes: { * 'invalid-trait': 'value' * } * } * }); * } catch (error) { * if (error.response?.status === 422) { * console.log('Trait validation failed:', error.response.data.errors); * } else if (error.response?.status === 404) { * console.log('Flexible asset not found'); * } else if (error.response?.status === 403) { * console.log('Permission denied - cannot update traits'); * } else { * console.log('Trait update failed:', error.message); * } * } * * @example * // Bulk trait updates for multiple assets * async function updateMultipleAssetTraits(updates) { * const results = []; * * for (const { id, traits } of updates) { * try { * const updated = await client.flexibleAssets.updateTraits(id, { * data: { * type: 'traits', * attributes: traits * } * }); * results.push({ id, status: 'updated', data: updated.data }); * } catch (error) { * results.push({ * id, * status: 'error', * error: error.message * }); * } * } * * return results; * } */ updateTraits(id: string, data: RequestBody): Promise>; }