@modelhealth/modelhealth 0.1.17

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/dist/index.js ADDED
@@ -0,0 +1,844 @@
1
+ /**
2
+ * Model Health SDK Client
3
+ *
4
+ * TypeScript/JavaScript client for the Model Health biomechanics SDK.
5
+ * Provides a clean, typed API over the WASM bindings.
6
+ *
7
+ * @packageDocumentation
8
+ *
9
+ * @example Basic usage
10
+ * ```typescript
11
+ * import { ModelHealthService } from '@modelhealth/modelhealth';
12
+ *
13
+ * // Create and initialize client with API key
14
+ * const client = new ModelHealthService({ apiKey: "your-api-key-here" });
15
+ * await client.init();
16
+ *
17
+ * // Authenticate (optional - API key already provides authentication)
18
+ * const result = await client.login("user@example.com", "password");
19
+ * if (result === "verification_required") {
20
+ * await client.verify("123456", true);
21
+ * }
22
+ *
23
+ * // Get sessions
24
+ * const sessions = await client.sessionList();
25
+ * ```
26
+ */
27
+ let wasmModule = null;
28
+ let wasmInitialized = false;
29
+ let wasmInitPromise = null;
30
+ /**
31
+ * Initialize the WASM module.
32
+ *
33
+ * This must be called before using the SDK. It loads and initializes
34
+ * the WebAssembly module containing the core SDK functionality.
35
+ *
36
+ * @internal
37
+ */
38
+ async function initWasm() {
39
+ if (wasmInitialized)
40
+ return;
41
+ if (wasmInitPromise)
42
+ return wasmInitPromise;
43
+ wasmInitPromise = (async () => {
44
+ try {
45
+ wasmModule = await import("../wasm/model_health_wasm.js");
46
+ await wasmModule.default();
47
+ await wasmModule.init();
48
+ wasmInitialized = true;
49
+ }
50
+ catch (error) {
51
+ wasmInitPromise = null;
52
+ throw new Error(`Failed to initialize WASM module: ${error}`);
53
+ }
54
+ })();
55
+ return wasmInitPromise;
56
+ }
57
+ /**
58
+ * Model Health SDK Client for biomechanical analysis.
59
+ *
60
+ * Main entry point for interacting with the Model Health SDK.
61
+ * Provides authentication, session management, data download,
62
+ * and analysis capabilities.
63
+ *
64
+ * @example Create with API key
65
+ * ```typescript
66
+ * const client = new ModelHealthService({
67
+ * apiKey: "your-api-key-here"
68
+ * });
69
+ * await client.init();
70
+ *
71
+ * // SDK is ready to use
72
+ * const sessions = await client.sessionList();
73
+ * ```
74
+ *
75
+ * @example With custom configuration
76
+ * ```typescript
77
+ * const client = new ModelHealthService({
78
+ * apiKey: "your-api-key"
79
+ * });
80
+ * await client.init();
81
+ * ```
82
+ */
83
+ export class ModelHealthService {
84
+ /**
85
+ * Create a new Model Health client.
86
+ *
87
+ * @param config Configuration options including API key
88
+ * @throws If API key is not provided
89
+ *
90
+ * @example Default configuration
91
+ * ```typescript
92
+ * const client = new ModelHealthService({
93
+ * apiKey: "your-api-key-here"
94
+ * });
95
+ * ```
96
+ *
97
+ * @example Custom configuration
98
+ * ```typescript
99
+ * const client = new ModelHealthService({
100
+ * apiKey: "your-api-key",
101
+ * autoInit: false
102
+ * });
103
+ * ```
104
+ */
105
+ constructor(config) {
106
+ this.wasmClient = null;
107
+ this.initialized = false;
108
+ if (!config.apiKey) {
109
+ throw new Error("API key is required. Provide it in the config: { apiKey: 'your-key' }");
110
+ }
111
+ this.config = {
112
+ apiKey: config.apiKey,
113
+ autoInit: config.autoInit ?? true,
114
+ };
115
+ // Auto-initialize if requested
116
+ if (this.config.autoInit) {
117
+ this.init().catch((error) => {
118
+ console.error("Failed to auto-initialize Model Health client:", error);
119
+ });
120
+ }
121
+ }
122
+ /**
123
+ * Initialize the WASM module and client.
124
+ *
125
+ * Must be called before using any other methods if `autoInit: false`
126
+ * was specified in the configuration. Safe to call multiple times.
127
+ *
128
+ * @throws If WASM initialization fails
129
+ *
130
+ * @example
131
+ * ```typescript
132
+ * const client = new ModelHealthService({
133
+ * apiKey: "your-key",
134
+ * autoInit: false
135
+ * });
136
+ * await client.init();
137
+ * ```
138
+ */
139
+ async init() {
140
+ if (this.initialized)
141
+ return;
142
+ await initWasm();
143
+ // Create the WASM client with API key
144
+ try {
145
+ this.wasmClient = new wasmModule.ModelHealthService(this.config.apiKey);
146
+ }
147
+ catch (error) {
148
+ throw new Error(`Failed to create Model Health client: ${error}`);
149
+ }
150
+ this.initialized = true;
151
+ }
152
+ /**
153
+ * Ensure the client is initialized.
154
+ *
155
+ * @private
156
+ * @throws If client is not initialized
157
+ */
158
+ ensureInitialized() {
159
+ if (!this.initialized) {
160
+ throw new Error("Model Health client not initialized. Call init() before using the client.");
161
+ }
162
+ }
163
+ // MARK: - Authentication
164
+ /**
165
+ * Checks if a user is currently authenticated.
166
+ *
167
+ * @returns `true` if authenticated, `false` otherwise
168
+ *
169
+ * @example
170
+ * ```typescript
171
+ * if (await client.isAuthenticated()) {
172
+ * // Proceed with authenticated operations
173
+ * const sessions = await client.sessionList();
174
+ * } else {
175
+ * // Show login screen
176
+ * }
177
+ * ```
178
+ */
179
+ async isAuthenticated() {
180
+ this.ensureInitialized();
181
+ return await this.wasmClient.isAuthenticated();
182
+ }
183
+ // MARK: - Sessions
184
+ /**
185
+ * Retrieves all sessions for the authenticated user.
186
+ *
187
+ * @returns An array of `Session` objects. Returns an empty array if no sessions exist.
188
+ * @throws If the request fails due to network issues, authentication problems,
189
+ * or server errors.
190
+ *
191
+ * @example
192
+ * ```typescript
193
+ * try {
194
+ * const sessions = await client.sessionList();
195
+ * console.log(`Found ${sessions.length} sessions`);
196
+ * for (const session of sessions) {
197
+ * console.log(`Session: ${session.id}`);
198
+ * }
199
+ * } catch (error) {
200
+ * console.log(`Failed to fetch sessions: ${error}`);
201
+ * }
202
+ * ```
203
+ */
204
+ async sessionList() {
205
+ this.ensureInitialized();
206
+ const result = await this.wasmClient.sessionList();
207
+ return this.parseResponse(result);
208
+ }
209
+ /**
210
+ * Retrieve a specific session by ID with all activities populated.
211
+ *
212
+ * @param sessionId Unique session identifier
213
+ * @returns The requested session with complete activity data
214
+ * @throws If the session doesn't exist, user lacks access, or request fails
215
+ *
216
+ * @example
217
+ * ```typescript
218
+ * const session = await client.getSession("session-abc123");
219
+ * console.log(`Session has ${session.activities.length} activities`);
220
+ * ```
221
+ */
222
+ async getSession(sessionId) {
223
+ this.ensureInitialized();
224
+ const result = await this.wasmClient.getSession(sessionId);
225
+ return this.parseResponse(result);
226
+ }
227
+ /**
228
+ * Creates a new session.
229
+ *
230
+ * A session is required before performing camera calibration. It represents
231
+ * a single calibration workflow and groups multiple cameras together.
232
+ *
233
+ * After creating a session, use camera calibration methods to calibrate your cameras.
234
+ *
235
+ * @returns A `Session` object with a unique identifier
236
+ * @throws If session creation fails
237
+ *
238
+ * @example
239
+ * ```typescript
240
+ * // Create session
241
+ * const session = await client.createSession();
242
+ *
243
+ * // Proceed with calibration
244
+ * const details = {
245
+ * rows: 4,
246
+ * columns: 5,
247
+ * square_size: 35,
248
+ * placement: "perpendicular"
249
+ * };
250
+ * // await client.calibrateCamera(session, details, (status) => { ... });
251
+ * ```
252
+ */
253
+ async createSession() {
254
+ this.ensureInitialized();
255
+ const result = await this.wasmClient.createSession();
256
+ return this.parseResponse(result);
257
+ }
258
+ /**
259
+ * Calibrates a camera using a checkerboard pattern.
260
+ *
261
+ * **Requirements:**
262
+ * - A printed checkerboard pattern
263
+ * - Accurate measurement of square size in millimeters
264
+ * - Multiple views of the checkerboard from different angles
265
+ *
266
+ * The calibration is automated and typically completes in a few seconds
267
+ *
268
+ * @param session The session created with `createSession()`
269
+ * @param checkerboardDetails Configuration of the calibration checkerboard
270
+ * @param statusCallback Callback function called with calibration progress updates
271
+ * @throws If calibration fails (insufficient views, pattern not detected, etc.)
272
+ *
273
+ * @example
274
+ * ```typescript
275
+ * const session = await client.createSession();
276
+ *
277
+ * const details = {
278
+ * rows: 4, // Internal corners, not squares (for 5×6 board)
279
+ * columns: 5, // Internal corners, not squares (for 5×6 board)
280
+ * square_size: 35, // Measured in millimeters
281
+ * placement: "perpendicular"
282
+ * };
283
+ *
284
+ * await client.calibrateCamera(session, details, (status) => {
285
+ * console.log("Calibration status:", status);
286
+ * });
287
+ * // Calibration complete, proceed to neutral pose
288
+ * ```
289
+ */
290
+ async calibrateCamera(session, checkerboardDetails, statusCallback) {
291
+ this.ensureInitialized();
292
+ const jsCallback = (statusJson) => {
293
+ statusCallback(statusJson);
294
+ };
295
+ await wasmModule.calibrateCamera(this.config.apiKey, session, checkerboardDetails, jsCallback);
296
+ }
297
+ /**
298
+ * Captures the subject's neutral standing pose for model scaling.
299
+ *
300
+ * This step is required after camera calibration and before recording movement activities.
301
+ * It takes a quick video of the subject standing in a neutral position, which is
302
+ * used to scale the biomechanical model to match the subject's dimensions.
303
+ *
304
+ * **Instructions for subject:**
305
+ * - Stand upright in a relaxed, natural position
306
+ * - Face forward with arms spread slightly at sides
307
+ * - Remain still for a few seconds
308
+ *
309
+ * @param subject The subject to calibrate the neutral pose for
310
+ * @param session The session to perform calibration in
311
+ * @param statusCallback Callback function called with calibration progress updates
312
+ * @throws If pose capture fails (subject not detected, poor lighting, etc.)
313
+ *
314
+ * @example
315
+ * ```typescript
316
+ * // After successful camera calibration
317
+ * await client.calibrateNeutralPose(subject, session, (status) => {
318
+ * console.log("Neutral pose status:", status);
319
+ * });
320
+ * // Model now scaled, ready to record movement activities
321
+ * ```
322
+ */
323
+ async calibrateNeutralPose(subject, session, statusCallback) {
324
+ this.ensureInitialized();
325
+ const jsCallback = (statusJson) => {
326
+ statusCallback(statusJson);
327
+ };
328
+ await wasmModule.calibrateNeutralPose(this.config.apiKey, subject, session, jsCallback);
329
+ }
330
+ // MARK: - Subjects
331
+ /**
332
+ * Retrieves all subjects associated with the authenticated account.
333
+ *
334
+ * Subjects represent individuals being monitored or assessed. Each subject
335
+ * contains demographic information, physical measurements, and categorization tags.
336
+ *
337
+ * @returns An array of `Subject` objects
338
+ * @throws If the request fails or authentication has expired
339
+ *
340
+ * @example
341
+ * ```typescript
342
+ * const subjects = await client.subjectList();
343
+ * for (const subject of subjects) {
344
+ * console.log(`${subject.name}: ${subject.height ?? 0}cm, ${subject.weight ?? 0}kg`);
345
+ * }
346
+ *
347
+ * // Filter by tags
348
+ * const athletes = subjects.filter(s => s.subject_tags.includes("athlete"));
349
+ * ```
350
+ */
351
+ async subjectList() {
352
+ this.ensureInitialized();
353
+ const result = await this.wasmClient.subjectList();
354
+ return this.parseResponse(result);
355
+ }
356
+ /**
357
+ * Creates a new subject in the system.
358
+ *
359
+ * Subjects represent individuals being monitored or assessed. After creating
360
+ * a subject, they can be associated with sessions for neutral pose calibration
361
+ * and movement activities.
362
+ *
363
+ * @param parameters Subject details including name, measurements, and tags
364
+ * @returns The newly created `Subject` with its assigned ID
365
+ * @throws If creation fails (validation errors, duplicate name, etc.)
366
+ *
367
+ * @example
368
+ * ```typescript
369
+ * const params = {
370
+ * name: "John Doe",
371
+ * weight: 75.0, // kilograms
372
+ * height: 180.0, // centimeters
373
+ * birth_year: 1990,
374
+ * gender: "man",
375
+ * sex_at_birth: "man",
376
+ * characteristics: "Regular training schedule",
377
+ * subject_tags: ["athlete"],
378
+ * terms: true
379
+ * };
380
+ *
381
+ * const subject = await client.createSubject(params);
382
+ * console.log(`Created subject with ID: ${subject.id}`);
383
+ *
384
+ * // Use the subject for calibration
385
+ * // await client.calibrateNeutralPose(subject, session, (status) => { ... });
386
+ * ```
387
+ */
388
+ async createSubject(parameters) {
389
+ this.ensureInitialized();
390
+ const result = await this.wasmClient.createSubject(parameters);
391
+ return this.parseResponse(result);
392
+ }
393
+ // MARK: - Activity Management
394
+ /**
395
+ * Retrieves activities for a specific subject with pagination and sorting.
396
+ *
397
+ * This method allows you to fetch activities associated with a particular subject,
398
+ * with control over pagination and sort order. This is useful for displaying
399
+ * activity history or implementing infinite scroll interfaces.
400
+ *
401
+ * @param subjectId The ID of the subject whose activities to retrieve
402
+ * @param startIndex Zero-based index to start from (for pagination). Use 0 for first page.
403
+ * @param count Number of activities to retrieve per request
404
+ * @param sort Sort order for the results (e.g., "updated_at" for most recent first)
405
+ * @returns An array of activities for the specified subject
406
+ * @throws If the request fails or authentication has expired
407
+ *
408
+ * @example
409
+ * ```typescript
410
+ * // Get the 20 most recent activities for a subject
411
+ * const recentActivities = await client.getActivitiesForSubject(
412
+ * "subject-123",
413
+ * 0,
414
+ * 20,
415
+ * "updated_at"
416
+ * );
417
+ *
418
+ * // Pagination - get the next 20 activities
419
+ * const nextPage = await client.getActivitiesForSubject(
420
+ * "subject-123",
421
+ * 20,
422
+ * 20,
423
+ * "updated_at"
424
+ * );
425
+ * ```
426
+ */
427
+ async getActivitiesForSubject(subjectId, startIndex, count, sort) {
428
+ this.ensureInitialized();
429
+ const result = await this.wasmClient.getActivitiesForSubject(subjectId, startIndex, count, sort);
430
+ return this.parseResponse(result);
431
+ }
432
+ /**
433
+ * Retrieves a specific activity by its ID.
434
+ *
435
+ * Use this method to fetch the complete details of an activity, including
436
+ * its videos, results, and current processing status.
437
+ *
438
+ * @param activityId The unique identifier of the activity
439
+ * @returns The requested activity with all its details
440
+ * @throws If the activity doesn't exist, or if authentication has expired
441
+ *
442
+ * @example
443
+ * ```typescript
444
+ * const activity = await client.getActivity("abc123");
445
+ * console.log(`Activity: ${activity.name ?? "Unnamed"}`);
446
+ * console.log(`Status: ${activity.status}`);
447
+ * console.log(`Videos: ${activity.videos.length}`);
448
+ * ```
449
+ */
450
+ async getActivity(activityId) {
451
+ this.ensureInitialized();
452
+ const result = await this.wasmClient.getActivity(activityId);
453
+ return this.parseResponse(result);
454
+ }
455
+ /**
456
+ * Updates an existing activity.
457
+ *
458
+ * Use this method to modify activity properties such as the name.
459
+ * The activity is updated on the server and the updated version is returned.
460
+ *
461
+ * @param activity The activity to update (with modified properties)
462
+ * @returns The updated activity as stored on the server
463
+ * @throws If the update fails or authentication has expired
464
+ *
465
+ * @example
466
+ * ```typescript
467
+ * let activity = await client.getActivity("abc123");
468
+ * // Modify the activity name
469
+ * activity.name = "CMJ Baseline Test";
470
+ * const updated = await client.updateActivity(activity);
471
+ * console.log(`Updated: ${updated.name ?? ""}`);
472
+ * ```
473
+ *
474
+ * @note Not all activity properties can be modified. Only mutable fields
475
+ * (such as `name`) will be updated on the server.
476
+ */
477
+ async updateActivity(activity) {
478
+ this.ensureInitialized();
479
+ const result = await this.wasmClient.updateActivity(activity);
480
+ return this.parseResponse(result);
481
+ }
482
+ /**
483
+ * Deletes an activity from the system.
484
+ *
485
+ * This permanently removes the activity and all its associated data,
486
+ * including videos and analysis results. This action cannot be undone.
487
+ *
488
+ * @param activity The activity to delete
489
+ * @throws If the deletion fails or authentication has expired
490
+ *
491
+ * @example
492
+ * ```typescript
493
+ * const activity = await client.getActivity("abc123");
494
+ * await client.deleteActivity(activity);
495
+ * // Activity and all associated data are now permanently deleted
496
+ * ```
497
+ *
498
+ * @warning This operation is irreversible. All videos, analysis results,
499
+ * and metadata associated with this activity will be permanently lost.
500
+ */
501
+ async deleteActivity(activity) {
502
+ this.ensureInitialized();
503
+ await this.wasmClient.deleteActivity(activity);
504
+ }
505
+ /**
506
+ * Retrieves all available activity tags.
507
+ *
508
+ * Activity tags provide a way to categorize and filter activities.
509
+ * This method returns all tags configured in the system, which can be
510
+ * used for filtering or organizing activities in your application.
511
+ *
512
+ * @returns An array of available activity tags
513
+ * @throws If the request fails or authentication has expired
514
+ *
515
+ * @example
516
+ * ```typescript
517
+ * const tags = await client.getActivityTags();
518
+ * for (const tag of tags) {
519
+ * console.log(`${tag.label}: ${tag.value}`);
520
+ * }
521
+ *
522
+ * // Use tags for filtering or categorization
523
+ * const cmjTag = tags.find(t => t.value === "cmj");
524
+ * ```
525
+ */
526
+ async getActivityTags() {
527
+ this.ensureInitialized();
528
+ const result = await this.wasmClient.getActivityTags();
529
+ return this.parseResponse(result);
530
+ }
531
+ // MARK: - Activities
532
+ /**
533
+ * Retrieves all movement activities associated with the authenticated account.
534
+ *
535
+ * Activities represent individual recording sessions and contain references to
536
+ * captured videos and analysis results. Use this to review past data or
537
+ * fetch analysis for completed activities.
538
+ *
539
+ * @param sessionId Session identifier
540
+ * @returns An array of `Activity` objects
541
+ * @throws If the request fails or authentication has expired
542
+ *
543
+ * @example
544
+ * ```typescript
545
+ * const activities = await client.activityList(session.id);
546
+ *
547
+ * // Find completed activities ready for analysis
548
+ * const completed = activities.filter(t => t.status === "completed");
549
+ *
550
+ * // Access videos and results
551
+ * for (const activity of completed) {
552
+ * console.log(`Activity: ${activity.name ?? activity.id}`);
553
+ * console.log(`Videos: ${activity.videos.length}`);
554
+ * console.log(`Results: ${activity.results.length}`);
555
+ * }
556
+ * ```
557
+ */
558
+ async activityList(sessionId) {
559
+ this.ensureInitialized();
560
+ const result = await this.wasmClient.trialList(sessionId);
561
+ return this.parseResponse(result);
562
+ }
563
+ /**
564
+ * Download video data for a specific activity.
565
+ *
566
+ * Asynchronously fetches all videos associated with a given activity that match the specified type.
567
+ * Videos with invalid URLs or failed downloads are silently excluded from the result.
568
+ *
569
+ * @param activity The activity whose videos should be downloaded
570
+ * @param version The version type of videos to download (default: "synced")
571
+ * @returns An array of video data as Uint8Array. The array may be empty if no valid
572
+ * videos are available or all downloads fail.
573
+ *
574
+ * @example
575
+ * ```typescript
576
+ * const activity = // ... obtained activity
577
+ * const videoData = await client.downloadActivityVideos(activity, "raw");
578
+ *
579
+ * for (const data of videoData) {
580
+ * // Process video data
581
+ * }
582
+ * ```
583
+ *
584
+ * @note This method performs concurrent downloads for optimal performance. Individual download
585
+ * failures do not affect other requests.
586
+ */
587
+ async downloadActivityVideos(activity, version = "synced") {
588
+ this.ensureInitialized();
589
+ const result = await this.wasmClient.downloadTrialVideos(activity, version);
590
+ const videos = [];
591
+ for (let i = 0; i < result.length; i++) {
592
+ videos.push(new Uint8Array(result[i]));
593
+ }
594
+ return videos;
595
+ }
596
+ /**
597
+ * Downloads result data files from a processed activity.
598
+ *
599
+ * After an activity completes processing, various result files become available for download.
600
+ * Use this method to retrieve specific types of data (kinematic measurements, visualizations)
601
+ * in their native file formats (JSON, CSV).
602
+ *
603
+ * This method is useful when you need access to raw analysis data rather than the
604
+ * structured metrics provided by analysis result methods.
605
+ *
606
+ * @param activity The completed activity to download data from
607
+ * @param dataTypes The types of result data to download
608
+ * @returns An array of result data, one entry per requested type. Returns an empty array if no
609
+ * results are available or all downloads fail.
610
+ *
611
+ * @example
612
+ * ```typescript
613
+ * // Download kinematics in MOT format
614
+ * const results = await client.downloadActivityResultData(activity, ["kinematics_mot"]);
615
+ *
616
+ * for (const result of results) {
617
+ * switch (result.result_data_type) {
618
+ * case "kinematics_mot":
619
+ * // Use result.data directly as a .mot file
620
+ * break;
621
+ * }
622
+ * }
623
+ *
624
+ * // Download multiple types in one call
625
+ * const allData = await client.downloadActivityResultData(
626
+ * activity,
627
+ * ["kinematics_mot", "animation"]
628
+ * );
629
+ * console.log(`Downloaded ${allData.length} result files`);
630
+ * ```
631
+ *
632
+ * @note This method performs concurrent downloads for optimal performance.
633
+ * Individual download failures do not affect other requests and failed downloads
634
+ * are silently excluded from results.
635
+ */
636
+ async downloadActivityResultData(activity, dataTypes) {
637
+ this.ensureInitialized();
638
+ const result = await this.wasmClient.downloadActivityResultData(activity, dataTypes);
639
+ return this.parseResponse(result);
640
+ }
641
+ /**
642
+ * Downloads analysis result data for a completed activity.
643
+ *
644
+ * @param activity The activity that has completed analysis
645
+ * @param dataTypes The types of analysis result data to download
646
+ * @returns An array of analysis result data, one entry per requested type. Returns an empty
647
+ * array if no results are available or all downloads fail.
648
+ *
649
+ * @example
650
+ * ```typescript
651
+ * const results = await client.downloadActivityAnalysisResultData(
652
+ * activity,
653
+ * ["metrics", "report"]
654
+ * );
655
+ *
656
+ * for (const result of results) {
657
+ * switch (result.result_data_type) {
658
+ * case "metrics":
659
+ * const json = JSON.parse(new TextDecoder().decode(result.data));
660
+ * break;
661
+ * case "report":
662
+ * // Use result.data directly as a PDF
663
+ * break;
664
+ * case "data":
665
+ * // Use result.data directly as a ZIP file
666
+ * break;
667
+ * }
668
+ * }
669
+ * ```
670
+ *
671
+ * @note Individual download failures are silently excluded from results.
672
+ */
673
+ async downloadActivityAnalysisResultData(activity, dataTypes) {
674
+ this.ensureInitialized();
675
+ const result = await this.wasmClient.downloadTrialAnalysisResultData(activity, dataTypes);
676
+ return this.parseResponse(result);
677
+ }
678
+ // MARK: - Recording & Analysis
679
+ /**
680
+ * Starts recording a dynamic movement activity.
681
+ *
682
+ * After completing calibration steps (camera calibration and neutral pose),
683
+ * use this method to begin recording an activity.
684
+ *
685
+ * @param activityName A descriptive name for this activity (e.g., "cmj-test")
686
+ * @param session The session this activity is associated with
687
+ * @returns The newly created activity
688
+ * @throws If recording cannot start (session not calibrated, camera issues, etc.)
689
+ *
690
+ * @example
691
+ * ```typescript
692
+ * // Record a CMJ session
693
+ * const activity = await client.record("cmj-2024", session);
694
+ * // Subject performs CMJ while cameras record
695
+ *
696
+ * // When complete, stop recording
697
+ * await client.stopRecording(session);
698
+ * ```
699
+ */
700
+ async record(activityName, session) {
701
+ this.ensureInitialized();
702
+ const result = await this.wasmClient.record(activityName, session);
703
+ return this.parseResponse(result);
704
+ }
705
+ /**
706
+ * Stops recording of a dynamic movement activity in a session.
707
+ *
708
+ * Call this method when the subject has completed the movement activity.
709
+ *
710
+ * @param session The session to stop recording in
711
+ * @throws If the activity cannot be stopped (invalid session ID, already stopped, etc.)
712
+ *
713
+ * @example
714
+ * ```typescript
715
+ * // After recording is complete
716
+ * await client.stopRecording(session);
717
+ * ```
718
+ */
719
+ async stopRecording(session) {
720
+ this.ensureInitialized();
721
+ await this.wasmClient.stopRecording(session);
722
+ }
723
+ /**
724
+ * Retrieves the current processing status of an activity.
725
+ *
726
+ * Poll this method to determine when an activity is ready for analysis.
727
+ * Activities must complete video upload and processing before analysis can begin.
728
+ *
729
+ * @param activity A completed activity
730
+ * @returns The current processing status
731
+ * @throws Network or authentication errors
732
+ *
733
+ * @example
734
+ * ```typescript
735
+ * const status = await client.getStatus(activity);
736
+ *
737
+ * switch (status.type) {
738
+ * case "ready":
739
+ * console.log("Activity ready for analysis");
740
+ * break;
741
+ * case "processing":
742
+ * console.log("Still processing...");
743
+ * break;
744
+ * case "uploading":
745
+ * console.log(`Uploaded ${status.uploaded}/${status.total} videos`);
746
+ * break;
747
+ * case "failed":
748
+ * console.log("Processing failed");
749
+ * break;
750
+ * }
751
+ * ```
752
+ */
753
+ async getStatus(activity) {
754
+ this.ensureInitialized();
755
+ const result = await this.wasmClient.getStatus(activity);
756
+ return this.parseResponse(result);
757
+ }
758
+ /**
759
+ * Starts an analysis task for a completed activity.
760
+ *
761
+ * The activity must have completed processing (status `.ready`) before analysis can begin.
762
+ * Use the returned `AnalysisTask` to poll for completion.
763
+ *
764
+ * @param analysisType The type of analysis to perform, Gait, Squats, etc
765
+ * @param activity The activity to analyze
766
+ * @param session The session containing the activity
767
+ * @returns An analysis task for tracking completion
768
+ * @throws Network or authentication errors
769
+ *
770
+ * @example
771
+ * ```typescript
772
+ * const task = await client.startAnalysis(
773
+ * "counterMovementJump",
774
+ * activity,
775
+ * session
776
+ * );
777
+ *
778
+ * // Poll for completion
779
+ * const status = await client.getAnalysisStatus(task);
780
+ * ```
781
+ */
782
+ async startAnalysis(analysisType, activity, session) {
783
+ this.ensureInitialized();
784
+ const result = await this.wasmClient.startAnalysis(analysisType, activity, session);
785
+ return this.parseResponse(result);
786
+ }
787
+ /**
788
+ * Retrieves the current status of an analysis task.
789
+ *
790
+ * Poll this method to monitor analysis progress. When status is `.completed`,
791
+ * use `downloadActivityAnalysisResultData` to fetch metrics, report, or raw data.
792
+ *
793
+ * @param task The task returned from `startAnalysis`
794
+ * @returns The current analysis status
795
+ * @throws Network or authentication errors
796
+ *
797
+ * @example
798
+ * ```typescript
799
+ * const status = await client.getAnalysisStatus(task);
800
+ *
801
+ * switch (status.type) {
802
+ * case "processing":
803
+ * console.log("Analysis running...");
804
+ * break;
805
+ * case "completed":
806
+ * const results = await client.downloadActivityAnalysisResultData(
807
+ * activity,
808
+ * ["metrics", "report"]
809
+ * );
810
+ * const metricsEntry = results.find((r) => r.result_data_type === "metrics");
811
+ * if (metricsEntry?.data) {
812
+ * const metrics = JSON.parse(new TextDecoder().decode(metricsEntry.data));
813
+ * console.log("Metrics:", metrics);
814
+ * }
815
+ * break;
816
+ * case "failed":
817
+ * console.log("Analysis failed");
818
+ * break;
819
+ * }
820
+ * ```
821
+ */
822
+ async getAnalysisStatus(task) {
823
+ this.ensureInitialized();
824
+ const result = await this.wasmClient.getAnalysisStatus(task);
825
+ return this.parseResponse(result);
826
+ }
827
+ // MARK: - Utilities
828
+ /**
829
+ * Parse JSON response from WASM.
830
+ *
831
+ * @private
832
+ * @param value Value from WASM (may be string or object)
833
+ * @returns Parsed TypeScript object
834
+ */
835
+ parseResponse(value) {
836
+ if (typeof value === "string") {
837
+ return JSON.parse(value);
838
+ }
839
+ return value;
840
+ }
841
+ }
842
+ // MARK: - Exports
843
+ export * from "./types.js";
844
+ //# sourceMappingURL=index.js.map