Load & Scale: In-Memory, SQLite, Add-ons, Query Bundling
Choose the load and storage strategy by data size and by the query ability of the source. A small static file and a large queryable corpus do not deserve the same machinery — match the mechanism to the data.
Preload small static data into memory
Section titled “Preload small static data into memory”A fictional schema needs to turn postal codes into city names from a 5,000-row reference file with no query API. Fetch it once at init, parse it into a lookup, and serve every call from memory — not one network round-trip per request:
// once, at init:const rows = parseCsv( await fetchText( 'https://data.example/plz-cities.csv' ) )const cityByPlz = new Map( rows.map( ( r ) => [ r.plz, r.city ] ) )
// per call: instant, offlinegetCity: async ( { payload } ) => ( { response: { city: cityByPlz.get( payload.plz ) ?? null } } )Preload is a pattern — load-on-init plus an in-memory cache — not a schema field.
Escalate to SQLite for large static data
Section titled “Escalate to SQLite for large static data”If that reference set were 5 million rows with range and geo queries, holding it in memory is wasteful. Escalate to an embedded SQLite file, declare the driver, and guard the handler so it fails clearly when the driver is missing:
main = { requiredLibraries: [ 'better-sqlite3' ], /* … */ }
findNearby: async ( { libraries, payload } ) => { if( !libraries[ 'better-sqlite3' ] ) { throw new Error( 'STOPS-001: SQLite driver not injected' ) } // query the prepared DB by bounding box …}Large + static + queryable → SQLite. Smaller or one-off → keep it in memory.
Package reusable data as an add-on
Section titled “Package reusable data as an add-on”When one dataset backs many methods, expose it as a thin add-on that names the source and how to load it, and let init parse it once:
main = { source: 'acme-stops', mode: 'url', url: 'https://data.example/stops.geojson', addon: 'geo-geojson' }// init parses the file once → memory; every method serves from that single parsed copyOver-fetch beats under-fetch
Section titled “Over-fetch beats under-fetch”A fictional places API rate-limits hard. Asking for cafés, then bakeries, then pharmacies as three calls burns three slots; one combined query costs one:
BAD : query(type=cafe) ; query(type=bakery) ; query(type=pharmacy) → 3 rate-limit slotsGOOD: query(type in [cafe,bakery,pharmacy]) → 1 slotBe precise about what the limit governs: the number of clauses is not the limit — the size of the result set is. Where a source allows it, ship selections: pre-built query templates with the known gotchas already baked in, so callers don’t rediscover them.
Related
Section titled “Related”- ./13-geo-conventions.md — see chapter 13.