AbstractOptionaloptions: DestinationConstructorProtectedallowOptionalcacheOptional cache key prefix for all cached methods. When set, this prefix is prepended to all cache keys, preventing cache collisions when multiple instances of the same base class exist (e.g., multiple parks using the same framework).
Can be set directly as a string property, or implement getCacheKeyPrefix() method for dynamic prefixes.
Whether this destination supports a real-time live data stream. Parks with WebSocket or database sync feeds set this to true. The collector checks this to decide whether to run a stream loop.
ReadonlyhttpDefault language for localized strings Can be overridden by subclasses or via {PREFIX}_LANGUAGE env var with
ProtectedliveFraction of tracked entities that may retire in one snapshot before the gate assumes the feed is degraded rather than the entities retired.
A source can return a well-formed but gutted payload — an empty array, a CDN stub, a partial regeneration — which parses cleanly and so never throws. Retiring on that publishes a confident CLOSED for a park that is open. Mass disappearance is far more often a broken feed than a mass retirement, so past this share the gate declines to act and warns.
Measured against raw absence rather than against the entities that are eligible to close, because eligibility needs the age window to elapse and a guard that waited for it could not see a collapse until the collapse had already outlived it.
Known limit: this catches simultaneous collapses, not staggered ones. A
source shedding rows in waves, with a retirement window between them,
keeps every wave under the threshold and is genuinely indistinguishable
from progressive retirement without venue-level grouping. Every
simultaneity-based guard shares this, the collector's own bulk-close
guard included. Grouping the fraction per parkId is the refinement if
it ever bites in practice.
ProtectedliveFloor below which liveEntityRetirementMaxFraction does not apply. A destination tracking a handful of entities can legitimately retire most of them at once; the proportional guard is aimed at collapses measured in dozens.
ProtectedliveHow many consecutive successful snapshots an entity must be absent from before it can retire, on top of liveEntityRetirementMs.
Age alone is wall-clock, and nothing advances it while the source is down: a proxy block, a container stop or a deploy gap longer than the window all leave every timestamp stale, so the first poll to succeed afterwards would retire everything missing from that one sample on the strength of a single observation. Requiring the absence to repeat means a close always rests on several builds.
This also sets how long a collapse must persist before the degraded-feed
guard arms, which couples it to the collector's polling interval across
repo boundaries: liveEntityRetirementMinMisses × pollInterval has to
land comfortably inside liveEntityRetirementMs, or the guard
cannot arm before the age window opens and a mistimed recovery is closed
silently again. Universal is the tightest case at 3 × 45 minutes against
a four hour window, a 1.78x margin; the boundary sits at a 80 minute
poll. Raising the collector's closed-park interval past that, or dropping
a destination's window below 135 minutes, reopens the hole.
ProtectedliveHow long an entity may be missing from a full buildLiveData() snapshot before retireMissingLiveEntities presumes it retired and force-closes it. Conservative default: the confirmed real-world cases were stale 17 and 50+ days, so a week already improves on both by an order of magnitude while tolerating a normal multi-day show hiatus. Override per-destination if evidence supports a tighter or looser window.
ProtectedliveHow long a retired entity keeps having its CLOSED row re-appended before the gate forgets it entirely.
The repeat exists so a close that was built but never delivered is not lost for good. It cannot run forever, though: a permanently dead id would otherwise count as fresh evidence of a collapse on every future build, and enough of them accumulated across seasons would trip liveEntityRetirementMaxFraction for good and silently disable the gate. A few days covers any rejected batch or dead process by orders of magnitude, and bounds the pile.
ProtectedretireOpt-in: force-close a previously-live entity once it has been absent from a full buildLiveData() snapshot for longer than liveEntityRetirementMs.
The collector is upsert-only with no delete path, so simply omitting a retired entity from buildLiveData() output achieves nothing — the last value sits there indefinitely (see nigloland.ts RIDE_RETIREMENT_MS for the same trap on a wait-time signal). Confirmed independently on two park modules: a seasonal show run ends, the entity leaves the upstream feed entirely, and its live row freezes mid-run — 50+ days OPERATING on one, 17+ days on another (parksapi #74, #83).
Off by default. A destination whose buildLiveData() legitimately omits
entities for unrelated reasons (a genuinely partial feed, a bug) would
have them wrongly force-closed, so this only applies where the pattern
has actually been confirmed. Only ever applied to a full snapshot
(scope undefined) — a partial/streaming build's absentees carry no
meaning and are never gated.
Timezone for this destination. Subclasses should override this with the park's local timezone. Used by virtual queue helpers to format dates in the park's timezone.
Protected_Protected
Internal initialization hook for subclasses
Override this method to provide custom initialization logic.
Called once per instance by init().
Inject proxy settings into HTTP requests. Runs last (priority 999) so all auth/header injectors fire before the URL is rewritten to point at the proxy service.
Unwrap proxy responses (e.g., Scrapfly wraps responses in JSON). Runs first (priority -999) so downstream response handlers see the unwrapped response, not the proxy envelope.
Add a prefix to use when looking up config values from environment variables or config object. This allows multiple destinations to co-exist in the same environment without clashing on config keys.
Prefix to add to config lookups (e.g. 'UNIVERSAL' to check UNIVERSAL_
ProtectedbuildHelper to build boarding group queue data
Constructs a BOARDING_GROUP queue object with allocation information. Automatically formats dates in the destination's timezone.
Boarding group status (AVAILABLE, PAUSED, or CLOSED)
Optionaloptions: {Additional boarding group information
Boarding group queue object
State of boarding group availability
Current boarding group end number
Current boarding group start number
Estimated wait time in minutes
Next boarding group allocation time
ProtectedbuildBuild the list of entities for this destination
Subclasses should override this method to return their entities. The returned entities will automatically have their parkId and destinationId resolved based on the parent hierarchy.
List of entities (hierarchy will be resolved automatically)
ProtectedbuildBuild live data for all entities in this destination
Subclasses should override this method to return live data (wait times, operating status, showtimes, etc.) for their entities.
Optionalscope: ReadonlySet<string>
Optional set of published entity ids to limit the build to (see getLiveData). Streaming destinations may honour it to build only the changed entities; an override that ignores it returns the full snapshot.
List of live data for entities
ProtectedbuildBuild a real-time stream of live data updates
Override this method in parks that have a WebSocket or database sync feed. The default implementation is an empty generator (returns immediately, never yields).
The connection to the live data source should be opened in _init(), not here. This method subscribes to the already-open connection and yields updates as they arrive.
Async generator yielding LiveData[] per update
ProtectedbuildHelper to build paid return time queue data
Constructs a PAID_RETURN_TIME queue object with pricing information. Automatically formats dates in the destination's timezone.
Queue state (AVAILABLE, TEMP_FULL, or FINISHED)
Start time of return window (Date or ISO string)
End time of return window (Date or ISO string)
Currency code (e.g., 'USD', 'EUR')
Price in cents (e.g., 1500 for $15.00)
Paid return time queue object
Price information for paid return time
End time of return window
Start time of return window
State of return time availability
ProtectedbuildHelper to build return time queue data
Constructs a RETURN_TIME queue object with proper formatting. Automatically formats dates in the destination's timezone.
Queue state (AVAILABLE, TEMP_FULL, or FINISHED)
Start time of return window (Date or ISO string)
End time of return window (Date or ISO string)
Return time queue object
End time of return window
Start time of return window
State of return time availability
ProtectedbuildBuild schedules for all entities in this destination
Subclasses should override this method to return operating hours, show times, and other schedule information for their entities.
List of schedules for entities
ProtectedcalculateCalculate return window based on current wait time
Common pattern for parks like Efteling where virtual queue window is calculated as: now + waitTime to now + waitTime + windowDuration
Wait time in minutes to add to base time
Optionaloptions: { baseTime?: Date; windowMinutes?: number }
Optional configuration
Object with formatted start and end times
ProtectedfetchFetch the latest version of an app from the themeparks.wiki appwatch
mirror (Play Store metadata). Used by getAppwatchVersion(); subclasses
shouldn't call this directly.
Get the latest published version string of a mobile app, via appwatch.
Returns fallback when appwatch is unreachable or doesn't have the
version field. Cached 12h per (subclass, packageId) — invalidate the
cache entry from a response-error handler if the upstream API ever
starts gating on version.
OptionalgetGet all entities (parks, attractions, dining, shows, hotels) for this destination
⚠️ DO NOT OVERRIDE THIS METHOD ⚠️
This method automatically calls init() before fetching entities, and calls resolveEntityHierarchy() on the returned entities to set parkId and destinationId based on parent relationships.
To provide entities, implement buildEntityList() instead.
List of entities with resolved hierarchy
Get live data for all entities in this destination
⚠️ DO NOT OVERRIDE THIS METHOD ⚠️
This method automatically calls init() before fetching live data. If you need to provide post-processing or validation of live data, consider using the transform pattern in buildLiveData() instead.
To provide live data, implement buildLiveData() instead.
Optionalscope: ReadonlySet<string>
Optional set of published entity ids to limit the build to. Streaming (push) destinations pass the ids whose source docs changed this fire so the build is emit-per-changed-entity rather than a full snapshot. Undefined (the default, and all poll/REST destinations) builds everything. Passed through to buildLiveData(); subclasses that ignore it stay full-snapshot.
List of live data for entities
ProtectedgetGet localized string value with fallback logic
Handles both simple strings and multi-language objects. For multi-language objects, uses intelligent fallback: tries exact match, then base language (en-gb -> en), then fallback language, then first available.
LocalisedString (string or multi-language object)
Optionallanguage: "en" | "en-gb" | "en-us" | "de" | "fr" | "es" | "it" | "nl" | "ja" | "ko" | "zh"
Preferred language code (defaults to instance language config)
Fallback language if preferred unavailable (defaults to 'en')
Localized string value
// Simple string - returns as-is
this.getLocalizedString("Space Mountain") // => "Space Mountain"
// Multi-language object with exact match
this.getLocalizedString({ en: "Space Mountain", fr: "Space Mountain" }, "fr")
// => "Space Mountain"
// Multi-language with base language fallback
this.getLocalizedString({ en: "Space Mountain" }, "en-gb")
// => "Space Mountain" (falls back to 'en')
Get schedules for all entities in this destination
⚠️ DO NOT OVERRIDE THIS METHOD ⚠️
This method automatically calls init() before fetching schedules. If you need to provide post-processing or validation of schedules, consider using the transform pattern in buildSchedules() instead.
To provide schedules, implement buildSchedules() instead.
List of schedules for entities
The latest upstream snapshot time recorded for each park, one entry per park. Empty for a destination whose source carries no stamp.
A park the latest build did not read keeps its previous entry, with its
previous readAt, so a caller should judge recency from readAt rather
than assume every entry was read by the latest build.
ProtectedinitInitialize the destination
This method is called automatically before any data retrieval methods (getEntities, getLiveData, getSchedules). It runs only once per instance, even if called multiple times, thanks to @reusable({forever: true}).
Subclasses should override _init() instead of this method.
ProtectedmapMap array of source items to Entity objects
Helper method to reduce boilerplate when converting API responses to Entity objects. Provides declarative mapping configuration instead of manual object construction.
Note: This method does NOT set parkId automatically. Use resolveEntityHierarchy() after mapping all entities to correctly populate parkId and destinationId based on the parent chain.
const entities = this.mapEntities(apiRides, {
idField: 'Id',
nameField: 'MblDisplayName',
entityType: 'ATTRACTION',
parentIdField: 'VenueId',
locationFields: { lat: 'Latitude', lng: 'Longitude' },
destinationId: 'universalorlando',
timezone: 'America/New_York',
filter: (ride) => ride.IsActive === true,
});
ProtectedrecordRecord a park's upstream snapshot time, for sources that publish one. Call from buildLiveData() whenever the source's stamp was read, including when the snapshot is then withheld: that is when the age matters most.
A non-finite time or an empty park id is ignored, so a parse failure can never read as a timestamp.
The park's entity id.
When the source says its snapshot was generated.
When the snapshot was read. Defaults to now.
ProtectedresolveResolve entity hierarchy relationships (parkId and destinationId)
Walks the parent chain for each entity to correctly set parkId and destinationId based on ancestor types. This handles edge cases like:
Rules:
Validation:
Array of entities to resolve
Same array with parkId and destinationId correctly set
Stream live data updates in real time
Returns an async generator that yields LiveData[] arrays as updates arrive from the upstream feed. Each yield contains only the item(s) that changed — a single-element array for per-document sources (e.g. Couchbase Lite), or a full snapshot for sources that send all data at once (e.g. WebSocket).
For parks without a live feed (hasLiveStream = false), the generator returns immediately without yielding.
The generator ends when the upstream connection closes. The caller is responsible for reconnecting by calling streamLiveData() again in a loop.
Opt out of the collapsed-entity-list guard in getEntities.
Only for a destination that legitimately publishes no attractions, restaurants or shows — a listing that exists to carry opening hours, say. A destination that normally has content and simply lost it upstream must NOT set this: the whole point of the guard is that losing everything looks identical to having nothing.