@more-ink/irt-edge 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +64 -0
- package/README.md +350 -33
- package/examples/sdk-usage.ts +111 -0
- package/package.json +24 -11
- package/sdk/client.d.ts +117 -0
- package/sdk/client.d.ts.map +1 -0
- package/sdk/client.js +186 -0
- package/sdk/client.js.map +1 -0
- package/sdk/index.d.ts +46 -0
- package/sdk/index.d.ts.map +1 -0
- package/sdk/index.js +49 -0
- package/sdk/index.js.map +1 -0
- package/sdk/types.d.ts +89 -0
- package/sdk/types.d.ts.map +1 -0
- package/sdk/types.js +6 -0
- package/sdk/types.js.map +1 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -116
- package/dist/index.js.map +0 -1
- package/dist/routes.d.ts +0 -24
- package/dist/routes.js +0 -212
- package/dist/routes.js.map +0 -1
- package/dist/seed.d.ts +0 -8
- package/dist/seed.js +0 -50
- package/dist/seed.js.map +0 -1
- package/dist/storage/redis.d.ts +0 -24
- package/dist/storage/redis.js +0 -171
- package/dist/storage/redis.js.map +0 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# @more-ink/irt-edge
|
|
2
|
+
|
|
3
|
+
## 1.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Add generic type parameter support for type-safe skill identifiers
|
|
8
|
+
|
|
9
|
+
The SDK now supports generic type parameters (`TSkillId`) for compile-time type safety, following the same pattern as @more-ink/irt-core. This enables:
|
|
10
|
+
|
|
11
|
+
- Type-safe skill identifiers with TypeScript union types or enums
|
|
12
|
+
- IDE autocomplete for skill names
|
|
13
|
+
- Compile-time validation preventing invalid skill identifiers
|
|
14
|
+
- 100% backward compatible (defaults to `string`)
|
|
15
|
+
|
|
16
|
+
Example usage:
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
type MySkills = "grammar" | "vocabulary" | "listening";
|
|
20
|
+
const client = new IrtClient<MySkills>({ baseUrl: "..." });
|
|
21
|
+
|
|
22
|
+
// TypeScript ensures skillId is valid at compile time
|
|
23
|
+
await client.recordAnswer({
|
|
24
|
+
userId: "user123",
|
|
25
|
+
skillId: "grammar", // ✅ Type-checked
|
|
26
|
+
itemId: "item456",
|
|
27
|
+
score: 0.8,
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
All SDK types now support the generic parameter:
|
|
32
|
+
|
|
33
|
+
- `IrtClient<TSkillId>`
|
|
34
|
+
- `RecordAnswerRequest<TSkillId>`
|
|
35
|
+
- `RecordAnswerResponse<TSkillId>`
|
|
36
|
+
- `SelectNextItemRequest<TSkillId>`
|
|
37
|
+
- `SelectNextItemResponse<TSkillId>`
|
|
38
|
+
- `UserStateResponse<TSkillId>`
|
|
39
|
+
- `ItemStateResponse<TSkillId>`
|
|
40
|
+
- And all other related types
|
|
41
|
+
|
|
42
|
+
## 1.1.0
|
|
43
|
+
|
|
44
|
+
### Minor Changes
|
|
45
|
+
|
|
46
|
+
- Initial SDK release: Add lightweight API client for JavaScript/TypeScript frontends
|
|
47
|
+
|
|
48
|
+
- New `IrtClient` class for consuming IRT Edge API
|
|
49
|
+
- Full TypeScript support with type definitions
|
|
50
|
+
- Complete API methods: recordAnswer, selectNextItem, getUserState, getItemState, health
|
|
51
|
+
- Zero runtime dependencies (only peer dependency on @more-ink/irt-core)
|
|
52
|
+
- Comprehensive documentation and examples
|
|
53
|
+
- Backend server code remains private (not published to npm)
|
|
54
|
+
|
|
55
|
+
## 1.0.0
|
|
56
|
+
|
|
57
|
+
### Major Changes
|
|
58
|
+
|
|
59
|
+
- First major release
|
|
60
|
+
|
|
61
|
+
### Patch Changes
|
|
62
|
+
|
|
63
|
+
- Updated dependencies
|
|
64
|
+
- @more-ink/irt-core@1.0.0
|
package/README.md
CHANGED
|
@@ -1,20 +1,209 @@
|
|
|
1
1
|
# @more-ink/irt-edge
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Two packages in one:**
|
|
4
|
+
1. **SDK** - Lightweight API client for JavaScript/TypeScript frontends (published to npm)
|
|
5
|
+
2. **Backend Server** - Production HTTP API server built with Hono for Aliyun Function Compute (not published)
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
- [SDK Usage (Frontend)](#sdk-usage-frontend)
|
|
12
|
+
- [Installation](#installation)
|
|
13
|
+
- [Quick Start](#quick-start)
|
|
14
|
+
- [Configuration](#configuration)
|
|
15
|
+
- [API Methods](#api-methods)
|
|
16
|
+
- [Error Handling](#error-handling)
|
|
17
|
+
- [Best Practices](#best-practices)
|
|
18
|
+
- [Backend Server Setup](#backend-server-setup)
|
|
19
|
+
- [Backend Deployment](#backend-deployment-aliyun-fc)
|
|
20
|
+
- [SDK Publishing](#sdk-publishing-npm)
|
|
21
|
+
- [Package Scripts](#package-scripts)
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## SDK Usage (Frontend)
|
|
26
|
+
|
|
27
|
+
### Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install @more-ink/irt-edge @more-ink/irt-core
|
|
31
|
+
# or
|
|
32
|
+
pnpm add @more-ink/irt-edge @more-ink/irt-core
|
|
33
|
+
# or
|
|
34
|
+
yarn add @more-ink/irt-edge @more-ink/irt-core
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Note:** `@more-ink/irt-core` is a peer dependency that provides type definitions.
|
|
38
|
+
|
|
39
|
+
### Quick Start
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { IrtClient } from '@more-ink/irt-edge'
|
|
43
|
+
|
|
44
|
+
const client = new IrtClient({
|
|
45
|
+
baseUrl: 'https://your-api.example.com',
|
|
46
|
+
headers: {
|
|
47
|
+
'Authorization': 'Bearer YOUR_TOKEN' // Optional
|
|
48
|
+
},
|
|
49
|
+
timeout: 10000 // Optional (default: 10000ms)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
// Record an answer and get the next item
|
|
53
|
+
const result = await client.recordAnswer({
|
|
54
|
+
userId: 'user123',
|
|
55
|
+
skillId: 'math',
|
|
56
|
+
itemId: 'item456',
|
|
57
|
+
score: 0.8,
|
|
58
|
+
timestamp: Date.now()
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
console.log('Updated ability:', result.theta)
|
|
62
|
+
console.log('Next item:', result.nextItem)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Configuration
|
|
66
|
+
|
|
67
|
+
#### IrtClientConfig
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
interface IrtClientConfig {
|
|
71
|
+
baseUrl: string // API server base URL (required)
|
|
72
|
+
headers?: Record<string, string> // Custom headers (e.g., auth tokens)
|
|
73
|
+
timeout?: number // Request timeout in ms (default: 10000)
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### API Methods
|
|
78
|
+
|
|
79
|
+
#### recordAnswer()
|
|
80
|
+
|
|
81
|
+
Record a user's response and get the next recommended item.
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
async recordAnswer(params: RecordAnswerRequest): Promise<RecordAnswerResponse>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Parameters:**
|
|
88
|
+
```typescript
|
|
89
|
+
interface RecordAnswerRequest {
|
|
90
|
+
userId: string // User identifier
|
|
91
|
+
skillId: string // Skill identifier
|
|
92
|
+
itemId: string // Item identifier
|
|
93
|
+
score: number // Response score [0,1] (0=wrong, 1=correct)
|
|
94
|
+
timestamp?: number // When response occurred (ms)
|
|
95
|
+
updateOptions?: Partial<UpdateOptions> // Override learning rates
|
|
96
|
+
selectionOptions?: Partial<NextItemOptions> // Override selection behavior
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Returns:** Updated ability (theta), standard error (se), next item, and updated states.
|
|
101
|
+
|
|
102
|
+
**Example:**
|
|
103
|
+
```typescript
|
|
104
|
+
const result = await client.recordAnswer({
|
|
105
|
+
userId: 'alice',
|
|
106
|
+
skillId: 'geometry',
|
|
107
|
+
itemId: 'q100',
|
|
108
|
+
score: 1.0,
|
|
109
|
+
timestamp: Date.now()
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
console.log(`Ability: ${result.theta.toFixed(2)} ± ${result.se.toFixed(2)}`)
|
|
113
|
+
if (result.nextItem) {
|
|
114
|
+
console.log(`Next question: ${result.nextItem.id}`)
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
#### selectNextItem()
|
|
119
|
+
|
|
120
|
+
Get the next recommended item without recording a response.
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
async selectNextItem(params: SelectNextItemRequest): Promise<SelectNextItemResponse>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
#### getUserState() / getUserStates()
|
|
127
|
+
|
|
128
|
+
Get current user ability estimates.
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
async getUserState(userId: string, skillId: string): Promise<UserStateResponse>
|
|
132
|
+
async getUserStates(userId: string): Promise<UserStatesResponse>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
#### getItemState() / getItemStates()
|
|
136
|
+
|
|
137
|
+
Get item parameters and metadata.
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
async getItemState(itemId: string, skillId: string): Promise<ItemStateResponse>
|
|
141
|
+
async getItemStates(itemId: string): Promise<ItemStatesResponse>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
#### health()
|
|
145
|
+
|
|
146
|
+
Check API server health status.
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
async health(): Promise<HealthResponse>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
See `examples/sdk-usage.ts` for complete working examples.
|
|
153
|
+
|
|
154
|
+
### Error Handling
|
|
155
|
+
|
|
156
|
+
All methods throw errors for network failures, HTTP errors, timeouts, and invalid responses.
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
try {
|
|
160
|
+
const result = await client.recordAnswer({...})
|
|
161
|
+
// Handle success
|
|
162
|
+
} catch (error) {
|
|
163
|
+
if (error instanceof Error) {
|
|
164
|
+
console.error('API error:', error.message)
|
|
165
|
+
if (error.message.includes('timeout')) {
|
|
166
|
+
// Retry logic
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Best Practices
|
|
173
|
+
|
|
174
|
+
1. **Reuse client instances** - Create one client and reuse it
|
|
175
|
+
2. **Use timestamps** - Always provide client-side timestamps for accurate analytics
|
|
176
|
+
3. **Handle errors gracefully** - Show user-friendly messages on errors
|
|
177
|
+
4. **Check for null items** - The API may return `null` if no suitable item is available
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
const result = await client.selectNextItem({...})
|
|
181
|
+
if (result.nextItem) {
|
|
182
|
+
// Show item to user
|
|
183
|
+
} else {
|
|
184
|
+
// No more items available
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Backend Server Setup
|
|
191
|
+
|
|
192
|
+
The backend server is **not published** to npm. It runs as a service on Aliyun Function Compute.
|
|
193
|
+
|
|
194
|
+
### Features
|
|
6
195
|
|
|
7
196
|
- RESTful IRT API with Redis storage (Upstash)
|
|
8
197
|
- Aliyun FC lifecycle hooks (`/initialize`, `/pre-stop`)
|
|
9
198
|
- Graceful shutdown and health checks
|
|
10
199
|
|
|
11
|
-
|
|
200
|
+
### Development Setup
|
|
12
201
|
|
|
13
202
|
```bash
|
|
14
203
|
# From repo root
|
|
15
204
|
pnpm install
|
|
16
205
|
|
|
17
|
-
# Create .env file
|
|
206
|
+
# Create .env file with:
|
|
18
207
|
# UPSTASH_REDIS_REST_URL=https://...
|
|
19
208
|
# UPSTASH_REDIS_REST_TOKEN=...
|
|
20
209
|
|
|
@@ -25,53 +214,181 @@ pnpm --filter @more-ink/irt-edge dev
|
|
|
25
214
|
pnpm --filter @more-ink/irt-edge seed
|
|
26
215
|
```
|
|
27
216
|
|
|
28
|
-
|
|
217
|
+
### Backend Environment Variables
|
|
29
218
|
|
|
30
|
-
To deploy to Aliyun FC from the repo root, run:
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
pnpm --filter @more-ink/irt-edge run deploy
|
|
34
|
-
```
|
|
35
|
-
or
|
|
36
219
|
```bash
|
|
37
|
-
|
|
38
|
-
|
|
220
|
+
# Aliyun Function Compute
|
|
221
|
+
ALIYUN_ACCESS_KEY="YOUR_ALIYUN_ACCESS_KEY"
|
|
222
|
+
ALIYUN_SECRET_ACCESS_KEY="YOUR_ALIYUN_SECRET_KEY"
|
|
39
223
|
|
|
40
|
-
|
|
224
|
+
# Upstash Redis
|
|
225
|
+
UPSTASH_REDIS_URL_DK="https://upstash-redis-dk.duoink.co"
|
|
226
|
+
UPSTASH_REDIS_TOKEN_DK="YOUR_UPSTASH_TOKEN"
|
|
227
|
+
|
|
228
|
+
# Feishu (Lark) Integration
|
|
229
|
+
FEISHU_APP_ID="YOUR_FEISHU_APP_ID"
|
|
230
|
+
FEISHU_APP_SECRET="YOUR_FEISHU_APP_SECRET"
|
|
231
|
+
FEISHU_CHAT_ID="YOUR_FEISHU_CHAT_ID"
|
|
41
232
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
2
|
|
45
|
-
3. Uploading from `deploy-output/` as an FC layer
|
|
233
|
+
# Optional Redis fallback
|
|
234
|
+
REDIS_URL=""
|
|
235
|
+
REDIS_DB=2
|
|
46
236
|
|
|
47
|
-
|
|
237
|
+
# Debug mode for Function Compute
|
|
238
|
+
DEBUG_FC="true"
|
|
48
239
|
|
|
49
|
-
|
|
240
|
+
# Server port (optional, default: 9000)
|
|
241
|
+
PORT=9000
|
|
242
|
+
```
|
|
50
243
|
|
|
51
|
-
|
|
244
|
+
See `src/index.ts` for default IRT engine parameters (learning rates, selection options).
|
|
245
|
+
|
|
246
|
+
### API Endpoints
|
|
247
|
+
|
|
248
|
+
**IRT Operations:**
|
|
52
249
|
- `POST /api/irt/answer` - Record response and get next item
|
|
53
250
|
- `POST /api/irt/next-item` - Get next item without recording response
|
|
54
251
|
- `GET /api/irt/health` - Health check
|
|
55
252
|
|
|
56
|
-
|
|
253
|
+
**Aliyun FC Lifecycle:**
|
|
57
254
|
- `POST /initialize` - Instance startup (verifies Redis)
|
|
58
255
|
- `GET /pre-stop` - Instance shutdown (closes Redis)
|
|
59
256
|
|
|
60
|
-
|
|
257
|
+
**System:**
|
|
61
258
|
- `GET /` - Service info
|
|
62
259
|
|
|
63
|
-
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Backend Deployment (Aliyun FC)
|
|
263
|
+
|
|
264
|
+
Deploy the backend server to Aliyun FC:
|
|
64
265
|
|
|
65
266
|
```bash
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
ALIYUN_SECRET_ACCESS_KEY=...
|
|
70
|
-
FEISHU_APP_ID=...
|
|
71
|
-
FEISHU_APP_SECRET=...
|
|
72
|
-
FEISHU_CHAT_ID=...
|
|
73
|
-
PORT=9000 # optional
|
|
267
|
+
pnpm --filter @more-ink/irt-edge run deploy
|
|
268
|
+
# or from root
|
|
269
|
+
pnpm dp
|
|
74
270
|
```
|
|
75
271
|
|
|
76
|
-
|
|
272
|
+
### How Deployment Works
|
|
273
|
+
|
|
274
|
+
The deploy process handles pnpm's symlinked `node_modules`:
|
|
275
|
+
1. Build TypeScript to `dist/`
|
|
276
|
+
2. Run `pnpm deploy --prod` to create `deploy-output/` with flat `node_modules`
|
|
277
|
+
3. Upload from `deploy-output/` as an FC layer
|
|
278
|
+
|
|
279
|
+
This ensures all dependencies are included, not just symlinks to pnpm's store.
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## SDK Publishing (npm)
|
|
284
|
+
|
|
285
|
+
Publish the SDK to npm (backend code stays private):
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
# From repo root (recommended)
|
|
289
|
+
pnpm pub
|
|
290
|
+
|
|
291
|
+
# Or from package directory
|
|
292
|
+
cd packages/irt-edge
|
|
293
|
+
pnpm publish:sdk
|
|
294
|
+
|
|
295
|
+
# Or using npm directly
|
|
296
|
+
npm publish --access public
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### What Gets Published
|
|
300
|
+
|
|
301
|
+
**Published to npm:**
|
|
302
|
+
- ✅ `sdk/` - Compiled SDK code (~40KB unpacked)
|
|
303
|
+
- ✅ `README.md`, `CHANGELOG.md` - Documentation
|
|
304
|
+
- ✅ `examples/` - Usage examples
|
|
305
|
+
- ✅ `package.json` - Package metadata
|
|
306
|
+
|
|
307
|
+
**NOT published (stays private):**
|
|
308
|
+
- ❌ Backend server code (`src/index.ts`, `routes.ts`, `storage/`)
|
|
309
|
+
- ❌ Scripts and tests (`scripts/`, `tests/`)
|
|
310
|
+
- ❌ Config files (`.env`, `tsconfig.json`, etc.)
|
|
311
|
+
- ❌ Backend build output (`dist/`, `deploy-output/`)
|
|
312
|
+
|
|
313
|
+
The `.npmignore` file ensures only SDK files are published.
|
|
314
|
+
|
|
315
|
+
### Publishing Checklist
|
|
316
|
+
|
|
317
|
+
Before publishing:
|
|
318
|
+
|
|
319
|
+
1. **Version Management**
|
|
320
|
+
- Update version in `package.json` (follow [semver](https://semver.org/))
|
|
321
|
+
- Update `CHANGELOG.md` with changes
|
|
322
|
+
|
|
323
|
+
2. **Code Quality**
|
|
324
|
+
- Build SDK: `pnpm build:sdk`
|
|
325
|
+
- Run tests: `pnpm test`
|
|
326
|
+
- Check TypeScript: `tsc -p tsconfig.sdk.json --noEmit`
|
|
327
|
+
|
|
328
|
+
3. **Verify Package**
|
|
329
|
+
- Dry-run: `npm pack --dry-run`
|
|
330
|
+
- Check package size (<50KB)
|
|
331
|
+
- Verify only SDK files included
|
|
332
|
+
|
|
333
|
+
4. **Publish**
|
|
334
|
+
- `pnpm pub` (auto-builds before publishing)
|
|
335
|
+
|
|
336
|
+
5. **Post-Publishing**
|
|
337
|
+
- Verify on npm: https://www.npmjs.com/package/@more-ink/irt-edge
|
|
338
|
+
- Tag release: `git tag v1.0.0 && git push origin v1.0.0`
|
|
339
|
+
- Create GitHub release
|
|
340
|
+
|
|
341
|
+
### Test Installation
|
|
342
|
+
|
|
343
|
+
```bash
|
|
344
|
+
mkdir /tmp/test-irt-sdk && cd /tmp/test-irt-sdk
|
|
345
|
+
npm init -y
|
|
346
|
+
npm install @more-ink/irt-edge @more-ink/irt-core
|
|
347
|
+
node -e "const { IrtClient } = require('@more-ink/irt-edge'); console.log('OK')"
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Package Scripts
|
|
353
|
+
|
|
354
|
+
### Development & Backend
|
|
355
|
+
- `pnpm dev` - Run dev server with hot reload
|
|
356
|
+
- `pnpm build` - Build backend server to `dist/`
|
|
357
|
+
- `pnpm start` - Start production backend server
|
|
358
|
+
- `pnpm seed` - Seed Redis with test data
|
|
359
|
+
- `pnpm deploy` - Deploy backend to Aliyun FC
|
|
360
|
+
|
|
361
|
+
### SDK Publishing
|
|
362
|
+
- `pnpm build:sdk` - Build SDK to `sdk/`
|
|
363
|
+
- `pnpm publish:sdk` - Build and publish SDK to npm
|
|
364
|
+
- `pnpm pub` - Shortcut for `pnpm publish:sdk` (from root)
|
|
365
|
+
|
|
366
|
+
### Testing
|
|
367
|
+
- `pnpm test` - Run test suite
|
|
368
|
+
- `pnpm test:watch` - Run tests in watch mode
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## Architecture
|
|
373
|
+
|
|
374
|
+
```
|
|
375
|
+
irt-edge/
|
|
376
|
+
├── src/
|
|
377
|
+
│ ├── sdk/ # ✅ SDK source (published)
|
|
378
|
+
│ │ ├── types.ts # Type definitions
|
|
379
|
+
│ │ ├── client.ts # IrtClient class
|
|
380
|
+
│ │ └── index.ts # Public exports
|
|
381
|
+
│ ├── index.ts # ❌ Backend server (not published)
|
|
382
|
+
│ ├── routes.ts # ❌ Backend routes
|
|
383
|
+
│ └── storage/ # ❌ Backend storage layer
|
|
384
|
+
├── sdk/ # ✅ Compiled SDK (published)
|
|
385
|
+
├── dist/ # ❌ Backend build (not published)
|
|
386
|
+
├── examples/ # ✅ SDK examples (published)
|
|
387
|
+
├── tsconfig.json # Backend build config
|
|
388
|
+
└── tsconfig.sdk.json # SDK build config
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
## License
|
|
392
|
+
|
|
393
|
+
Proprietary - All Rights Reserved
|
|
77
394
|
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example usage of the @more-ink/irt-edge SDK
|
|
3
|
+
*
|
|
4
|
+
* This demonstrates how frontend applications can consume the IRT Edge API,
|
|
5
|
+
* including type-safe skill identifiers.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { IrtClient } from '../sdk/index'
|
|
9
|
+
|
|
10
|
+
// Define your app's skill types for compile-time type safety
|
|
11
|
+
type AppSkills = 'math-algebra' | 'math-geometry' | 'reading' | 'writing'
|
|
12
|
+
|
|
13
|
+
async function main() {
|
|
14
|
+
// Initialize the client with type-safe skills
|
|
15
|
+
const client = new IrtClient<AppSkills>({
|
|
16
|
+
baseUrl: 'http://localhost:9000',
|
|
17
|
+
headers: {
|
|
18
|
+
// Add any authentication headers here if needed
|
|
19
|
+
// 'Authorization': 'Bearer YOUR_TOKEN'
|
|
20
|
+
},
|
|
21
|
+
timeout: 10000, // 10 seconds
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
// 1. Check API health
|
|
26
|
+
console.log('=== Health Check ===')
|
|
27
|
+
const health = await client.health()
|
|
28
|
+
console.log('API Status:', health)
|
|
29
|
+
console.log()
|
|
30
|
+
|
|
31
|
+
// 2. Record an answer and get the next item
|
|
32
|
+
console.log('=== Record Answer & Get Next Item ===')
|
|
33
|
+
const answerResult = await client.recordAnswer({
|
|
34
|
+
userId: 'demo-user-001',
|
|
35
|
+
skillId: 'math-algebra',
|
|
36
|
+
itemId: 'question-42',
|
|
37
|
+
score: 0.8, // 80% correct
|
|
38
|
+
timestamp: Date.now(),
|
|
39
|
+
// Optional: override default update/selection options
|
|
40
|
+
updateOptions: {
|
|
41
|
+
thetaLR: 0.05,
|
|
42
|
+
aLR: 0.001,
|
|
43
|
+
bLR: 0.01,
|
|
44
|
+
},
|
|
45
|
+
selectionOptions: {
|
|
46
|
+
topKRandomize: 5,
|
|
47
|
+
explorationChance: 0.1,
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
console.log('Updated Ability (theta):', answerResult.theta.toFixed(3))
|
|
52
|
+
console.log('Standard Error (SE):', answerResult.se.toFixed(3))
|
|
53
|
+
console.log('Next Item:', answerResult.nextItem)
|
|
54
|
+
console.log()
|
|
55
|
+
|
|
56
|
+
// 3. Get current user state
|
|
57
|
+
console.log('=== Get User State ===')
|
|
58
|
+
const userState = await client.getUserState('demo-user-001', 'math-algebra')
|
|
59
|
+
console.log('Current Theta:', userState.user.theta.toFixed(3))
|
|
60
|
+
console.log('Information Sum:', userState.user.infoSum.toFixed(3))
|
|
61
|
+
console.log('Standard Error:', userState.se.toFixed(3))
|
|
62
|
+
console.log()
|
|
63
|
+
|
|
64
|
+
// 4. Get all skill states for a user
|
|
65
|
+
console.log('=== Get All User Skills ===')
|
|
66
|
+
const allUserStates = await client.getUserStates('demo-user-001')
|
|
67
|
+
console.log(`User has ${allUserStates.count} skill(s):`)
|
|
68
|
+
allUserStates.skills.forEach(skill => {
|
|
69
|
+
console.log(` - ${skill.user.skillId}: theta=${skill.user.theta.toFixed(3)}, se=${skill.se.toFixed(3)}`)
|
|
70
|
+
})
|
|
71
|
+
console.log()
|
|
72
|
+
|
|
73
|
+
// 5. Select next item without recording a response
|
|
74
|
+
console.log('=== Select Next Item (without answer) ===')
|
|
75
|
+
const nextItem = await client.selectNextItem({
|
|
76
|
+
userId: 'demo-user-001',
|
|
77
|
+
skillId: 'math-algebra',
|
|
78
|
+
selectionOptions: {
|
|
79
|
+
topKRandomize: 3,
|
|
80
|
+
explorationChance: 0.15,
|
|
81
|
+
},
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
console.log('User Theta:', nextItem.user?.theta.toFixed(3))
|
|
85
|
+
console.log('Recommended Item:', nextItem.nextItem)
|
|
86
|
+
console.log()
|
|
87
|
+
|
|
88
|
+
// 6. Get item state
|
|
89
|
+
if (nextItem.nextItem) {
|
|
90
|
+
console.log('=== Get Item State ===')
|
|
91
|
+
const itemState = await client.getItemState(
|
|
92
|
+
nextItem.nextItem.id,
|
|
93
|
+
'math-algebra'
|
|
94
|
+
)
|
|
95
|
+
console.log('Item Parameters:')
|
|
96
|
+
console.log(' - Discrimination (a):', itemState.a.toFixed(3))
|
|
97
|
+
console.log(' - Difficulty (b):', itemState.b.toFixed(3))
|
|
98
|
+
console.log(' - Times Seen:', itemState.timesSeen)
|
|
99
|
+
console.log()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
console.log('✅ All operations completed successfully!')
|
|
103
|
+
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error('❌ Error:', error instanceof Error ? error.message : error)
|
|
106
|
+
process.exit(1)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Run the example
|
|
111
|
+
main()
|
package/package.json
CHANGED
|
@@ -1,27 +1,40 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@more-ink/irt-edge",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Edge
|
|
5
|
-
"main": "
|
|
6
|
-
"types": "
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "IRT Edge API client SDK for JavaScript/TypeScript frontends.",
|
|
5
|
+
"main": "sdk/index.js",
|
|
6
|
+
"types": "sdk/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./sdk/index.d.ts",
|
|
10
|
+
"import": "./sdk/index.js",
|
|
11
|
+
"require": "./sdk/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
7
14
|
"files": [
|
|
8
|
-
"
|
|
15
|
+
"sdk",
|
|
16
|
+
"README.md",
|
|
17
|
+
"CHANGELOG.md",
|
|
18
|
+
"examples"
|
|
9
19
|
],
|
|
10
|
-
"
|
|
11
|
-
"@
|
|
12
|
-
"hono": "^4.10.7",
|
|
13
|
-
"ioredis": "^5.8.2",
|
|
14
|
-
"@more-ink/irt-core": "1.0.0"
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"@more-ink/irt-core": "^1.0.0"
|
|
15
22
|
},
|
|
23
|
+
"dependencies": {},
|
|
16
24
|
"devDependencies": {
|
|
25
|
+
"@hono/node-server": "^1.19.6",
|
|
17
26
|
"@larksuiteoapi/node-sdk": "^1.55.0",
|
|
18
27
|
"@upstash/redis": "^1.35.7",
|
|
19
28
|
"dotenv": "^16.6.1",
|
|
20
29
|
"fc-deploy": "^1.2.3",
|
|
21
|
-
"
|
|
30
|
+
"hono": "^4.10.7",
|
|
31
|
+
"ioredis": "^5.8.2",
|
|
32
|
+
"tslib": "^2.8.1",
|
|
33
|
+
"@more-ink/irt-core": "1.0.0"
|
|
22
34
|
},
|
|
23
35
|
"scripts": {
|
|
24
36
|
"build": "tsc -p tsconfig.json",
|
|
37
|
+
"build:sdk": "tsc -p tsconfig.sdk.json",
|
|
25
38
|
"start": "node dist/index.js",
|
|
26
39
|
"test": "vitest run",
|
|
27
40
|
"test:watch": "vitest watch",
|