trackswap 0.2.2 → 0.2.4

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.
Files changed (53) hide show
  1. package/README.md +16 -0
  2. package/dist/FIT/base.d.ts +30 -0
  3. package/dist/FIT/decoder.d.ts +48 -0
  4. package/dist/FIT/encoder.d.ts +96 -1
  5. package/dist/FIT/encoderCourse.d.ts +45 -0
  6. package/dist/FIT/encoderWorkout.d.ts +45 -0
  7. package/dist/FIT/health.d.ts +191 -4
  8. package/dist/FIT/index.d.ts +1 -1
  9. package/dist/FIT/mesg.d.ts +3 -0
  10. package/dist/FIT/middleware.d.ts +15 -0
  11. package/dist/FIT/plugins.d.ts +9 -0
  12. package/dist/FIT/processor.d.ts +24 -0
  13. package/dist/FIT/types/checksum_type.d.ts +6 -0
  14. package/dist/FIT/types/date_time_type.d.ts +3 -0
  15. package/dist/FIT/types/file.d.ts +63 -0
  16. package/dist/FIT/types/file_flags_type.d.ts +9 -0
  17. package/dist/FIT/types/local_date_time_type.d.ts +3 -0
  18. package/dist/FIT/types/mesg_count_type.d.ts +6 -0
  19. package/dist/FIT/types/mesg_num.d.ts +369 -0
  20. package/dist/FIT/types/message_index_type.d.ts +6591 -0
  21. package/dist/FIT/types/sport.d.ts +44 -44
  22. package/dist/FIT/types.d.ts +509 -0
  23. package/dist/GPX/base.d.ts +24 -0
  24. package/dist/GPX/converters.d.ts +44 -0
  25. package/dist/GPX/decoder.d.ts +66 -0
  26. package/dist/GPX/encoder.d.ts +109 -0
  27. package/dist/GPX/middleware.d.ts +9 -0
  28. package/dist/GPX/processor.d.ts +18 -0
  29. package/dist/GPX/types.d.ts +138 -0
  30. package/dist/TCX/base.d.ts +33 -0
  31. package/dist/TCX/converters.d.ts +33 -0
  32. package/dist/TCX/decoder.d.ts +54 -0
  33. package/dist/TCX/encoder.d.ts +116 -0
  34. package/dist/TCX/middleware.d.ts +12 -0
  35. package/dist/TCX/processor.d.ts +30 -0
  36. package/dist/TCX/types.d.ts +13 -0
  37. package/dist/TrackSwap.d.ts +4 -0
  38. package/dist/activity/base.d.ts +54 -0
  39. package/dist/activity/converters.d.ts +57 -0
  40. package/dist/activity/encoders.d.ts +34 -0
  41. package/dist/activity/metrics/MetricsCalculator.d.ts +30 -0
  42. package/dist/activity/processor.d.ts +31 -0
  43. package/dist/core/base.d.ts +46 -0
  44. package/dist/core/builtin-adapters.d.ts +1 -0
  45. package/dist/core/xml-utils.d.ts +24 -0
  46. package/dist/errors.d.ts +1 -1
  47. package/dist/fit-debug.d.ts +3 -0
  48. package/dist/index.cjs +5 -5
  49. package/dist/index.d.ts +2 -2
  50. package/dist/index.js +5 -5
  51. package/dist/types.d.ts +102 -1
  52. package/dist/util.d.ts +9 -0
  53. package/package.json +3 -2
package/README.md CHANGED
@@ -59,6 +59,22 @@ const converted = await trackSwap.transcode(input, {
59
59
  const course = await trackSwap.encodeCourse(input);
60
60
  ~~~
61
61
 
62
+ FIT 力量训练会将 `set` message 按原始顺序投影到
63
+ `activity.strengthSets`,保留动作、休息、次数、重量、时长和设备动作标签:
64
+
65
+ ~~~ts
66
+ const file = await trackSwap.decodeActivity(fitBuffer, { format: "fit" });
67
+ const [activity] = file.activities ?? [];
68
+
69
+ for (const phase of activity?.strengthSets ?? []) {
70
+ console.log(phase.setType, phase.repetitions, phase.weight, phase.exercises);
71
+ }
72
+ ~~~
73
+
74
+ 部分 FIT 活动包含 `record`、但没有实际 `lap` message。TrackSwap 会把这些
75
+ Record 放入一个合成 Lap,确保统一活动模型不丢样本;不会根据 Session 的
76
+ `numLaps` 编造 Lap 明细。
77
+
62
78
  ## FIT 健康数据
63
79
 
64
80
  FIT 文件只解码一次,活动与健康数据作为同一 `TrackDocument` 的 facet 返回。健康结构不直接绑定 Garmin Connect JSON,使用稳定的中间表示:
@@ -1,23 +1,47 @@
1
1
  import { IPlugin } from "../core/base.js";
2
2
  import { FITContext, FITDecoderMesgs, FITFileType } from "./types.js";
3
+ /**
4
+ * FIT message converter plugin interface
5
+ */
3
6
  export interface IFITMessageConverter extends IPlugin<FITContext> {
7
+ /** Supported message types */
4
8
  supportedMessageTypes: string[];
9
+ /** Convert messages */
5
10
  convertMessages(messages: any[], context: FITContext): any;
11
+ /** Whether it supports the message type */
6
12
  supports(messageType: string): boolean;
13
+ /** Priority */
7
14
  priority?: number;
8
15
  }
16
+ /**
17
+ * FIT data structuring plugin interface
18
+ */
9
19
  export interface IFITStructurePlugin extends IPlugin<FITContext> {
20
+ /** Structure data */
10
21
  structureData(messages: FITDecoderMesgs, context: FITContext): Partial<FITFileType>;
22
+ /** Priority */
11
23
  priority?: number;
12
24
  }
25
+ /**
26
+ * FIT middleware plugin interface
27
+ */
13
28
  export interface IFITMiddlewarePlugin extends IPlugin<FITContext> {
29
+ /** Priority */
14
30
  priority?: number;
31
+ /** Execute during parse stage */
15
32
  onParse?(buffer: Buffer, context: FITContext): Promise<Buffer> | Buffer;
33
+ /** Execute during message extraction stage */
16
34
  onExtractMessages?(messages: FITDecoderMesgs, context: FITContext): Promise<FITDecoderMesgs> | FITDecoderMesgs;
35
+ /** Execute during data structuring stage */
17
36
  onStructure?(result: Partial<FITFileType>, context: FITContext): Promise<Partial<FITFileType>> | Partial<FITFileType>;
37
+ /** Execute during completion stage */
18
38
  onComplete?(result: FITFileType, context: FITContext): Promise<FITFileType> | FITFileType;
39
+ /** Error handling */
19
40
  onError?(error: Error, context: FITContext): Promise<void> | void;
20
41
  }
42
+ /**
43
+ * FIT message converter base class
44
+ */
21
45
  export declare abstract class BaseFITMessageConverter implements IFITMessageConverter {
22
46
  abstract name: string;
23
47
  abstract supportedMessageTypes: string[];
@@ -36,6 +60,9 @@ export declare abstract class BaseFITMessageConverter implements IFITMessageConv
36
60
  records: any[];
37
61
  }>;
38
62
  }
63
+ /**
64
+ * FIT data structuring plugin base class
65
+ */
39
66
  export declare abstract class BaseFITStructurePlugin implements IFITStructurePlugin {
40
67
  abstract name: string;
41
68
  version: string;
@@ -45,6 +72,9 @@ export declare abstract class BaseFITStructurePlugin implements IFITStructurePlu
45
72
  destroy(context: FITContext): Promise<void>;
46
73
  validate(context: FITContext): Promise<boolean>;
47
74
  }
75
+ /**
76
+ * FIT middleware base class
77
+ */
48
78
  export declare abstract class BaseFITMiddleware implements IFITMiddlewarePlugin {
49
79
  abstract name: string;
50
80
  version: string;
@@ -1,5 +1,8 @@
1
1
  import { IFITMessageConverter, IFITStructurePlugin, IFITMiddlewarePlugin } from "./base.js";
2
2
  import { FITFileType } from "./types.js";
3
+ /**
4
+ * FIT decoder - follows GPX style plugin architecture
5
+ */
3
6
  export declare class FITDecoder {
4
7
  private converterPlugins;
5
8
  private registeredPlugins;
@@ -9,20 +12,65 @@ export declare class FITDecoder {
9
12
  private initialized;
10
13
  private defaultPluginsRegistered;
11
14
  constructor();
15
+ /**
16
+ * Initialize pipeline - register fixed core processors
17
+ */
12
18
  private initializePipeline;
19
+ /**
20
+ * Auto-register default plugins
21
+ */
13
22
  private registerDefaultPlugins;
23
+ /**
24
+ * Add converter for specific message type, sorted by priority
25
+ */
14
26
  private addConverterForMessageType;
27
+ /**
28
+ * Add structure plugin
29
+ */
15
30
  private addStructurePlugin;
31
+ /**
32
+ * Register message converter plugin
33
+ */
16
34
  registerMessageConverter(plugin: IFITMessageConverter): Promise<void>;
35
+ /**
36
+ * Register structure plugin
37
+ */
17
38
  registerStructurePlugin(plugin: IFITStructurePlugin): Promise<void>;
39
+ /**
40
+ * Register middleware plugin
41
+ */
18
42
  registerMiddleware(plugin: IFITMiddlewarePlugin): Promise<void>;
43
+ /**
44
+ * Get message type converter (returns highest priority one)
45
+ */
19
46
  getConverter(messageType: string): IFITMessageConverter | undefined;
47
+ /**
48
+ * Get all converters for specified message type (sorted by priority)
49
+ */
20
50
  getConverters(messageType: string): IFITMessageConverter[];
51
+ /**
52
+ * Remove plugin
53
+ */
21
54
  unregisterPlugin(pluginName: string): boolean;
55
+ /**
56
+ * Initialize decoder
57
+ */
22
58
  initialize(): Promise<void>;
59
+ /**
60
+ * Destroy decoder
61
+ */
23
62
  destroy(): Promise<void>;
63
+ /**
64
+ * Create decode context
65
+ */
24
66
  private createContext;
67
+ /**
68
+ * Execute middleware hook
69
+ */
25
70
  private executeMiddlewareHook;
71
+ /**
72
+ * Main parse method - fixed pipeline + flexible middleware
73
+ */
26
74
  parseByBuffer(buffer: Buffer, options?: {
27
75
  userData?: Record<string, any>;
28
76
  }): Promise<FITFileType | undefined>;
@@ -1,42 +1,137 @@
1
1
  import { FITFileType } from "./types.js";
2
+ /**
3
+ * FIT encoder options
4
+ */
2
5
  export interface FITEncoderOptions {
6
+ /** Whether to enable compact mode */
3
7
  compact?: boolean;
8
+ /** Default sport type */
4
9
  defaultActivity?: string;
10
+ /** Default sub sport type */
5
11
  defaultSubActivity?: string;
6
12
  }
13
+ /**
14
+ * FIT encoder - encodes FITFileType data to FIT binary format
15
+ */
7
16
  export declare class FITEncoder {
8
17
  private localNum;
9
18
  private mesgDefn;
10
19
  private messages;
11
20
  private options;
21
+ /**
22
+ * Constructor
23
+ * @param options Encoder options
24
+ */
12
25
  constructor(options?: FITEncoderOptions);
26
+ /**
27
+ * Encode FIT data to Buffer - Activity file type
28
+ */
13
29
  encode(fitData: FITFileType): Promise<Buffer>;
30
+ /**
31
+ * Encode FIT data to Buffer - Course file type
32
+ */
14
33
  encodeCourse(fitData: FITFileType): Promise<Buffer>;
34
+ /**
35
+ * Encode FIT data to Blob
36
+ */
15
37
  encodeToBlob(fitData: FITFileType, fileType?: "activity" | "course"): Promise<Blob>;
38
+ /**
39
+ * Reset encoder state
40
+ */
16
41
  private reset;
42
+ /**
43
+ * Build FIT file content
44
+ */
17
45
  private buildFITContent;
46
+ /**
47
+ * Build Activity file content
48
+ */
18
49
  private buildActivityContent;
50
+ /**
51
+ * Build Course file content
52
+ */
19
53
  private buildCourseContent;
54
+ /**
55
+ * Build file ID
56
+ */
20
57
  private buildFileId;
58
+ /**
59
+ * Build Session
60
+ * 构造 Session 信息
61
+ */
21
62
  private buildSession;
63
+ /**
64
+ * Build Laps
65
+ */
22
66
  private buildLaps;
67
+ /**
68
+ * Build single Lap
69
+ * 构造 Lap 信息
70
+ */
23
71
  private buildLap;
72
+ /**
73
+ * Build Records
74
+ */
24
75
  private buildRecords;
76
+ /**
77
+ * Build single Record
78
+ */
25
79
  private buildRecord;
80
+ /**
81
+ * Build Event
82
+ */
26
83
  private buildEvent;
84
+ /**
85
+ * Build Activity
86
+ */
27
87
  private buildActivity;
88
+ /**
89
+ * Build Course
90
+ */
28
91
  private buildCourse;
92
+ /**
93
+ * Build Events from Laps
94
+ */
29
95
  private buildEventsFromLaps;
96
+ /**
97
+ * Build Activity from Session
98
+ */
30
99
  private buildActivityFromSession;
100
+ /**
101
+ * Parse timestamp
102
+ */
31
103
  private parseTimestamp;
32
- private convertToSemicircles;
104
+ /**
105
+ * Validate required fields
106
+ */
33
107
  private validateRequiredFields;
108
+ /**
109
+ * Write message
110
+ */
34
111
  private writeMesg;
112
+ /**
113
+ * Get Blob object
114
+ */
35
115
  get blob(): Blob;
116
+ /**
117
+ * Get data length
118
+ */
36
119
  get dataLen(): number;
120
+ /**
121
+ * Get data CRC
122
+ */
37
123
  get dataCrc(): number;
124
+ /**
125
+ * Get file header
126
+ */
38
127
  get header(): ArrayBuffer;
128
+ /**
129
+ * Get file trailer
130
+ */
39
131
  get trailer(): ArrayBuffer;
132
+ /**
133
+ * @deprecated Use encode() instead
134
+ */
40
135
  encoder(fitData: FITFileType): Promise<Buffer>;
41
136
  }
42
137
  declare const _default: {
@@ -1,37 +1,82 @@
1
1
  import { CoursePoint as CoursePointType } from "./types/course_point.js";
2
2
  import { MesgNum } from "./types/mesg_num.js";
3
3
  import { Sport, SubSport } from "./types/message_index_type.js";
4
+ /**
5
+ * 路线轨迹点(用于生成 record 消息)。
6
+ * 默认输入经纬度为「度」,内部会转换为 FIT semicircles。
7
+ */
4
8
  export interface CourseRecordPoint {
9
+ /** 轨迹点时间(Date 或 FIT 时间戳/Unix 时间戳)。 */
5
10
  timestamp: Date | number;
11
+ /** 纬度(默认按度处理,范围通常 -90~90)。 */
6
12
  latitude: number;
13
+ /** 经度(默认按度处理,范围通常 -180~180)。 */
7
14
  longitude: number;
15
+ /** 距离(单位 m,可选)。 */
8
16
  distance?: number;
17
+ /** 海拔(单位 m,可选)。 */
9
18
  altitude?: number;
19
+ /** 速度(单位 m/s,可选)。 */
10
20
  speed?: number;
11
21
  }
22
+ /**
23
+ * 路线拐点(用于生成 course_point 消息)。
24
+ */
12
25
  export interface CourseCuePoint {
26
+ /** 拐点时间。 */
13
27
  timestamp: Date | number;
28
+ /** 拐点纬度(默认按度处理)。 */
14
29
  latitude: number;
30
+ /** 拐点经度(默认按度处理)。 */
15
31
  longitude: number;
32
+ /** 拐点类型(左转/右转/补给点等),默认 GENERIC。 */
16
33
  type?: CoursePointType;
34
+ /** 拐点名称。 */
17
35
  name?: string;
36
+ /** 拐点对应路线距离(单位 m,可选)。 */
18
37
  distance?: number;
38
+ /** 是否收藏点。 */
19
39
  favorite?: boolean;
20
40
  }
41
+ /**
42
+ * 课程计划(业务模型)。
43
+ */
21
44
  export interface CoursePlan {
45
+ /** 路线名称。 */
22
46
  name: string;
47
+ /** 运动类型,默认 CYCLING。 */
23
48
  sport?: Sport;
49
+ /** 子运动类型,默认 GENERIC。 */
24
50
  subSport?: SubSport;
51
+ /** 路线轨迹点(至少 1 个)。 */
25
52
  records: CourseRecordPoint[];
53
+ /** 路线拐点列表(可选)。 */
26
54
  coursePoints?: CourseCuePoint[];
55
+ /** file_id.manufacturer,可选,默认 development。 */
27
56
  manufacturer?: string | number;
57
+ /** file_id.product,可选,默认 1。 */
28
58
  product?: number;
59
+ /** file_id.serialNumber,可选,默认 1。 */
29
60
  serialNumber?: number;
61
+ /** file_id.timeCreated,可选,默认首个 record.timestamp。 */
30
62
  timeCreated?: Date | number;
31
63
  }
64
+ /**
65
+ * 中间消息结构,统一喂给 Garmin Encoder.onMesg()。
66
+ */
32
67
  export interface FitMesg {
68
+ /** FIT 消息号。 */
33
69
  mesgNum: MesgNum;
70
+ /** 消息体。 */
34
71
  data: Record<string, unknown>;
35
72
  }
73
+ /**
74
+ * 课程映射:
75
+ * FILE_ID(course) + COURSE + LAP + EVENT(start/stop_all) + RECORD[] + COURSE_POINT[]。
76
+ */
36
77
  export declare function buildCourseMesgs(plan: CoursePlan): FitMesg[];
78
+ /**
79
+ * 编码入口:
80
+ * 先映射消息,再写入 Garmin Encoder,最后 close() 输出 FIT 字节。
81
+ */
37
82
  export declare function encodeCourse(plan: CoursePlan): Uint8Array;
@@ -1,5 +1,9 @@
1
1
  import { MesgNum } from "./types/mesg_num.js";
2
2
  import { Intensity, Sport, SubSport } from "./types/message_index_type.js";
3
+ /**
4
+ * 业务层的「步骤时长」抽象。
5
+ * 注意:这里只表达业务含义,具体如何编码到 FIT 在 mapDuration() 里处理。
6
+ */
3
7
  export type WorkoutDuration = {
4
8
  kind: "time";
5
9
  seconds: number;
@@ -12,30 +16,71 @@ export type WorkoutDuration = {
12
16
  } | {
13
17
  kind: "open";
14
18
  };
19
+ /**
20
+ * 业务层的「步骤目标」抽象。
21
+ * V1 仅支持 open(不设目标区间)。
22
+ */
15
23
  export type WorkoutTarget = {
16
24
  kind: "open";
17
25
  };
26
+ /**
27
+ * 单个训练步骤(业务模型)。
28
+ */
18
29
  export interface WorkoutStep {
30
+ /** 步骤名称(如 Warmup / Interval)。 */
19
31
  name?: string;
32
+ /** 步骤强度,默认 ACTIVE。 */
20
33
  intensity?: Intensity;
34
+ /** 步骤时长/结束条件(必填)。 */
21
35
  duration: WorkoutDuration;
36
+ /** 步骤目标,V1 默认 open。 */
22
37
  target?: WorkoutTarget;
23
38
  }
39
+ /**
40
+ * 一个完整的训练计划(业务模型)。
41
+ * - 前 5 个字段是核心业务字段
42
+ * - 后面的 manufacturer/product... 是 FIT file_id 附加字段,可选
43
+ */
24
44
  export interface WorkoutPlan {
45
+ /** 训练名称,映射到 FIT 的 wktName。 */
25
46
  name: string;
47
+ /** 训练描述,映射到 FIT 的 wktDescription。 */
26
48
  description?: string;
49
+ /** 运动类型(跑步/骑行/游泳等)。 */
27
50
  sport: Sport;
51
+ /** 子运动类型,默认 GENERIC。 */
28
52
  subSport?: SubSport;
53
+ /** 训练步骤列表,至少 1 条。 */
29
54
  steps: WorkoutStep[];
55
+ /** file_id.manufacturer,可选,不传则用 development。 */
30
56
  manufacturer?: string | number;
57
+ /** file_id.product,可选,默认 1。 */
31
58
  product?: number;
59
+ /** file_id.serialNumber,可选,默认 1。 */
32
60
  serialNumber?: number;
61
+ /** workout.messageIndex,可选,默认 0。 */
33
62
  messageIndex?: number;
63
+ /** file_id.timeCreated,可选,默认 new Date()。 */
34
64
  timeCreated?: Date | number;
35
65
  }
66
+ /**
67
+ * 中间层消息结构:后续统一喂给 Garmin Encoder.onMesg()。
68
+ */
36
69
  export interface FitMesg {
70
+ /** FIT 消息号,例如 FILE_ID/WORKOUT/WORKOUT_STEP。 */
37
71
  mesgNum: MesgNum;
72
+ /** Garmin Encoder 需要的消息体数据。 */
38
73
  data: Record<string, unknown>;
39
74
  }
75
+ /**
76
+ * 核心映射函数:
77
+ * WorkoutPlan -> FILE_ID + WORKOUT + WORKOUT_STEP[]。
78
+ */
40
79
  export declare function buildWorkoutMesgs(plan: WorkoutPlan): FitMesg[];
80
+ /**
81
+ * 编码入口:
82
+ * 1) 先构造中间消息数组
83
+ * 2) 逐条写入 Garmin Encoder
84
+ * 3) close() 得到 FIT 二进制(Uint8Array)
85
+ */
41
86
  export declare function encodeWorkout(plan: WorkoutPlan): Uint8Array;