@levelmoment/sdk-core
v0.1.2
Published
Platform-agnostic core of the LevelMoment SDK. Consumed by @levelmoment/sdk-web and @levelmoment/sdk-react-native.
Maintainers
Readme
@levelmoment/sdk-core
Status: ✅ Complete — full 8-type question support, session-based ad breaks, mastery tracking
Platform-agnostic SDK logic shared across all LevelMoment platform SDKs (web, React Native, Flutter). Contains types, auth helpers, the impression queue, and the core LevelMomentAd class.
Platform SDKs (sdk/web, sdk/react-native, sdk/flutter) wrap this package with their own storage implementations and platform-specific APIs.
What's Done
src/types.ts— all shared domain types:- 8 safe question meta types:
MultipleChoiceMeta,MultipleChoiceImageMeta,ImagePromptMeta,TextInputMeta,OrderingMeta,FillInBlankMeta,MatchingMeta,CategorizingMeta. All carry an optionalvisual?: { src: string; alt: string }(#122) — a base64data:image/svg+xmlURI for a parametric visual model; carries no answer data, so it passesstripAnswerDatauntouched QuestionMetadiscriminated union +Question,SessionQuestionBreakFormat("flashcard" | "quiz" | "deep_dive"),BreakSession,SessionSummary,SessionAnswerMasteryStatus,ProgressUpdate,ConceptLesson,QuestionInstructionLevelMomentConfig,AdLoadCallback,AdShowCallback,LevelMomentAdHandle,RewardItem,LevelMomentAdErrorImpressionEvent,AnswerPayload
- 8 safe question meta types:
src/auth.ts—TokenStoreinterface,MemoryTokenStoreimplementation,buildAuthHeader()utilitysrc/queue.ts—ImpressionQueueclass withQueueStorageinterface,MemoryQueueStoragefallback, background flush loop with retrysrc/ad.ts—LevelMomentAdclass implementing the full load/show/session patternLevelMomentAd.load(config, queue, callbacks, options?, fetchFn?)— static factory;options.formatselects flashcard (default), quiz, or deep_divead.show(callbacks)— displays from cache (no network wait)ad.isThrottled()— returns true when throttle limit reached (impression still recorded)ad.advanceSession(answer)— submits current answer; returns anAdvanceResult{ outcome, reveal?, progressUpdate?, next }(graded outcome + correct-answer teach-back;nextisnullwhen the session is complete)ad.submitFlashcardAnswer(answer)— flashcard grading path; sameAdvanceResultshape (nextalwaysnull) so the renderer can celebrate/reveal before firing reward + dismissad.noteQuestionShown()— records an impression for the CURRENT question, idempotent per question.show()covers question 1; the renderer calls it again for each subsequent session question so every question earns its own impression rowad.getLesson()— returnsConceptLessonfor deep_dive sessionsad.getInstruction(questionId)— returns per-questionQuestionInstructionif availablead.getSessionSummary()— returnsSessionSummarywith correct count, pass/fail, progress updatesad.getSessionInfo()—{ currentIndex, totalQuestions, passThreshold }for progress UI (nullfor flashcard)ad.notifyCorrectAnswer(callbacks, answeredAt)— legacy flashcard path; firesonUserEarnedReward(amount: 1)ad.notifyDismissed(callbacks, answeredAt, selectedIndex)— legacy flashcard path; firesonAdDismissedad.dispose()— releases resources
What's Not Done
- [ ]
LevelMomentAdunit tests —ImpressionQueueandMemoryQueueStorageare covered; add tests forLevelMomentAd.load/show/advanceSession - [ ] Impression queue has no max-size cap — add pruning to prevent unbounded growth on persistent network failure
- [ ]
_submitAnsweris best-effort (errors swallowed) — consider a retry queue for answers too
Setup
# From repo root
npm install
# Run unit tests (Vitest)
cd sdk/core && npx vitest run
# Type-check this package only
npx tsc --noEmit
# Build (ESM + CJS dual bundle via tsup)
npm run buildKey Files
| File | Purpose |
| ------------------- | ----------------------------------------------------- |
| src/types.ts | All shared interfaces and types |
| src/auth.ts | Token management interfaces + buildAuthHeader() |
| src/queue.ts | Impression queue with pluggable storage |
| src/ad.ts | LevelMomentAd — the core ad unit (load/show/notify) |
| src/index.ts | Public API exports |
| src/queue.test.ts | Vitest unit tests for ImpressionQueue + storage |
Design Decisions
fetchFnis injected intoLevelMomentAd.load()so tests can provide a mock without patching globals.QueueStorageandTokenStoreare interfaces, not implementations. Platform SDKs providelocalStorage(web),AsyncStorage(RN), orSharedPreferences(Flutter) backends.- Impression queue uses a background interval (default 10s). It also flushes immediately on
tryFlush(), which platform SDKs call on app background/close.
