@more-ink/irt-edge
v2.1.1
Published
IRT Edge API client SDK for JavaScript/TypeScript frontends.
Downloads
51
Readme
@more-ink/irt-edge
Two packages in one:
- SDK - Lightweight API client for JavaScript/TypeScript frontends (published to npm)
- Backend Server - Production HTTP API server built with Hono for Aliyun Function Compute (not published)
Table of Contents
SDK Usage (Frontend)
Installation
npm install @more-ink/irt-edge @more-ink/irt-core
# or
yarn add @more-ink/irt-edge @more-ink/irt-coreNote: @more-ink/irt-core is a peer dependency that provides type definitions.
Quick Start
import { IrtClient } from '@more-ink/irt-edge'
const client = new IrtClient({
baseUrl: 'https://your-api.example.com',
headers: {
'Authorization': 'Bearer YOUR_TOKEN' // Optional
},
timeout: 10000 // Optional (default: 10000ms)
})
// Record an answer and get the next item
const result = await client.recordAnswer({
userId: 'user123',
skillId: 'math',
itemId: 'item456',
score: 0.8,
timestamp: Date.now()
})
console.log('Updated ability:', result.theta)
console.log('Next item:', result.nextItem)Configuration
IrtClientConfig
interface IrtClientConfig {
baseUrl: string // API server base URL (required)
headers?: Record<string, string> // Custom headers (e.g., auth tokens)
timeout?: number // Request timeout in ms (default: 10000)
}API Methods
recordAnswer()
Record a user's response and get the next recommended item.
async recordAnswer(params: RecordAnswerRequest): Promise<RecordAnswerResponse>Parameters:
interface RecordAnswerRequest {
userId: string // User identifier
skillId: string // Skill identifier
itemId: string // Item identifier
score: number // Response score [0,1] (0=wrong, 1=correct)
timestamp?: number // When response occurred (ms)
updateOptions?: Partial<UpdateOptions> // Override learning rates
selectionOptions?: Partial<NextItemOptions> // Override selection behavior
}Returns: Updated ability (theta), standard error (se), next item, and updated states.
Example:
const result = await client.recordAnswer({
userId: 'alice',
skillId: 'geometry',
itemId: 'q100',
score: 1.0,
timestamp: Date.now()
})
console.log(`Ability: ${result.theta.toFixed(2)} ± ${result.se.toFixed(2)}`)
if (result.nextItem) {
console.log(`Next question: ${result.nextItem.id}`)
}recordMultiSkillAnswers()
Record multiple skill scores that came from a single multi-skill item. This is useful when one interaction yields separate sub-skill scores (e.g., integrated tasks that grade both reading and writing).
async recordMultiSkillAnswers(
params: RecordMultiSkillAnswersRequest,
): Promise<RecordMultiSkillAnswersResponse>Parameters:
interface RecordMultiSkillAnswersRequest {
userId: string
itemId: string
timestamp?: number
updateOptions?: Partial<UpdateOptions>
skillScores: Array<{
skillId: string
score: number // [0,1]
updateOptions?: Partial<UpdateOptions>
}>
}Returns: Array of per-skill updates with theta, SE, updated user/item snapshots.
Example:
const multi = await client.recordMultiSkillAnswers({
userId: 'alice',
itemId: 'essay-22',
skillScores: [
{ skillId: 'reading', score: 0.9 },
{ skillId: 'writing', score: 0.6 },
],
})
multi.results.forEach((entry) => {
console.log(entry.skillId, entry.theta, entry.se)
})recordAnswerOnly()
Record a user's response without requesting the next recommended item (useful when you already control sequencing).
async recordAnswerOnly(params: RecordAnswerRequest): Promise<RecordAnswerOnlyResponse>await client.recordAnswerOnly({
userId: 'alice',
skillId: 'geometry',
itemId: 'q100',
score: 1.0,
timestamp: Date.now()
})selectNextItem()
Get the next recommended item without recording a response.
async selectNextItem(params: SelectNextItemRequest): Promise<SelectNextItemResponse>getUserState() / getUserStates()
Get current user ability estimates.
async getUserState(userId: string, skillId: string): Promise<UserStateResponse>
async getUserStates(userId: string): Promise<UserStatesResponse>getItemState() / getItemStates()
Get item parameters and metadata.
async getItemState(itemId: string, skillId: string): Promise<ItemStateResponse>
async getItemStates(itemId: string): Promise<ItemStatesResponse>deleteUser() / deleteItem()
Delete a user (and their skill states) or an item (and all per-skill calibrations). These are mainly for integration tests and fixture cleanup.
async deleteUser(userId: string): Promise<DeleteUserResponse>
async deleteItem(itemId: string): Promise<DeleteItemResponse>health()
Check API server health status.
async health(): Promise<HealthResponse>See examples/sdk-usage.ts for complete working examples.
Error Handling
All methods throw errors for network failures, HTTP errors, timeouts, and invalid responses.
try {
const result = await client.recordAnswer({...})
// Handle success
} catch (error) {
if (error instanceof Error) {
console.error('API error:', error.message)
if (error.message.includes('timeout')) {
// Retry logic
}
}
}Best Practices
- Reuse client instances - Create one client and reuse it
- Use timestamps - Always provide client-side timestamps for accurate analytics
- Handle errors gracefully - Show user-friendly messages on errors
- Check for null items - The API may return
nullif no suitable item is available
const result = await client.selectNextItem({...})
if (result.nextItem) {
// Show item to user
} else {
// No more items available
}Backend Server Setup
The backend server is not published to npm. It runs as a service on Aliyun Function Compute.
Features
- RESTful IRT API backed by PostgreSQL via Prisma (Redis route kept only for diagnostics)
- Aliyun FC lifecycle hooks (
/initialize,/pre-stop) - Graceful shutdown and health checks
Development Setup
# From repo root
npm install
# Create .env file with:
# UPSTASH_REDIS_REST_URL=https://...
# UPSTASH_REDIS_REST_TOKEN=...
# Run dev server
npm run --workspace @more-ink/irt-edge dev
# Seed data
npm run --workspace @more-ink/irt-edge seedBackend Environment Variables
# Aliyun Function Compute
ALIYUN_ACCESS_KEY="YOUR_ALIYUN_ACCESS_KEY"
ALIYUN_SECRET_ACCESS_KEY="YOUR_ALIYUN_SECRET_KEY"
# Database (PostgreSQL via Prisma)
DATABASE_URL="postgres://user:pass@host:port/db?schema=public"
# If using PgBouncer / pooled connections, also set DIRECT_URL for Prisma CLI commands
# DIRECT_URL="postgres://user:pass@host:port/db?schema=public"
# Feishu (Lark) Integration
FEISHU_APP_ID="YOUR_FEISHU_APP_ID"
FEISHU_APP_SECRET="YOUR_FEISHU_APP_SECRET"
FEISHU_CHAT_ID="YOUR_FEISHU_CHAT_ID"
# Optional legacy Redis diagnostics route
REDIS_URL=""
REDIS_DB=2
# Debug mode for Function Compute
DEBUG_FC="true"
# Server port (optional, default: 9000)
PORT=9000See src/index.ts for default IRT engine parameters (learning rates, selection options).
API Endpoints
IRT Operations:
POST /api/irt/answer- Record response and get next itemPOST /api/irt/answer-multi- Record multiple skill responses for one itemPOST /api/irt/next-item- Get next item without recording responseDELETE /api/irt/users/:userId- Delete a user and all skill statesDELETE /api/irt/items/:itemId- Delete an item and its per-skill calibrationsGET /api/irt/health- Health check
Aliyun FC Lifecycle:
POST /initialize- Instance startup (verifies Prisma DB and Redis diag route if configured)GET /pre-stop- Instance shutdown (closes Prisma DB and Redis if used)
System:
GET /- Service info
Backend Deployment (Aliyun FC)
Deploy the backend server to Aliyun FC:
# From root (recommended)
npm run dp
# Or from package dir
npm run --workspace @more-ink/irt-edge deployHow Deployment Works
Deployment now runs directly from the package root with npm-installed node_modules (no flattening step needed):
- Run
npm run --workspace @more-ink/irt-edge deploy(runsprisma generate, thenbuild, thenfc-deployfrom the package root) - Upload from the package root;
node_modulesalready contains real files.
Ensure DATABASE_URL (and DIRECT_URL if pooling) is set in the function env.
SDK Publishing (npm)
Publish the SDK to npm (backend code stays private):
# From repo root (recommended)
npm run pub
# Or from package directory
cd packages/irt-edge
npm run publish:sdk
# Or using npm directly
npm publish --access publicWhat Gets Published
Published to npm:
- ✅
sdk/- Compiled SDK code (~40KB unpacked) - ✅
README.md,CHANGELOG.md- Documentation - ✅
examples/- Usage examples - ✅
package.json- Package metadata
NOT published (stays private):
- ❌ Backend server code (
src/index.ts,routes.ts,storage/) - ❌ Scripts and tests (
scripts/,tests/) - ❌ Config files (
.env,tsconfig.json, etc.) - ❌ Backend build output (
dist/)
The .npmignore file ensures only SDK files are published.
Publishing Checklist
Before publishing:
Version Management
- Update version in
package.json(follow semver) - Update
CHANGELOG.mdwith changes
- Update version in
Code Quality
- Build SDK:
npm run --workspace @more-ink/irt-edge build:sdk - Run tests:
npm run --workspace @more-ink/irt-edge test - Check TypeScript:
tsc -p tsconfig.sdk.json --noEmit
- Build SDK:
Verify Package
- Dry-run:
npm pack --dry-run - Check package size (<50KB)
- Verify only SDK files included
- Dry-run:
Publish
npm run pub(auto-builds before publishing)
Post-Publishing
- Verify on npm: https://www.npmjs.com/package/@more-ink/irt-edge
- Tag release:
git tag v1.0.0 && git push origin v1.0.0 - Create GitHub release
Test Installation
mkdir /tmp/test-irt-sdk && cd /tmp/test-irt-sdk
npm init -y
npm install @more-ink/irt-edge @more-ink/irt-core
node -e "const { IrtClient } = require('@more-ink/irt-edge'); console.log('OK')"Package Scripts
Development & Backend
npm run --workspace @more-ink/irt-edge dev- Run dev server with hot reloadnpm run --workspace @more-ink/irt-edge build- Build backend server todist/npm run --workspace @more-ink/irt-edge start- Start production backend servernpm run --workspace @more-ink/irt-edge seed- Seed Redis with test datanpm run --workspace @more-ink/irt-edge deploy- Deploy backend to Aliyun FC
SDK Publishing
npm run --workspace @more-ink/irt-edge build:sdk- Build SDK tosdk/npm run --workspace @more-ink/irt-edge publish:sdk- Build and publish SDK to npmnpm run pub- Shortcut fornpm run --workspace @more-ink/irt-edge publish:sdk(from root)
Testing
npm run --workspace @more-ink/irt-edge test- Run test suitenpm run --workspace @more-ink/irt-edge test:watch- Run tests in watch mode
Architecture
irt-edge/
├── src/
│ ├── sdk/ # ✅ SDK source (published)
│ │ ├── types.ts # Type definitions
│ │ ├── client.ts # IrtClient class
│ │ └── index.ts # Public exports
│ ├── index.ts # ❌ Backend server (not published)
│ ├── routes.ts # ❌ Backend routes
│ └── storage/ # ❌ Backend storage layer
├── sdk/ # ✅ Compiled SDK (published)
├── dist/ # ❌ Backend build (not published)
├── examples/ # ✅ SDK examples (published)
├── tsconfig.json # Backend build config
└── tsconfig.sdk.json # SDK build configLicense
Proprietary - All Rights Reserved
