| name | develop |
| description | Expert Azure Functions (Node.js) developer for MMO FES Function App. Use when: implementing features, fixing bugs, refactoring code, researching codebase, planning solutions. Covers timer/HTTP triggers, retry patterns, App Insights integration. |
| license | OGL-UK-3.0 |
| metadata | {"author":"mmo-fes","version":"1.0"} |
Function App — Developer Skill
Expert software engineer for the MMO FES Azure Function App. Reads the codebase, researches, plans, reasons, writes production-ready code following Azure Functions conventions.
When to Use
- Implementing or modifying timer/HTTP triggered functions
- Working on retry logic or error handling
- Integrating with Application Insights
- Refactoring or restructuring function code
- Any production code writing task
Workflow
Before Making Changes
- Search codebase for similar function patterns
- Check existing tests to understand expected behavior
- Review
function.json trigger configuration
- Understand the retry and backoff patterns in use
During Implementation
- Follow all mandatory rules from the auto-loaded instruction files (
azure-functions.instructions.md, typescript.instructions.md)
- Initialize App Insights BEFORE creating Axios instances — order matters
- Export functions via
module.exports = func (Node.js Azure Functions pattern)
After Implementation
- Run tests:
npm test
- Verify coverage thresholds: 90% branches, functions, lines, statements
- Check problems panel for any issues
- Invoke the
/unit-tests skill to write or update tests
Project Conventions
Function Handler Pattern
const func = async (context, myTimer, overrideConfig) => {
context.log(`[COMPONENT][ACTION][STARTED]`, timeNow());
config = { ...config, ...overrideConfig };
if (myTimer.IsPastDue)
context.log('[SCHEDULED-JOBS][COMPONENT][RUNNING-LATE]', timeNow());
if (config.instrumentationKey)
appInsights.init(config.instrumentationKey, context);
};
module.exports = func;
Retry with Exponential Backoff
async function makeApiCallWithRetry(url, apiName, key, data, maxRetries, retryDelay) {
let retryCount = 0;
while (retryCount < maxRetries) {
try {
const response = await axios.put(`${url}${apiName}`, data, {
headers: { 'X-API-KEY': key, 'accept': 'application/json' }
});
return response;
} catch (error) {
retryCount++;
if (retryCount >= maxRetries) throw new Error('Max retries exceeded');
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, retryCount) * retryDelay)
);
}
}
}
Linear Retry with Delay Calculation
const retry = (fn, retries, delayFn) =>
fn(retries).catch(e =>
(retries > 0)
? wait(delayFn(retries)).then(() => retry(fn, retries - 1, delayFn))
: Promise.reject(new Error('failed'))
);
const calcDelay = (delay, totalRetries) => (retriesRemaining) =>
(totalRetries - retriesRemaining) * delay;
App Insights Tracking
const trackEvent = (name, properties) => {
if (appInsightsClient) {
appInsightsClient.trackEvent({
name, properties,
tagOverrides: operationIdOverride
});
}
};
Bracketed Logging with Timestamps
const timeNow = () => new Date(Date.now()).toISOString();
context.log(`[SCHEDULED-JOBS][LANDING-AND-REPORTING][STARTED]`, timeNow());
Anti-Patterns
Mandatory rules in the instruction files also apply. The items below are additional anti-patterns specific to this skill:
- Initializing Axios before App Insights — App Insights must be initialized first
- Forgetting
IsPastDue check on timer triggers
- Using
exports.handler instead of module.exports = func
- Hardcoding retry delay without exponential backoff calculation