Repository navigation
Cache
Every read goes through one built-in, per-instance cache. Screens get local data instantly, the network only refreshes it, and a failed refresh never discards data you already have. The cache is always on: there is no opt-in and no per-resource tuning.
A single exported constant drives everything: CACHE_TTL_MS = 60_000. An entry's age is now - fetchedAt.
| State | When | A read… |
|---|---|---|
| fresh | age < 60_000 |
is served with no request. |
| stale |
age >= 60_000, maintained entries only |
is served immediately while a background refresh runs. A failed refresh keeps the data and emits the cache error event. |
| dead |
age >= 60_000 for manual entries, fetchedAt in the future (clock moved backwards), or older than 30 days |
is never served while online. The read waits for the network; if that fails, the read fails. Offline it is served anyway: see 📴 Offline. |
Dead entries are deleted lazily (on read, when loading from storage, and after each getAll); there are no timers for it, and never while offline. The difference between stale and dead is the point of the design: maintained data is refreshed by the poller, so showing it instantly is safe; manual data has nobody refreshing it, so once it is older than the interval it must not be shown.
An entry is maintained when its request is one of the requests getAll performs (see below) and its resource is not in fetchIgnore. Everything else is manual: one homework day, one message, a QCM detail, a far-away timetable week, a message folder or another school year, getCloud at another depth. In particular:
- timetable: only the Monday-to-Sunday weeks previous, current and next relative to now. When the week rolls over, the oldest of them stops being maintained and is treated as manual on its next read;
-
getMessages()for the default school year,getCloud()at the default depth, and the list readsgetHomework(),getGrades(),getTimeline(),getCommonTimeline(),getAttendance(),getSettings(),getQcms(),getDocuments()are maintained; - a resource put in
fetchIgnoreis no longer polled, so it becomes manual (it dies after the interval). CallingstopPolling()does not change how data is served.
Keys are built from normalized arguments, so a default getCloud() and getCloud({ depth: 3 }) are the same key, and a default getMessages() and getMessages({ year: <default year> }) are the same key: a screen's call hits exactly the entry getAll stored.
What is cached: every verbe=get read that goes through the request layer (manual reads too — they simply die after the interval). Not cached: downloads, the token health check, login and 2FA, and getTimetableIcalUrl. The raw payload is cached, never the transformed one (transform() is not idempotent); results are shared and memoized, so treat them as read-only. Concurrent identical reads share one network request. { force: true } on any read skips the cache, fetches, and writes the result through.
Fetches every resource in parallel, always from the network, as a background request, and writes each result to the cache. It is built in — not an option or a flag.
const all = await ld.getAll({ ignore: ["cloud"] });
// { grades, timetable: { previous, current, next }, homework, messages,
// timeline, commonTimeline, attendance, settings, qcms, documents, cloud,
// errors, skipped }-
What it fetches, exactly:
timetableas three Monday-to-Sunday weeks (YYYY-MM-DD: previous, current, next);messagesas the message list for the default school year;homeworkas the list (index) only;cloudat the default depth; and the plain list reads forgrades,timeline,commonTimeline,attendance,settings,qcmsanddocuments. - What it never fetches: individual messages (opening one marks it as read on the server), individual homework days, QCM details, the iCal URL, and timetable weeks other than the three above.
-
Result: one failing resource never rejects
getAll— it appears inerrors. A resource whose module is unsupported goes intoskipped, noterrors. An unknown name inignorethrowsEdValidationError. -
getAllrejects in three cases only: there is no active session; the session is unrecoverable; or the session ended while it ran (EdAuthErrorwith codeSESSION_ENDED).
When ld.timetable.getTimetable() is called for a range that is exactly one Monday-to-Sunday week, the SDK readies the two neighbouring weeks afterwards: fresh neighbours are left alone, stale/dead/missing ones are fetched as fire-and-forget background requests. It never delays the requested week, never chains (a prefetched week does not prefetch its own neighbours), does not run for non-week ranges, offline, without a session, or after a session change, and failures are silent. Prefetched weeks outside the maintained three are manual entries: ready for the interval, then dead.
Constructing the instance arms polling (state running) with the given or default interval. Arming a timer is not I/O; ticks do nothing until a session exists. Each tick calls getAll({ ignore: fetchIgnore }); each entry of the returned errors map is reported through the sync status and emitted once as pollingError. After a successful interactive login and after every switchAccount(), a background getAll prefetch runs (it never delays the call and never fails it; the refresh-internal login and session restore never prefetch).
const ld = new Litedirecte({ fetchInterval: 30, fetchIgnore: ["cloud"] });
// Pause when the app goes to the background…
ld.stopPolling();
// …resume when it returns (restarts, re-times, catches up if stale).
ld.resumePolling();-
fetchIntervalis in seconds. Default60.0disables the timer. Otherwise a finite number>= 5; anything else throwsEdValidationErrorfrom the constructor,configure()andresumePolling(), leaving timer and settings untouched. -
fetchIgnorelists resources skipped by the login/switch prefetch and by every tick (same values and validation asgetAll'signore). An explicitgetAll({ ignore })orlogin(..., { ignore })argument wins over it. -
stopPolling()clears the timer (stopped); nothing restarts it exceptresumePolling().resumePolling(options?)is the single way to resume and to change polling settings: it stores the options, re-creates the one timer, clears an authentication pause, and runs one catch-up tick immediately when the last successfulgetAllis older than the interval (never a burst).ld.configure()stores the values and re-times only while running. There is never more than one timer; it isunref'd and cleared byld.destroy(). -
logout()andclearSession()do not stop the timer; ticks simply do nothing until a session exists again.
const off = ld.cache.watch("grades", ({ resource, key }) => console.log("changed:", key));
const offEvent = ld.cache.onEvent((e) => console.log(e.type)); // update | invalidate | clear | error
const last = ld.cache.getLastSync("grades"); // Date | null
ld.cache.invalidate("grades"); // maintained -> stale, manual -> dead
await ld.cache.clear();-
watchfires only when stored data actually changed (an identical payload does not fire). The cacheupdateevent carries{ resource, key }. Entries dying fires nothing. -
newGrade/newMessage/newTimelineEntrycome from the cache write: when a write replaces a previous living value for a maintained entry, items absent from the previous value are emitted. The first fetch only seeds the cache and emits nothing. - One sync status:
ld.sync.getState()returns{ state, lastSync, lastError }withstateone of'signed-out' | 'idle' | 'syncing' | 'offline' | 'error' | 'auth-required';ld.sync.onChange(cb)subscribes. It starts assigned-out, becomesidleafter login orloadSession, and returns tosigned-outafterlogout.syncingmeans agetAllis in flight.offlinemeans no request was possible: a tick found no network, or a read was served straight from the cache (see 📴 Offline). A failure is reported through the sync status and at most one older channel (pollingErrorfor polls, the cacheerrorevent for background revalidations). Apps should watchld.syncforauth-required(see Authentication).
const ld = new Litedirecte({ isOnline: () => navigator.onLine });Default: navigator.onLine where navigator exists and onLine is a boolean (guarded; never throws on Node, Bun, Deno, Hermes or edge runtimes), otherwise assume online. When offline, requests are not attempted.
-
A read is served the newest copy the cache holds, whatever its age — fresh, stale or dead. Nothing can refresh it while the network is down, so it is the answer.
getMessage(1234)an hour after you opened it still returns the message instead of throwing. -
It is never silent: the served read flips the sync status to
offline(once per transition, with anEdOfflineErroraslastError), sold.sync.onChangeandld.sync.getState()tell the app the data is not live. The read itself resolves, so watchld.syncto show an offline banner. -
Only a read with nothing cached rejects, immediately, with
EdOfflineError(extendsEdNetworkError, never retried).{ force: true }also rejects offline: forcing a refresh is an explicit request for the network, and there is nothing to fall back to. - Dead entries are not pruned while offline (nothing could replace them), so
getAll()— which always forces a network read and reports oneEdOfflineErrorper resource inerrors— never destroys the copy you have. - Back online, the next read of a dead entry drops it and refetches from EcoleDirecte.
Mutations still go through the offline queue as usual (see Configuration).
- Every session change (
logout,clearSession,switchAccount, a login as another account,destroy) increments the instance's session generation. A late response arriving under an older generation is abandoned: nothing is cached, no event or sync update is produced, and a waiter rejects withEdAuthErrorcodeSESSION_ENDED. A successful token refresh is not a session change. -
logout()andclearSession()purge memory and storage for every account of that instance. Everything is per instance: cache keys always include the account id explicitly (evengetDocuments, whose endpoint has none) and usestorageNamespace; data persists through the instance'sstorage, sopasskeyencryption applies. Two instances never share entries. A storage failure never breaks a read (reported throughonError). At most 300 entries are kept in memory (least recently used evicted). - Mutations keep the cache correct:
markAsDone/markAsUndonepatch the cached homework in place (no refetch);markAsRead/markAsUnreadpatch message lists, andmarkAsUnreaddrops the cached message detail so the nextgetMessagereaches the server;sendMessageandsendHomeworkCommentmark their resource stale (maintained) or dead (manual).
© 2026 typeof (Scolup) | Licensed under AGPL 3.0