@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 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
- Production HTTP API server for the streaming IRT engine, built with Hono for deployment on Aliyun Function Compute.
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
- ## Features
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
- ## Quick Start
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
- ## Deployment
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
- pnpm dp
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
- ### How Deployment Works
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
- The deploy process handles pnpm's symlinked `node_modules` by:
43
- 1. Building TypeScript to `dist/`
44
- 2. Running `pnpm deploy --prod` to create `deploy-output/` with flat, real `node_modules` (no symlinks)
45
- 3. Uploading from `deploy-output/` as an FC layer
233
+ # Optional Redis fallback
234
+ REDIS_URL=""
235
+ REDIS_DB=2
46
236
 
47
- This ensures all dependencies are included in the layer, not just symlinks to pnpm's store.
237
+ # Debug mode for Function Compute
238
+ DEBUG_FC="true"
48
239
 
49
- ## API Endpoints
240
+ # Server port (optional, default: 9000)
241
+ PORT=9000
242
+ ```
50
243
 
51
- ### IRT Operations
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
- ### Aliyun FC Lifecycle
253
+ **Aliyun FC Lifecycle:**
57
254
  - `POST /initialize` - Instance startup (verifies Redis)
58
255
  - `GET /pre-stop` - Instance shutdown (closes Redis)
59
256
 
60
- ### System
257
+ **System:**
61
258
  - `GET /` - Service info
62
259
 
63
- ## Environment Variables
260
+ ---
261
+
262
+ ## Backend Deployment (Aliyun FC)
263
+
264
+ Deploy the backend server to Aliyun FC:
64
265
 
65
266
  ```bash
66
- UPSTASH_REDIS_URL_DK=https://...
67
- UPSTASH_REDIS_TOKEN_DK=...
68
- ALIYUN_ACCESS_KEY=...
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
- See `src/index.ts` for default IRT engine parameters (learning rates, selection options).
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.0.0",
4
- "description": "Edge-function wrapper around the node-irt core library.",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
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
- "dist"
15
+ "sdk",
16
+ "README.md",
17
+ "CHANGELOG.md",
18
+ "examples"
9
19
  ],
10
- "dependencies": {
11
- "@hono/node-server": "^1.19.6",
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
- "tslib": "^2.8.1"
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",