Skip to content

Repository files navigation

themeparks

A typed, modern TypeScript/JavaScript SDK for the ThemeParks.wiki API. Built on the platform fetch with zero runtime dependencies, first-class TypeScript types, default-on caching, and ergonomic helpers for the common workflows (list destinations, walk a park's children, fetch live wait times, pull a date-ranged schedule).

📚 Full documentation, API reference, and cookbook

Install

npm i themeparks

Runs on Node 18+, evergreen browsers, Deno, Bun, and Cloudflare Workers. Zero runtime dependencies.

Print live wait times

Every example in this README works as plain JavaScript — save as .mjs and run with node. To use them as TypeScript, rename to .ts and run with npx tsx; the SDK ships full .d.ts types so TypeScript infers everything.

import{ThemeParks,currentWaitTime}from'themeparks';constMAGIC_KINGDOM='75ea578a-adc8-4116-a54d-dccb60765ef9';consttp=newThemeParks();constlive=awaittp.entity(MAGIC_KINGDOM).live();for(constentryof(live.liveData??[]).sort((a,b)=>a.name.localeCompare(b.name))){constwait=currentWaitTime(entry);console.log(`${entry.name.padEnd(50)}${wait===null ? '--' : `${wait} min`}`);}

Sample output:

Astro Orbiter 15 min
Big Thunder Mountain Railroad 45 min
Buzz Lightyear's Space Ranger Spin 20 min
Haunted Mansion 35 min
Jungle Cruise 40 min
...

currentWaitTime returns null for entities with no STANDBY queue right now (closed rides, shows, restaurants).

Client options

new ThemeParks(options) takes the following keyword options:

OptionTypeDefaultPurpose
baseUrlstringhttps://api.themeparks.wiki/v1API base URL (point at a mock / staging if you need to).
userAgentstringthemeparks-sdk-js/<version>Sent as the User-Agent header. Set this to identify your app.
fetchtypeof fetchglobalThis.fetchCustom fetch implementation. Useful for logging, mocking, or older runtimes.
timeoutMsnumber10000Per-request timeout in milliseconds.
retryPartial<RetryConfig>{ max: 3, on429: true }Retry/backoff behavior. max counts retries beyond the initial attempt (so 3 = up to 4 total).
cacheCache | false | { maxEntries? }in-memory LRUSee Caching below. false disables caching entirely.

Example:

consttp=newThemeParks({userAgent: 'my-app/1.2.3 (+https://example.com)',timeoutMs: 15_000,retry: {max: 5,on429: true},});

Reading every queue variant

currentWaitTime covers the standby-queue case. There are six queue variants in total, and an attraction may have more than one populated at once (e.g. STANDBY + SINGLE_RIDER + PAID_RETURN_TIME for a Lightning Lane ride).

Each variant is exposed as a key on entry.queue. All are optional — undefined if that queue type isn't offered for the attraction:

KeyFields
queue.STANDBYwaitTime: number | null
queue.SINGLE_RIDERwaitTime: number | null
queue.PAID_STANDBYwaitTime: number | null
queue.RETURN_TIMEstate, returnStart, returnEnd
queue.PAID_RETURN_TIMEstate, returnStart, returnEnd, price
queue.BOARDING_GROUPallocationStatus, currentGroupStart, currentGroupEnd, nextAllocationTime, estimatedWait

Direct access

import{ThemeParks}from'themeparks';consttp=newThemeParks();constlive=awaittp.entity('75ea578a-adc8-4116-a54d-dccb60765ef9').live();for(constentryoflive.liveData??[]){if(!entry.queue)continue;if(entry.queue.STANDBY?.waitTime!=null){console.log(`${entry.name}: standby ${entry.queue.STANDBY.waitTime} min`);}if(entry.queue.PAID_RETURN_TIME){constprt=entry.queue.PAID_RETURN_TIME;constprice=prt.price?.formatted??'?';console.log(`${entry.name}: Lightning Lane ${price}, return ${prt.returnStart}${prt.returnEnd}`,);}if(entry.queue.BOARDING_GROUP){constbg=entry.queue.BOARDING_GROUP;console.log(`${entry.name}: boarding group ${bg.currentGroupStart}${bg.currentGroupEnd}, `+`~${bg.estimatedWait} min, status ${bg.allocationStatus}`,);}}

Generic iteration

If branching on every variant is too verbose, narrowQueues(queue) flattens all populated variants into a typed discriminated union:

import{ThemeParks,narrowQueues}from'themeparks';consttp=newThemeParks();constlive=awaittp.entity('75ea578a-adc8-4116-a54d-dccb60765ef9').live();for(constentryoflive.liveData??[]){if(!entry.queue)continue;for(constqofnarrowQueues(entry.queue)){// q.type is 'STANDBY' | 'SINGLE_RIDER' | 'RETURN_TIME' | 'PAID_RETURN_TIME'// | 'BOARDING_GROUP' | 'PAID_STANDBY'// Narrowing on q.type gives you the exact fields for that variant.console.log(entry.name,q.type,q);}}

Ergonomic helpers

import{ThemeParks}from'themeparks';consttp=newThemeParks();// Directory lookup (loose, case-insensitive match on slug or name)constwdw=awaittp.destinations.find('waltdisneyworld');console.log(wdw?.id,wdw?.name);// Walk a destination and yield every descendant in ONE API callforawait(constchildoftp.entity('e957da41-3552-4cf6-b636-5babc5cbc4e5').walk()){console.log(child.entityType,child.name);}// Schedule across a date range (stitches monthly responses and filters)constmk='75ea578a-adc8-4116-a54d-dccb60765ef9';constentries=awaittp.entity(mk).schedule.range(newDate('2026-05-01'),newDate('2026-05-31'));console.log(`${entries.length} schedule entries`);

Low-level escape hatch

Every ergonomic helper is built on top of tp.raw, which is a thin, typed 1:1 wrapper over the OpenAPI operations. Use it directly when you want the raw response shape:

constlive=awaittp.raw.getEntityLive('75ea578a-adc8-4116-a54d-dccb60765ef9');constdests=awaittp.raw.getDestinations();constchildren=awaittp.raw.getEntityChildren('e957da41-3552-4cf6-b636-5babc5cbc4e5');

Error handling

All SDK errors inherit from ThemeParksError. The ones you'll typically catch:

import{ThemeParks,ApiError,RateLimitError,NetworkError,TimeoutError}from'themeparks';consttp=newThemeParks();try{awaittp.entity('75ea578a-adc8-4116-a54d-dccb60765ef9').live();}catch(err){if(errinstanceofRateLimitError){// 429: err.retryAfterMs is set if the server sent a Retry-After headerconsole.log(`rate limited, retry after ${err.retryAfterMs}ms`);}elseif(errinstanceofApiError){// any non-2xx statusconsole.log(`api error ${err.status} at ${err.url}:`,err.body);}elseif(errinstanceofNetworkError||errinstanceofTimeoutError){console.log('transport:',err);}else{throwerr;}}

RateLimitError is a subclass of ApiError, so order the branches carefully if you want to handle 429 specially.

Debugging — see every HTTP request

There's no built-in HTTP logger to flip on (we run directly on platform fetch). The clean idiom is to pass a wrapping fetch implementation:

import{ThemeParks}from'themeparks';consttp=newThemeParks({fetch: async(url,init)=>{console.log('→',init?.method??'GET',url);constres=awaitfetch(url,init);console.log('←',res.status,url);returnres;},cache: false,// so every call goes through the wrapper});

Note: cached responses bypass the fetch wrapper — they're returned before the transport is touched. Pass cache: false while debugging so every call is a real network round-trip.

Caching

The default client caches GET responses in-memory (LRU) with sensible per-endpoint TTLs:

EndpointTTLRationale
GET /destinations1 hourDirectory rarely changes.
GET /entity/{id}1 hourEntity metadata is static.
GET /entity/{id}/children1 hourPark topology is stable.
GET /entity/{id}/schedule[/yyyy/mm]5 minutesSchedules update but not rapidly.
GET /entity/{id}/live0 (bypass)Live data is always fetched.

Disable caching

consttp=newThemeParks({cache: false});

Plug in your own adapter

Cache is a structural interface — any object implementing get, set, and delete works. Redis, filesystem, IndexedDB, etc. (TypeScript shown; drop the annotations for plain JS):

import{ThemeParks,typeCache}from'themeparks';classMapCacheimplementsCache{privatereadonlydata=newMap<string,unknown>();get(key: string){returnthis.data.get(key);}set(key: string,value: unknown,_ttlMs: number){this.data.set(key,value);}delete(key: string){this.data.delete(key);}}consttp=newThemeParks({cache: newMapCache()});

The per-endpoint TTL table is applied by the transport layer, so your adapter receives the correct ttlMs for each call and can honor it however it likes (Redis EXPIRE, filesystem mtime, etc.).

What's new in v7

v7 is a full TypeScript rewrite. It replaces the v6 OpenAPI-Generator surface with a hand-crafted client built on platform fetch, ships real TypeScript types, adds ergonomic helpers (entity().walk(), schedule.range(), currentWaitTime, narrowQueues), default-on caching, and 429 Retry-After handling. See MIGRATION.md for a side-by-side v6 → v7 guide.

Supported runtimes

  • Node 18, 20, 22
  • Evergreen browsers
  • Deno
  • Bun
  • Cloudflare Workers

Links

License

MIT.

About

JavaScript library for ThemeParks.Wiki API

Resources

Stars

22 stars

Watchers

2 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages