MaintainX Upgrade & Migration
Current State
!npm list 2>/dev/null | head -20
Overview
Handle MaintainX API version upgrades, deprecations, and breaking changes with a safe, incremental migration strategy.
Prerequisites
- Existing MaintainX integration
- Test environment with separate API key
- Version control (git) for all integration code
Instructions
Step 1: Audit Current API Usage
import { readFileSync, readdirSync, statSync } from 'fs';
import { join } from 'path';
function findApiCalls(dir: string): Array<{ file: string; line: number; endpoint: string }> {
const results: Array<{ file: string; line: number; endpoint: string }> = [];
function scan(d: string) {
for (const entry of readdirSync(d)) {
const full = join(d, entry);
if (statSync(full).isDirectory()) {
if (!entry.startsWith('.') && entry !== 'node_modules') scan(full);
} else if (full.endsWith('.ts') || full.endsWith('.js')) {
const content = readFileSync(full, 'utf-8');
const lines = content.split('\n');
for (let i = 0; i < lines.length; i++) {
const match = lines[i].match(/'"`[^'"`]*)/);
if (match) {
results.push({ file: full, line: i + 1, endpoint: match[1] });
}
}
}
}
}
scan(dir);
return results;
}
const calls = findApiCalls('./src');
console.log('=== MaintainX API Usage Audit ===');
console.log(`Found ${calls.length} API calls:\n`);
const grouped = new Map<string, typeof calls>();
for (const call of calls) {
const base = call.endpoint.split('?')[0].replace(/\/\d+/, '/:id');
const existing = grouped.get(base) || [];
existing.push(call);
grouped.set(base, existing);
}
for (const [endpoint, usages] of grouped) {
console.log(`${endpoint} (${usages.length} calls):`);
for (const u of usages) {
console.log(` ${u.file}:${u.line}`);
}
}
Step 2: Version Compatibility Layer
type ApiVersion = 'v1' | 'v2';
interface VersionAdapter {
baseUrl: string;
transformRequest(endpoint: string, data: any): { endpoint: string; data: any };
transformResponse(endpoint: string, data: any): any;
}
const adapters: Record<ApiVersion, VersionAdapter> = {
v1: {
baseUrl: 'https://api.getmaintainx.com/v1',
transformRequest: (endpoint, data) => ({ endpoint, data }),
transformResponse: (endpoint, data) => data,
},
v2: {
baseUrl: 'https://api.getmaintainx.com/v2',
transformRequest: (endpoint, data) => {
if (endpoint.startsWith('/workorders') && data) {
if (data.) {
data. = data.;
data.;
}
}
{ endpoint, data };
},
: {
(data.) {
data. = data.;
}
data;
},
},
};
{
: ;
() {
. = adapters[version];
}
() {
{ : ep, : d } = ..(endpoint, data);
response = (, {
method,
: {
: ,
: ,
},
: d ? .(d) : ,
});
result = response.();
..(ep, result);
}
}
Step 3: Feature Flag Migration
const MIGRATION_FLAGS: Record<string, boolean> = {
USE_V2_WORKORDERS: false,
USE_V2_ASSETS: false,
USE_V2_PAGINATION: false,
};
function getApiVersion(endpoint: string): ApiVersion {
if (endpoint.startsWith('/workorders') && MIGRATION_FLAGS.USE_V2_WORKORDERS) return 'v2';
if (endpoint.startsWith('/assets') && MIGRATION_FLAGS.USE_V2_ASSETS) return 'v2';
return 'v1';
}
async function migratedRequest(method: string, endpoint: string, data?: any) {
const version = getApiVersion(endpoint);
const client = new (version);
client.(method, endpoint, data);
}
Step 4: Migration Tests
import { describe, it, expect } from 'vitest';
describe('API Version Migration', () => {
it('v1 and v2 return equivalent work order data', async () => {
const v1Client = new VersionedClient('v1');
const v2Client = new VersionedClient('v2');
const v1Result = await v1Client.request('GET', '/workorders?limit=5');
const v2Result = await v2Client.request('GET', '/workorders?limit=5');
expect(v1Result.workOrders.length).toBe(v2Result.workOrders.length);
expect(v1Result.workOrders[0]).toHaveProperty('id');
expect(v1Result.workOrders[0]).toHaveProperty('title');
expect(v1Result.workOrders[0]).toHaveProperty();
});
(, {
adapter = adapters.;
{ data } = adapter.(, {
: ,
: [{ : , : }],
});
(data.).();
(data.).();
});
});
Step 5: Rollback Procedure
#!/bin/bash
echo "=== MaintainX API Version Rollback ==="
echo "1. Set all feature flags to false:"
echo ' MIGRATION_FLAGS.USE_V2_WORKORDERS = false'
echo ' MIGRATION_FLAGS.USE_V2_ASSETS = false'
echo ""
echo "2. Redeploy with v1 configuration:"
echo " git revert HEAD --no-edit && git push"
echo ""
echo "3. Verify v1 endpoints are working:"
echo ' curl -s https://api.getmaintainx.com/v1/workorders?limit=1 \'
echo ' -H "Authorization: Bearer $MAINTAINX_API_KEY" | jq .status'
echo ""
echo "4. Monitor error rates for 30 minutes"
echo "5. Document issues for v2 retry"
Output
- API usage audit report listing all endpoints and call sites
- Version compatibility layer with request/response adapters
- Feature flag system for incremental per-endpoint migration
- Migration tests verifying v1/v2 equivalence
- Rollback procedure for safe revert
Error Handling
| Issue | Cause | Solution |
|---|
| 404 on v2 endpoint | Endpoint path changed | Update adapter mappings |
| Field missing in v2 response | Breaking schema change | Add field mapping in transformResponse |
| Mixed v1/v2 data in DB | Partial migration state | Run reconciliation to normalize |
| Feature flag stuck | Config not reloaded | Restart service or use dynamic config |
Resources
Next Steps
For CI/CD integration, see maintainx-ci-integration.
Examples
Dual-write during migration (write to both v1 and v2):
async function dualWrite(endpoint: string, data: any) {
const v1 = new VersionedClient('v1');
const v2 = new VersionedClient('v2');
const v1Result = await v1.request('POST', endpoint, data);
try {
await v2.request('POST', endpoint, data);
} catch (err) {
console.warn('v2 write failed (non-blocking):', err);
}
return v1Result;
}