sh-mcp/packages/qbo/src/dev-client.ts
Adam Moussa 2e16c306b0 Add in-memory dev clients; make package tool exports lazy
Add an in-memory Client implementation per integration package (seeded fake
data, no network) selected when SH_MCP_ENV=local (build-plan §4). Gmail/calendar/
tasks dev clients partition by ctx.sub; payments/qbo seed sensitive-looking
fields so the redaction egress path has real targets to mask.

Make the eager default-tool exports in tasks/reminders/qbo LAZY (getDefaultTools)
so importing a package barrel no longer constructs an AWS client at module load
(build-plan §7 'no I/O at import time') — the previous eager construction broke
server startup. Fix payments tsconfig rootDir (src, was '.') so its declarations
resolve under dist/index.d.ts like the other 8 packages.
2026-06-26 12:48:26 -04:00

63 lines
1.7 KiB
TypeScript

/**
* InMemoryQboClient — deterministic in-memory QboClientInterface for dev.
*
* Holds a few FAKE QBO vendor records in memory. Each carries a fabricated
* taxId so the redaction egress path has a sensitive target to mask before a
* response leaves the server. Filters by case-insensitive substring on
* displayName and respects maxResults. No OAuth, no network.
*
* This is for build-plan §4 "in-memory dev clients (tools return real data
* locally)".
*/
import type {
QboClientInterface,
QboVendor,
SearchVendorsParams,
SearchVendorsResult,
} from './client.js';
const SEED_VENDORS: QboVendor[] = [
{
id: '101',
displayName: 'Harbor Electric Co.',
email: 'ap@harborelectric.example',
phone: '(631) 555-0142',
active: true,
balance: 0,
taxId: '12-3456789',
},
{
id: '102',
displayName: 'Coastal Supply & Hardware',
email: 'billing@coastalsupply.example',
phone: '(631) 555-0188',
active: true,
balance: 1842.5,
taxId: '98-7654321',
},
{
id: '103',
displayName: 'Tidewater Plumbing LLC',
phone: '(631) 555-0123',
active: false,
balance: 0,
taxId: '45-6789012',
},
];
/** In-memory QboClientInterface seeded with fake vendors (taxId populated). */
export class InMemoryQboClient implements QboClientInterface {
async searchVendors(params: SearchVendorsParams): Promise<SearchVendorsResult> {
const needle = params.query.trim().toLowerCase();
const matched =
needle.length === 0
? SEED_VENDORS
: SEED_VENDORS.filter((v) => v.displayName.toLowerCase().includes(needle));
const limit = params.maxResults ?? 20;
return {
vendors: matched.slice(0, limit),
totalCount: matched.length,
};
}
}