This document describes how to enable and use TypeScript type checking for JavaScript files without a full TypeScript rewrite.
The project uses TypeScript's checkJs mode to provide type safety for JavaScript code through JSDoc annotations. This approach:
- No language change: JavaScript files remain unchanged; type info lives in JSDoc comments
- Opt-in: Gradual adoption — add types where most valuable
- IDE support: Full IntelliSense and type error detection in VS Code and other IDEs
- CI integration: Type checking runs in the CI pipeline to catch errors early
- Zero runtime cost: Types are stripped during compilation (JSDoc only)
File: tsconfig.json
Key settings:
"checkJs": true— Enable JavaScript type checking"strict": true— Strict mode for maximum safety"noEmit": true— Don't transpile, only check types"skipLibCheck": true— Skip third-party type checking (faster)
Check types before committing:
npm run typecheckOutput:
src/services/DonationService.js(234,26): error TS2322: Type 'undefined' is not assignable to type 'string'.
src/utils/validators.js(45,8): error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.
Type checking runs automatically as part of CI (to be integrated into your CI workflow):
npm run typecheckFailures block PR merge if branch protection is enabled.
/**
* Create a donation record in the database.
*
* @param {Object} params - Donation parameters
* @param {string} params.donorPublicKey - Stellar public key of donor (G...)
* @param {string} params.recipientPublicKey - Stellar public key of recipient
* @param {number} params.amountXlm - Amount in XLM (e.g., 10.5)
* @param {string} [params.memo] - Optional transaction memo
* @param {string} [params.idempotencyKey] - Optional idempotency key
* @returns {Promise<{id: number, transactionHash: string}>} Created donation record
* @throws {ValidationError} When inputs are invalid
* @throws {BusinessLogicError} When donation exceeds limits
*/
async createDonation({ donorPublicKey, recipientPublicKey, amountXlm, memo, idempotencyKey }) {
// Implementation
}/**
* @typedef {Object} DonationRecord
* @property {number} id - Unique donation identifier
* @property {string} donorPublicKey - Donor's Stellar public key
* @property {string} recipientPublicKey - Recipient's Stellar public key
* @property {number} amountXlm - Amount in XLM
* @property {string} [memo] - Optional memo
* @property {'pending'|'completed'|'failed'} status - Donation status
* @property {string} transactionHash - Stellar transaction hash (64 hex chars)
* @property {Date} createdAt - Creation timestamp
* @property {Date} [verifiedAt] - Verification timestamp
*/
/**
* Retrieve a donation by ID.
*
* @param {number} donationId - Donation identifier
* @returns {Promise<DonationRecord|null>} Donation record or null if not found
*/
async getDonationById(donationId) {
// Implementation
}/**
* List all donations with pagination.
*
* @param {Object} options - Query options
* @param {number} [options.limit=50] - Max results
* @param {number} [options.offset=0] - Pagination offset
* @returns {Promise<{total: number, donations: DonationRecord[]}>} Paginated results
*/
async listDonations({ limit = 50, offset = 0 }) {
// Implementation
}/**
* Verify a donation on the blockchain.
*
* @param {string} transactionHash - Stellar transaction hash
* @param {Object} [options] - Verification options
* @param {number} [options.maxRetries=5] - Max retry attempts
* @param {boolean} [options.throwOnNotFound=true] - Throw if not found vs return null
* @returns {Promise<{status: 'success'|'failed'|'pending', confirmations: number}>}
*/
async verifyDonation(transactionHash, options = {}) {
// Implementation
}/**
* Update donation memo (if allowed).
*
* @param {number} donationId - Donation identifier
* @param {string|null} newMemo - New memo (null to clear)
* @returns {Promise<void>}
* @throws {ValidationError} If memo is invalid
* @throws {NotFoundError} If donation not found
*/
async updateDonationMemo(donationId, newMemo) {
// Implementation
}/**
* Get or create a Stellar wallet service.
*
* @param {string} publicKey - Stellar public key
* @returns {WalletService} Wallet service instance
*/
getWalletService(publicKey) {
// Implementation
}/**
* @typedef {Object} CreateDonationParams
* @property {string} donorPublicKey - Donor's Stellar public key
* @property {string} recipientPublicKey - Recipient's Stellar public key
* @property {number} amountXlm - Amount in XLM (validated by rules)
* @property {string} [memo] - Optional transaction memo
* @property {string} [idempotencyKey] - Optional idempotency key
* @property {string} [asset] - Optional asset specification (default: XLM)
*/
/**
* @typedef {Object} DonationRecord
* @property {number} id - Database record ID
* @property {string} donorPublicKey - Donor Stellar address
* @property {string} recipientPublicKey - Recipient Stellar address
* @property {number} amountXlm - Amount in XLM
* @property {string} [memo] - Optional memo
* @property {string} transactionHash - Stellar transaction hash
* @property {'pending'|'completed'|'failed'|'cancelled'} status - Current status
* @property {Date} createdAt - Creation timestamp
* @property {Date|null} verifiedAt - Verification timestamp
*/Key Public Methods to Document:
createDonation(params)— Create and submit a donationverifyDonation(transactionHash)— Verify a donation on blockchaingetDonationById(id)— Retrieve a single donationlistDonations(options)— List donations with paginationupdateDonationStatus(id, status)— Update donation status
/**
* @typedef {Object} WalletMetadata
* @property {string} publicKey - Stellar public key (immutable)
* @property {string} [label] — Friendly name
* @property {string} [description] - Notes about the wallet
* @property {boolean} [isCustodial] - Whether API holds private key
* @property {string[]} [tags] - Labels for organization
* @property {Date} createdAt - Creation timestamp
* @property {Date} updatedAt - Last update timestamp
*/
/**
* @typedef {Object} WalletBalance
* @property {string} publicKey - Wallet's Stellar public key
* @property {number} balanceXlm - XLM balance
* @property {string[]} trustlines - List of trusted asset codes
* @property {Date} queriedAt - When balance was fetched
* @property {boolean} isFunded - Whether account has minimum reserve
*/Key Public Methods to Document:
createWallet(publicKey, metadata)— Register a walletgetWallet(publicKey)— Retrieve wallet metadataupdateWallet(publicKey, updates)— Update wallet metadatagetBalance(publicKey)— Query current balance on StellargetTransactionHistory(publicKey, options)— List transactions for a wallet
/**
* @typedef {Object} DailyStat
* @property {Date} date - Date in UTC
* @property {number} donationCount - Number of donations that day
* @property {number} totalAmount - Total XLM donated
* @property {number} uniqueDonors - Count of unique donors
* @property {number} uniqueRecipients - Count of unique recipients
* @property {number} averageAmount - Mean donation amount
* @property {number} medianAmount - Median donation amount
*/
/**
* @typedef {Object} StatsAggregate
* @property {Date} from - Start of period
* @property {Date} to - End of period
* @property {number} totalDonations - Sum of all donations
* @property {number} totalAmount - Total XLM
* @property {number} donorCount - Unique donors in period
* @property {number} recipientCount - Unique recipients in period
* @property {number} averageDonation - Mean donation amount
* @property {DailyStat[]} daily - Daily breakdown
*/Key Public Methods to Document:
getDailyStats(date)— Get stats for a single daygetWeeklyStats(startDate)— Get stats for a weekgetMonthlyStats(year, month)— Get stats for a monthgetSummary(options)— Get overall summary statisticsgetDonorStats(publicKey)— Get statistics for a specific donor
Mark optional values explicitly:
/**
* @param {string|null} memoField - Memo field (null if not provided)
* @returns {string|null} - Normalized memo or null
*/
function normalizeMemo(memoField) {
if (memoField === null) return null;
// ...
}Specify exact types for parameters to catch misuse:
/**
* WRONG: Too permissive
* @param {*} amount - Amount to donate
*
* CORRECT: Explicit type
* @param {number} amount - Amount in XLM
*/Always specify what the Promise resolves to:
/**
* WRONG: Missing return type
* @returns {Promise} Result of operation
*
* CORRECT: Specify resolved value
* @returns {Promise<{success: boolean, id: number}>}
*/Document all errors a function can throw:
/**
* Update a donation memo.
*
* @param {number} donationId - Donation ID
* @param {string} newMemo - New memo
* @throws {NotFoundError} Donation not found
* @throws {ValidationError} Memo format invalid
* @throws {BusinessLogicError} Memo can't be changed (already verified)
*/
async updateDonationMemo(donationId, newMemo) {
// Implementation
}Type checking works automatically with VS Code's built-in TypeScript support:
- Open a
.jsfile - Hover over variables to see inferred types
Cmd+K Cmd+I(Mac) orCtrl+K Ctrl+I(Windows) to view type informationCmd+Shift+M(Mac) orCtrl+Shift+M(Windows) to show all diagnostics in file
Settings (.vscode/settings.json):
{
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
}
}- Automatically recognizes JSDoc type annotations
- Settings → Languages & Frameworks → JavaScript → Code Assistance → Enable type inference
Start with highest-impact services:
- DonationService (core business logic)
- WalletService (frequent user interactions)
- StatsService (data correctness critical)
- StellarService (external API integration)
Add types to utility libraries:
- Validators (catch validation errors early)
- Database queries (type-safe queries)
- Formatters (ensure output shapes)
Add types to HTTP request handlers:
- Check request parameter types
- Verify response shapes
- Catch missing fields in JSON
Add to your CI pipeline (e.g., .github/workflows/ci.yml):
- name: Type checking
run: npm run typecheck
- name: Report type errors
if: failure()
run: npm run typecheck 2>&1 | tee typecheck-results.txtProblem: Function expects string but might receive undefined
// WRONG
/**
* @param {string} email - User email
*/
function sendEmail(email) {
// email could be undefined
}
// CORRECT
/**
* @param {string|undefined} email - User email (optional)
*/
function sendEmail(email) {
// Type checker knows email could be undefined
}Problem: Object shape not defined
// WRONG
/**
* @param {Object} donation - Donation object
*/
function processDonation(donation) {
console.log(donation.id); // Error: 'id' is unknown
}
// CORRECT
/**
* @typedef {Object} Donation
* @property {number} id - Donation ID
*
* @param {Donation} donation - Donation object
*/
function processDonation(donation) {
console.log(donation.id); // OK: 'id' is known
}Problem: Function called with wrong parameter types
// WRONG
/**
* @param {number} amount - Amount in XLM
*/
function validateAmount(amount) {
// amount is a number
}
validateAmount("10.5"); // Error: passing string, expecting number
// CORRECT
validateAmount(10.5); // OK: passing number- Contributing Guide — Add types when submitting PRs
- Development Guide — Troubleshooting type errors