npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tinyweb_dev/oe-exam-sdk

v1.0.5

Published

Reusable OceanEdu question components.

Readme

@tinyweb_dev/oe-exam-sdk

SDK tái sử dụng cho hệ thống OE Exam: màn tạo đề, quản lý câu hỏi, làm bài, xem kết quả và theme giao diện.

Cài đặt

npm install @tinyweb_dev/oe-exam-sdk

Cách dùng nhanh

'use client';

import {
  CreateExamPageContainer,
  ExamQuestionsPageContainer,
  ExamTakingPageContainer,
  ResultReviewPageContainer,
  StudentExamEntryPageContainer,
} from '@tinyweb_dev/oe-exam-sdk';
import {
  createExamCreateApi,
  createExamEntryApi,
  createExamQuestionsApi,
  createExamTakingApi,
  createResultReviewApi,
} from '@tinyweb_dev/oe-exam-sdk/api';

const baseUrl = process.env.NEXT_PUBLIC_API_URL;

export function CreateExamPage({ router }) {
  return (
    <CreateExamPageContainer
      api={createExamCreateApi({ baseUrl })}
      onNavigate={(href) => router.push(href)}
    />
  );
}

export function ExamQuestionsPage({ examId, router }) {
  return (
    <ExamQuestionsPageContainer
      examId={examId}
      api={createExamQuestionsApi({ baseUrl })}
      onNavigate={(href) => router.push(href)}
    />
  );
}

export function ExamEnterPage({ roomId, inviteToken, router }) {
  return (
    <StudentExamEntryPageContainer
      roomId={roomId}
      inviteToken={inviteToken}
      api={createExamEntryApi({ baseUrl })}
      onNavigate={(href) => router.push(href)}
    />
  );
}

export function ExamTakePage({ roomId, router }) {
  return (
    <ExamTakingPageContainer
      roomId={roomId}
      api={createExamTakingApi({ baseUrl })}
      onNavigate={(href) => router.replace(href)}
      resultPath={(attemptId) => `/student/results/${attemptId}/review`}
      onSubmitted={({ attemptId }) => router.replace(`/student/results/${attemptId}/review`)}
      proctoringEnabled
    />
  );
}

export function ResultReviewPage({ attemptId, token, router }) {
  return (
    <ResultReviewPageContainer
      attemptId={attemptId}
      token={token}
      api={createResultReviewApi({ baseUrl })}
      onNavigate={(href) => router.push(href)}
    />
  );
}

Entry points

@tinyweb_dev/oe-exam-sdk                       # export tổng hợp browser-safe (UI + API)
@tinyweb_dev/oe-exam-sdk/api                   # API adapters, constants, types
@tinyweb_dev/oe-exam-sdk/shared                # shared constants/types/helpers
@tinyweb_dev/oe-exam-sdk/mcp                   # MCP server ONLY (Node/stdio) — không re-export từ root
@tinyweb_dev/oe-exam-sdk/components            # shared UI components
@tinyweb_dev/oe-exam-sdk/components/exams      # màn tạo đề / câu hỏi / làm bài / vào phòng
@tinyweb_dev/oe-exam-sdk/components/exams/entry # màn vào phòng (entry) + state machine
@tinyweb_dev/oe-exam-sdk/components/exams/take # màn làm bài + renderer generic + hooks
@tinyweb_dev/oe-exam-sdk/components/questions  # hệ thống tạo câu hỏi
@tinyweb_dev/oe-exam-sdk/components/themes     # cambridge-yle, english-certification, summer-sky
@tinyweb_dev/oe-exam-sdk/components/results    # màn xem chi tiết kết quả

MCP: import từ subpath @tinyweb_dev/oe-exam-sdk/mcp (CLI/bin oe-exam-mcp). Root barrel không export MCP để tránh kéo node:fs vào client Next.js (UnhandledSchemeError).

API adapters

SDK không fix cứng API client. Host app có thể truyền adapter riêng qua prop api, hoặc dùng adapter mặc định:

import {
  createExamCreateApi,
  createExamQuestionsApi,
  createExamTakingApi,
  createResultReviewApi,
} from '@tinyweb_dev/oe-exam-sdk/api';

Các adapter mặc định nhận:

{
  baseUrl?: string;
  fetcher?: typeof fetch;
  headers?: HeadersInit;
}

createExamTakingApi đã bao gồm các API cần cho màn làm bài: start/resume attempt, questions, save answers, submit, upload audio, proctoring snapshot, realtime grading, grading summary/status/detail, room/attempt detail và keepalive save.

createExamEntryApi phục vụ màn vào phòng: room detail, active attempt theo room, start/delete attempt, join qua invite token và đăng ký contest (registration form).

Màn vào phòng (entry)

StudentExamEntryPageContainer port toàn bộ luồng /student/exams/:roomId/enter: join qua invite, gating quyền truy cập / đề thi / form đăng ký, state machine theo cửa sổ thời gian (BEFORE_START / WITHIN_WINDOW / EXPIRED / ALREADY_SUBMITTED), resume attempt đang dở và dialog "làm tiếp / làm lại từ đầu".

<StudentExamEntryPageContainer
  roomId={roomId}
  inviteToken={inviteToken}
  api={createExamEntryApi({ baseUrl })}
  onNavigate={(href) => router.push(href)}
  takePath={(id) => `/student/exams/${id}/take`}
  renderRegistrationForm={({ room, onSubmit, isSubmitting }) => (
    <MyRegistrationDialog room={room} onSubmit={onSubmit} isSubmitting={isSubmitting} />
  )}
/>

Themed waiting room (TOEIC / Summer Sky / Cambridge YLE) và registration form là phần phụ thuộc host app — truyền qua renderRegistrationForm hoặc bọc container.

Hooks màn làm bài

@tinyweb_dev/oe-exam-sdk/components/exams/take export các hook tái sử dụng:

useExamCountdown      # đếm ngược theo startedAt + duration, auto-submit khi hết giờ
useAnswerAutosave     # lưu debounce theo batch + flush keepalive khi ẩn tab/unload + retry khi online
useProctoringSnapshot # chụp webcam định kỳ và upload snapshot
useAttemptSubmit      # nộp bài (flush trước), chống double-submit, trả pending/auto-graded answer ids

ExamTakingPageContainer đã nối sẵn các hook trên. onSubmitted nhận { attemptId, pendingAnswerIds } để điều hướng sang màn review đúng ngữ cảnh; bật proctoring qua prop proctoringEnabled và truyền achieversExamTitle nếu cần gate video intro của giáo viên.

Theme làm bài

ExamTakingPageContainer tự chọn layout theo examTemplate.theme:

ENGLISH_CERTIFICATION -> EnglishCertificationExamLayout
CAMBRIDGE_YLE         -> CambridgeYleExamLayout
SUMMER_SKY            -> SummerSkyLayout
khác / rỗng           -> default SDK layout

Renderer câu hỏi trong màn làm bài hiện là GenericQuestionRenderer.

Màn xem kết quả

ResultReviewPageContainer sở hữu toàn bộ màn review và tự chọn giao diện theo template:

  • SPEAKING: dùng report nói chuyên biệt, chi tiết từng phần/câu, audio, AI assessment và speaking rubric.
  • Template còn lại: dùng OceanEduExamReport, phân tích tổng quan, rubric (nếu có) và ReviewQuestionRenderer đầy đủ cho câu hỏi dedicated lẫn Movers fallback.
  • Template PLACEMENT_TEST_KINDY_VER3PLACEMENT_TEST_PRESCHOOL dùng điểm rubric theo Part và ẩn chấm điểm từng câu.
'use client';

import {
  ResultReviewPageContainer,
  createResultReviewApi,
} from '@tinyweb_dev/oe-exam-sdk';

export function ResultReviewPage({ attemptId, token, isPrintMode, router }) {
  const reviewUrl = typeof window !== 'undefined' ? window.location.href : undefined;

  return (
    <ResultReviewPageContainer
      attemptId={attemptId}
      token={token}
      api={createResultReviewApi({ baseUrl })}
      isPrintMode={isPrintMode}
      canExportPdf
      reviewUrl={reviewUrl}
      assetBaseUrl="/speaking/ocean-edu"
      loginHref={`/auth/login?redirect=${encodeURIComponent(reviewUrl ?? '')}`}
      onBack={() => router.back()}
      onNavigate={(href) => router.push(href)}
      topActionsSlot={<SummaryVideoAction attemptId={attemptId} />}
      beforeReportSlot={<GiftClaimDialog attemptId={attemptId} />}
      afterReportSlot={<ResultExtraContent attemptId={attemptId} />}
    />
  );
}

Props và hành vi

| Prop | Hành vi | |---|---| | attemptId, token, api | Tải detail/attempt; token hỗ trợ link kết quả công khai. | | isPrintMode | Dừng polling và phát body[data-pdf-ready="true"] sau khi load để headless PDF renderer tiếp tục. | | canExportPdf | Hiện nút xuất PDF khi adapter có exportPdf; SDK tải blob bằng tên file từ API. | | reviewUrl | URL canonical đưa vào QR; SDK bổ sung verification token từ prop hoặc attempt. Mặc định dùng URL hiện tại trên browser. | | assetBaseUrl | Prefix tùy chọn cho logo-ocean.png, penguin-main.png, penguin-small.png, dofu2.png; bỏ prop để dùng asset bundle trong package. | | loginHref | Đích đăng nhập ở trạng thái 403/private; mặc định là /auth/login?redirect=<current-url>. | | onBack, onNavigate | Tích hợp router của host. Nếu có contest, back qua onNavigate về rooms của contest; nếu không có callback thì dùng browser history. |

Container poll mỗi 5 giây khi còn grading status đang xử lý hoặc LLM assessment chưa sẵn sàng. Các trạng thái loading, lỗi chung và 403 chưa công khai có UI riêng. showDetailedResults=false chỉ ẩn phần câu hỏi chi tiết, không ẩn report tổng quan.

Slots và phạm vi host

  • topActionsSlot: action bổ sung cạnh nút xuất PDF, ví dụ summary video.
  • beforeReportSlot: nội dung trước report, ví dụ gift claim hoặc thông báo nâng cấp.
  • afterReportSlot: nội dung sau report.

Gift claim, summary video/remotion, account upgrade, auth store và router không được nhúng trong SDK. Host truyền các phần này qua slot/callback; không cần nối lại page của oe-exam-fe để dùng container.

Các report shell, renderer, transform helper, polling/status helper và result-review types cũng được export từ @tinyweb_dev/oe-exam-sdk/components/results khi host cần tái sử dụng riêng lẻ.

MCP server

MCP đã tách sang package riêng:

npm install @tinyweb_dev/oe-exam-mcp-server

Chạy bằng PAT/API token. Nếu token có dạng oeexam_..., MCP tự gửi qua X-API-Key; token khác sẽ gửi qua Authorization: Bearer.

OE_API_URL=http://localhost:3000 OE_API_TOKEN=... npx @tinyweb_dev/oe-exam-mcp-server

Tools hiện có:

exam_templates_search      # tìm template qua filter name:like, lấy id để truyền examTemplateId
exam_template_get          # lấy templatePayload để truyền vào template
exam_question_template_get  # generate API-format questions từ template.parts
exam_json_template_generate # tạo JSON skeleton từ templateTitle/templateId để user edit trước
exam_create_from_json       # tạo exam từ JSON thân thiện: { name, templateTitle/templateId, parts: [{ questions: [...] }] }
exam_create_with_questions # tạo exam, patch template, tạo questions qua /manage/questions/batch
exam_result_get            # xem result review theo attemptId, hỗ trợ token public link

Luồng tạo exam nhanh qua MCP:

1. Gọi exam_json_template_generate với templateTitle hoặc templateId để tạo JSON skeleton theo template.parts và ghi ra file `<slug-title>.json`.
2. User/Roo sửa `parts[].questions[]` trong file JSON đó.
3. Gọi exam_create_from_json với nội dung JSON đã sửa. Tool tự search/get template, patch template, map questions theo template.parts rồi gọi /manage/questions/batch.

Prompt tạo file JSON skeleton trong Roo:

Gọi MCP tool exam_json_template_generate với input:
{
  "templateTitle": "Achievers 2026 Round 1",
  "name": "Sample exam",
  "examLevel": "A1",
  "difficulty": "MEDIUM",
  "durationMinutes": 45
}

Sau khi sửa file JSON, gọi create:

Đọc file JSON đã sửa rồi gọi MCP tool exam_create_from_json với nội dung file đó.

Ví dụ JSON thân thiện:

{
  "name": "Sample exam",
  "templateTitle": "Achievers 2026 Round 1",
  "totalScore": 3,
  "examLevel": "A1",
  "difficulty": "MEDIUM",
  "durationMinutes": 45,
  "parts": [
    {
      "name": "Part 1",
      "questions": [
        { "question": "Câu 1", "options": ["A", "B", "C", "D", "E"], "answer": "A" }
      ]
    }
  ]
}

Mock API

Dùng cho demo/storybook/test nhẹ:

import {
  createMockCreateExamApi,
  createMockExamQuestionsApi,
  createMockExamTakingApi,
  createMockResultReviewApi,
} from '@tinyweb_dev/oe-exam-sdk/api';

Ghi chú tích hợp

  • Không cần provider toàn cục.
  • Các tích hợp host-system nên đi qua props/API adapter.
  • Upload file trong component mặc định dùng blob: URL cục bộ; API upload thật nên truyền từ host app khi cần.

Dev local với host Next.js (không build tay mỗi lần sửa)

SDK không export thẳng src/ vì source dùng path alias @/ / @oe/exam (được tsc-alias rewrite lúc build). Host app luôn resolve exportsdist/.

Để sửa SDK và host nhận ngay:

1) Link package vào host

Yarn (oe-school/frontend — đã cấu hình):

// frontend/package.json
"@tinyweb_dev/oe-exam-sdk": "portal:../../oe-exam/sdk"
cd oe-school/frontend
yarn install

npm (host khác):

cd oe-exam/sdk && npm link
cd /path/to/host-nextjs && npm link @tinyweb_dev/oe-exam-sdk

2) Transpile package trong host Next.js

// next.config.ts
const nextConfig = {
  transpilePackages: ['@tinyweb_dev/oe-exam-sdk'],
};

(oe-school/frontend đã có sẵn trong transpilePackages.)

3) Chạy watch build SDK (bắt buộc — cập nhật dist/)

Terminal 1 — SDK:

cd oe-exam/sdk
npm run dev
# initial build:ts (không minify) + tsc --watch
# → tsc-alias + check-aliases sau mỗi lần emit thành công + copy assets

Terminal 2 — host:

cd oe-school/frontend   # hoặc host Next.js khác
yarn dev               # / npm run dev

Luồng:

SDK src/**  →  npm run dev (watch)  →  dist/**
       ↑ portal/npm link symlink
host node_modules/@tinyweb_dev/oe-exam-sdk
       →  Next transpilePackages + HMR

Không cần: npm pack / publish / reinstall version mới mỗi lần sửa.

Publish production vẫn dùng npm run build (có minify). npm run dev chỉ cho local.

Kiểm tra local

Trong repo này:

npm --prefix sdk run build
# hoặc watch khi dev host:
npm --prefix sdk run dev

Nếu chạy app dev:

npm run dev
# http://localhost:3000/dev/exam-sdk

Publish

CI release uses .github/workflows/release_sdk.yml on tags sdk-v* with npm Trusted Publishing (OIDC) (no long-lived NPM_TOKEN).

Configure Trusted Publisher on npmjs.com for @tinyweb_dev/oe-exam-sdk:

  • Organization or user: ocean-edu
  • Repository: oe-exam
  • Workflow filename: release_sdk.yml
  • Allowed actions: npm publish

Manual local publish (maintainer + 2FA):

cd sdk
npm version patch
npm publish --access public