@webpacked-timeline/core 1.0.0-beta.1

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 (77) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/LICENSE +21 -0
  3. package/README.md +162 -0
  4. package/dist/chunk-27XCNVPR.js +5969 -0
  5. package/dist/chunk-6PDBJDHM.js +2263 -0
  6. package/dist/chunk-BWPS6NQT.js +7465 -0
  7. package/dist/chunk-FBOYSUYV.js +1280 -0
  8. package/dist/chunk-FR632TZX.js +1870 -0
  9. package/dist/chunk-HW4Z7YLJ.js +1242 -0
  10. package/dist/chunk-HWW62IFH.js +5424 -0
  11. package/dist/chunk-I2GZXRH4.js +4790 -0
  12. package/dist/chunk-JQZE3OK4.js +1255 -0
  13. package/dist/chunk-KF7JNK2F.js +1864 -0
  14. package/dist/chunk-KR3P2DYK.js +5655 -0
  15. package/dist/chunk-MO5DSFSW.js +2214 -0
  16. package/dist/chunk-MQAW33RJ.js +5530 -0
  17. package/dist/chunk-N4WUWZZX.js +2833 -0
  18. package/dist/chunk-NRJV7I4C.js +1331 -0
  19. package/dist/chunk-NXG52532.js +2230 -0
  20. package/dist/chunk-PVXF67CN.js +1278 -0
  21. package/dist/chunk-QSB6DHIF.js +5429 -0
  22. package/dist/chunk-QYWJT7HR.js +5837 -0
  23. package/dist/chunk-SWBRCMW7.js +7466 -0
  24. package/dist/chunk-TAT3ULSV.js +2214 -0
  25. package/dist/chunk-TTDP5JUM.js +2228 -0
  26. package/dist/chunk-UAGP4VPG.js +1739 -0
  27. package/dist/chunk-WIG6SY7A.js +1183 -0
  28. package/dist/chunk-YJ2K5N2R.js +6187 -0
  29. package/dist/index-3Lr_vKBd.d.cts +2810 -0
  30. package/dist/index-3Lr_vKBd.d.ts +2810 -0
  31. package/dist/index-7IPJn1yM.d.cts +1146 -0
  32. package/dist/index-7IPJn1yM.d.ts +1146 -0
  33. package/dist/index-B0xOv0V0.d.cts +3259 -0
  34. package/dist/index-B0xOv0V0.d.ts +3259 -0
  35. package/dist/index-B2m3zwg7.d.cts +1381 -0
  36. package/dist/index-B2m3zwg7.d.ts +1381 -0
  37. package/dist/index-B3sUrU_X.d.cts +1249 -0
  38. package/dist/index-B3sUrU_X.d.ts +1249 -0
  39. package/dist/index-B6wla7ZJ.d.cts +2751 -0
  40. package/dist/index-B6wla7ZJ.d.ts +2751 -0
  41. package/dist/index-BIv8RWWT.d.cts +1574 -0
  42. package/dist/index-BIv8RWWT.d.ts +1574 -0
  43. package/dist/index-BJv6hDHL.d.cts +3255 -0
  44. package/dist/index-BJv6hDHL.d.ts +3255 -0
  45. package/dist/index-BUCimS2e.d.cts +1393 -0
  46. package/dist/index-BUCimS2e.d.ts +1393 -0
  47. package/dist/index-Bw_nvNcG.d.cts +1275 -0
  48. package/dist/index-Bw_nvNcG.d.ts +1275 -0
  49. package/dist/index-ByG0gOtd.d.cts +1167 -0
  50. package/dist/index-ByG0gOtd.d.ts +1167 -0
  51. package/dist/index-CDGd2XXv.d.cts +2492 -0
  52. package/dist/index-CDGd2XXv.d.ts +2492 -0
  53. package/dist/index-CznAVeJ6.d.cts +1145 -0
  54. package/dist/index-CznAVeJ6.d.ts +1145 -0
  55. package/dist/index-DQD9IMh7.d.cts +2534 -0
  56. package/dist/index-DQD9IMh7.d.ts +2534 -0
  57. package/dist/index-Dl3qtJEI.d.cts +2178 -0
  58. package/dist/index-Dl3qtJEI.d.ts +2178 -0
  59. package/dist/index-DnE2A-Nz.d.cts +2603 -0
  60. package/dist/index-DnE2A-Nz.d.ts +2603 -0
  61. package/dist/index-DrOA6QmW.d.cts +2492 -0
  62. package/dist/index-DrOA6QmW.d.ts +2492 -0
  63. package/dist/index-Vpa3rPEM.d.cts +1402 -0
  64. package/dist/index-Vpa3rPEM.d.ts +1402 -0
  65. package/dist/index-jP6BomSd.d.cts +2640 -0
  66. package/dist/index-jP6BomSd.d.ts +2640 -0
  67. package/dist/index-wiGRwVyY.d.cts +3259 -0
  68. package/dist/index-wiGRwVyY.d.ts +3259 -0
  69. package/dist/index.cjs +7386 -0
  70. package/dist/index.d.cts +1 -0
  71. package/dist/index.d.ts +1 -0
  72. package/dist/index.js +263 -0
  73. package/dist/internal.cjs +7721 -0
  74. package/dist/internal.d.cts +704 -0
  75. package/dist/internal.d.ts +704 -0
  76. package/dist/internal.js +405 -0
  77. package/package.json +58 -0
@@ -0,0 +1,1167 @@
1
+ /**
2
+ * FRAME-BASED TIME REPRESENTATION
3
+ *
4
+ * This file defines the foundational time system for the entire timeline engine.
5
+ *
6
+ * WHY FRAMES INSTEAD OF MILLISECONDS?
7
+ * - Frames are discrete, integer values (no floating-point drift)
8
+ * - Frames are deterministic (same input always produces same output)
9
+ * - Frames match how video editors actually work (frame-accurate editing)
10
+ * - Frames prevent rounding errors that accumulate over time
11
+ *
12
+ * CRITICAL RULE:
13
+ * All time values in the timeline state MUST be stored as frames.
14
+ * Never store time as seconds or milliseconds in state.
15
+ *
16
+ * USAGE:
17
+ * ```typescript
18
+ * const fps = 30 as FrameRate;
19
+ * const frame = 150 as Frame; // 5 seconds at 30fps
20
+ * ```
21
+ */
22
+ /**
23
+ * Frame - A discrete point in time, measured in frames
24
+ *
25
+ * This is a "branded type" - it's just a number at runtime,
26
+ * but TypeScript treats it as a distinct type to prevent mixing
27
+ * frames with regular numbers accidentally.
28
+ *
29
+ * INVARIANT: Frame values must be non-negative integers
30
+ */
31
+ type Frame = number & {
32
+ readonly __brand: 'Frame';
33
+ };
34
+ /**
35
+ * FrameRate - Frames per second (FPS)
36
+ *
37
+ * Common values: 24, 25, 30, 60
38
+ *
39
+ * INVARIANT: FrameRate must be a positive number
40
+ */
41
+ type FrameRate = number & {
42
+ readonly __brand: 'FrameRate';
43
+ };
44
+ /**
45
+ * Create a Frame value from a number
46
+ *
47
+ * This function validates and rounds the input to ensure it's a valid frame number.
48
+ *
49
+ * @param value - The frame number (will be rounded to nearest integer)
50
+ * @returns A valid Frame value
51
+ * @throws Error if value is negative
52
+ */
53
+ declare function frame(value: number): Frame;
54
+ /**
55
+ * Create a FrameRate value from a number
56
+ *
57
+ * @param value - The frames per second
58
+ * @returns A valid FrameRate value
59
+ * @throws Error if value is not positive
60
+ */
61
+ declare function frameRate(value: number): FrameRate;
62
+ /**
63
+ * Check if a value is a valid frame number
64
+ *
65
+ * @param value - The value to check
66
+ * @returns true if the value is a non-negative integer
67
+ */
68
+ declare function isValidFrame(value: number): boolean;
69
+ /**
70
+ * Check if a value is a valid frame rate
71
+ *
72
+ * @param value - The value to check
73
+ * @returns true if the value is positive
74
+ */
75
+ declare function isValidFrameRate(value: number): boolean;
76
+
77
+ /**
78
+ * CLIP MODEL
79
+ *
80
+ * A Clip represents a time-bound reference to an Asset on a Track.
81
+ *
82
+ * WHAT IS A CLIP?
83
+ * - A piece of media placed at a specific time on the timeline
84
+ * - References an Asset (the source media)
85
+ * - Defines WHEN it appears (timeline bounds)
86
+ * - Defines WHAT portion of the asset to play (media bounds)
87
+ *
88
+ * KEY CONCEPTS:
89
+ *
90
+ * 1. TIMELINE BOUNDS:
91
+ * - timelineStart: When the clip appears on the timeline
92
+ * - timelineEnd: When the clip ends on the timeline
93
+ * - Duration = timelineEnd - timelineStart
94
+ *
95
+ * 2. MEDIA BOUNDS:
96
+ * - mediaIn: Start frame in the source asset
97
+ * - mediaOut: End frame in the source asset
98
+ * - Defines which portion of the asset to play
99
+ *
100
+ * EXAMPLE:
101
+ * ```typescript
102
+ * // A 10-second video asset
103
+ * const asset = { id: 'asset_1', duration: frame(300) }; // 300 frames at 30fps
104
+ *
105
+ * // A clip that shows 3 seconds of the video (frames 60-150)
106
+ * // starting at 5 seconds on the timeline
107
+ * const clip: Clip = {
108
+ * id: 'clip_1',
109
+ * assetId: 'asset_1',
110
+ * trackId: 'track_1',
111
+ * timelineStart: frame(150), // 5 seconds * 30fps
112
+ * timelineEnd: frame(240), // 8 seconds * 30fps
113
+ * mediaIn: frame(60), // Start at 2 seconds into the asset
114
+ * mediaOut: frame(150), // End at 5 seconds into the asset
115
+ * };
116
+ * ```
117
+ *
118
+ * INVARIANTS (Phase 1 - No Speed Remapping):
119
+ * - timelineEnd > timelineStart
120
+ * - mediaOut > mediaIn
121
+ * - timelineEnd - timelineStart === mediaOut - mediaIn (same duration)
122
+ * - mediaOut <= asset.duration (can't exceed asset bounds)
123
+ * - All frame values must be non-negative
124
+ *
125
+ * FUTURE: When speed remapping is added, the duration constraint will change.
126
+ */
127
+
128
+ /**
129
+ * Clip - A time-bound reference to an Asset
130
+ */
131
+ interface Clip {
132
+ /** Unique identifier */
133
+ id: string;
134
+ /** Reference to the asset this clip uses */
135
+ assetId: string;
136
+ /** Reference to the track this clip belongs to */
137
+ trackId: string;
138
+ /** Start frame on the timeline */
139
+ timelineStart: Frame;
140
+ /** End frame on the timeline */
141
+ timelineEnd: Frame;
142
+ /** Start frame in the source asset */
143
+ mediaIn: Frame;
144
+ /** End frame in the source asset */
145
+ mediaOut: Frame;
146
+ /** Optional label for the clip */
147
+ label?: string;
148
+ /** Optional metadata for custom use cases */
149
+ metadata?: Record<string, unknown>;
150
+ /** Optional link group ID - clips in same group move/delete together */
151
+ linkGroupId?: string;
152
+ /** Optional group ID - for visual organization */
153
+ groupId?: string;
154
+ }
155
+ /**
156
+ * Create a new clip
157
+ *
158
+ * @param params - Clip parameters
159
+ * @returns A new Clip object
160
+ */
161
+ declare function createClip(params: {
162
+ id: string;
163
+ assetId: string;
164
+ trackId: string;
165
+ timelineStart: Frame;
166
+ timelineEnd: Frame;
167
+ mediaIn: Frame;
168
+ mediaOut: Frame;
169
+ label?: string;
170
+ metadata?: Record<string, unknown>;
171
+ }): Clip;
172
+ /**
173
+ * Get the timeline duration of a clip
174
+ *
175
+ * @param clip - The clip
176
+ * @returns Duration in frames
177
+ */
178
+ declare function getClipDuration(clip: Clip): Frame;
179
+ /**
180
+ * Get the media duration of a clip
181
+ *
182
+ * @param clip - The clip
183
+ * @returns Duration in frames
184
+ */
185
+ declare function getClipMediaDuration(clip: Clip): Frame;
186
+ /**
187
+ * Check if a clip contains a specific frame on the timeline
188
+ *
189
+ * @param clip - The clip
190
+ * @param frame - The frame to check
191
+ * @returns true if the frame is within the clip's timeline bounds
192
+ */
193
+ declare function clipContainsFrame(clip: Clip, frame: Frame): boolean;
194
+ /**
195
+ * Check if two clips overlap on the timeline
196
+ *
197
+ * @param clip1 - First clip
198
+ * @param clip2 - Second clip
199
+ * @returns true if the clips overlap
200
+ */
201
+ declare function clipsOverlap(clip1: Clip, clip2: Clip): boolean;
202
+
203
+ /**
204
+ * TRACK MODEL
205
+ *
206
+ * A Track is a horizontal container for Clips.
207
+ *
208
+ * WHAT IS A TRACK?
209
+ * - A layer that holds clips (like a layer in Photoshop)
210
+ * - Provides organization and isolation for clips
211
+ * - Has a type (video or audio) that clips must match
212
+ *
213
+ * WHY TRACKS?
214
+ * - Organize clips into layers
215
+ * - Enable stacking and compositing (track order matters)
216
+ * - Provide track-level controls (mute, lock)
217
+ * - Isolate editing operations
218
+ *
219
+ * TRACK ORDER:
220
+ * Tracks are rendered bottom-to-top:
221
+ * - tracks[0] = bottom layer (rendered first)
222
+ * - tracks[n] = top layer (rendered last, appears on top)
223
+ *
224
+ * EXAMPLE:
225
+ * ```typescript
226
+ * const track: Track = {
227
+ * id: 'track_1',
228
+ * name: 'Video Track 1',
229
+ * type: 'video',
230
+ * clips: [],
231
+ * locked: false,
232
+ * muted: false,
233
+ * };
234
+ * ```
235
+ *
236
+ * INVARIANTS:
237
+ * - Clips on a track must not overlap
238
+ * - All clips must match the track type
239
+ * - Clips array should be sorted by timelineStart (for performance)
240
+ */
241
+
242
+ /**
243
+ * TrackType - The kind of content this track holds
244
+ */
245
+ type TrackType = 'video' | 'audio';
246
+ /**
247
+ * Track - A container for clips
248
+ */
249
+ interface Track {
250
+ /** Unique identifier */
251
+ id: string;
252
+ /** Human-readable name */
253
+ name: string;
254
+ /** Type of content this track holds */
255
+ type: TrackType;
256
+ /** Clips on this track (should be sorted by timelineStart) */
257
+ clips: Clip[];
258
+ /** Whether the track is locked (prevents editing) */
259
+ locked: boolean;
260
+ /** Whether the track is muted (affects playback) */
261
+ muted: boolean;
262
+ /** Whether the track is soloed (mutes all other tracks) */
263
+ solo: boolean;
264
+ /** Track height in pixels (for UI rendering) */
265
+ height: number;
266
+ /** Optional metadata for custom use cases */
267
+ metadata?: Record<string, unknown>;
268
+ }
269
+ /**
270
+ * Create a new track
271
+ *
272
+ * @param params - Track parameters
273
+ * @returns A new Track object
274
+ */
275
+ declare function createTrack(params: {
276
+ id: string;
277
+ name: string;
278
+ type: TrackType;
279
+ clips?: Clip[];
280
+ locked?: boolean;
281
+ muted?: boolean;
282
+ solo?: boolean;
283
+ height?: number;
284
+ metadata?: Record<string, unknown>;
285
+ }): Track;
286
+ /**
287
+ * Sort clips on a track by timeline start frame
288
+ *
289
+ * This is useful for maintaining clip order and improving
290
+ * query performance.
291
+ *
292
+ * @param track - The track to sort
293
+ * @returns A new track with sorted clips
294
+ */
295
+ declare function sortTrackClips(track: Track): Track;
296
+
297
+ /**
298
+ * TIMELINE MODEL
299
+ *
300
+ * The Timeline is the root container for the entire editing project.
301
+ *
302
+ * WHAT IS A TIMELINE?
303
+ * - The top-level data structure for a project
304
+ * - Contains all tracks (which contain all clips)
305
+ * - Defines the frame rate (FPS) for the entire project
306
+ * - Defines the total duration of the project
307
+ *
308
+ * WHY A TIMELINE?
309
+ * - Single source of truth for all timeline data
310
+ * - Defines the temporal bounds of the project
311
+ * - Provides a consistent frame rate for all time calculations
312
+ *
313
+ * EXAMPLE:
314
+ * ```typescript
315
+ * const timeline: Timeline = {
316
+ * id: 'timeline_1',
317
+ * name: 'My Project',
318
+ * fps: frameRate(30),
319
+ * duration: frame(9000), // 5 minutes at 30fps
320
+ * tracks: [],
321
+ * };
322
+ * ```
323
+ *
324
+ * INVARIANTS:
325
+ * - FPS is immutable after timeline creation
326
+ * - Duration must be positive
327
+ * - All tracks must have unique IDs
328
+ */
329
+
330
+ /**
331
+ * Timeline - The root container for a timeline project
332
+ */
333
+ interface Timeline {
334
+ /** Unique identifier */
335
+ id: string;
336
+ /** Human-readable name */
337
+ name: string;
338
+ /** Frames per second (immutable after creation) */
339
+ fps: FrameRate;
340
+ /** Total duration of the timeline in frames */
341
+ duration: Frame;
342
+ /** Tracks in the timeline (ordered bottom-to-top) */
343
+ tracks: Track[];
344
+ /** Optional metadata for custom use cases */
345
+ metadata?: Record<string, unknown>;
346
+ }
347
+ /**
348
+ * Create a new timeline
349
+ *
350
+ * @param params - Timeline parameters
351
+ * @returns A new Timeline object
352
+ */
353
+ declare function createTimeline(params: {
354
+ id: string;
355
+ name: string;
356
+ fps: FrameRate;
357
+ duration: Frame;
358
+ tracks?: Track[];
359
+ metadata?: Record<string, unknown>;
360
+ }): Timeline;
361
+
362
+ /**
363
+ * ASSET MODEL
364
+ *
365
+ * An Asset represents immutable metadata about a media file.
366
+ *
367
+ * WHAT IS AN ASSET?
368
+ * - A reference to source media (video, audio, image)
369
+ * - Contains metadata like duration and type
370
+ * - Immutable once registered (duration never changes)
371
+ *
372
+ * WHY SEPARATE ASSETS FROM CLIPS?
373
+ * - Multiple clips can reference the same asset
374
+ * - Asset duration is the source of truth
375
+ * - Clips can trim/slice the asset without modifying it
376
+ *
377
+ * EXAMPLE:
378
+ * ```typescript
379
+ * const asset: Asset = {
380
+ * id: 'asset_1',
381
+ * type: 'video',
382
+ * duration: frame(3600), // 2 minutes at 30fps
383
+ * sourceUrl: 'https://example.com/video.mp4',
384
+ * };
385
+ * ```
386
+ *
387
+ * INVARIANTS:
388
+ * - Asset ID must be unique
389
+ * - Duration must be positive
390
+ * - Duration is immutable after registration
391
+ */
392
+
393
+ /**
394
+ * AssetType - The kind of media this asset represents
395
+ */
396
+ type AssetType = 'video' | 'audio' | 'image';
397
+ /**
398
+ * Asset - Immutable metadata about a media file
399
+ */
400
+ interface Asset {
401
+ /** Unique identifier */
402
+ id: string;
403
+ /** Type of media */
404
+ type: AssetType;
405
+ /** Total duration of the asset in frames */
406
+ duration: Frame;
407
+ /** Source URL or file path */
408
+ sourceUrl: string;
409
+ /** Optional metadata for custom use cases */
410
+ metadata?: Record<string, unknown>;
411
+ }
412
+ /**
413
+ * Create a new asset
414
+ *
415
+ * @param params - Asset parameters
416
+ * @returns A new Asset object
417
+ */
418
+ declare function createAsset(params: {
419
+ id: string;
420
+ type: AssetType;
421
+ duration: Frame;
422
+ sourceUrl: string;
423
+ metadata?: Record<string, unknown>;
424
+ }): Asset;
425
+
426
+ /**
427
+ * LINKING TYPES
428
+ *
429
+ * Link groups synchronize edits across multiple clips.
430
+ * When one clip in a link group is moved/deleted, all linked clips are affected.
431
+ */
432
+ /**
433
+ * Link group - relates clips for synchronized editing
434
+ */
435
+ interface LinkGroup {
436
+ id: string;
437
+ clipIds: string[];
438
+ createdAt?: number;
439
+ }
440
+
441
+ /**
442
+ * GROUPING TYPES
443
+ *
444
+ * Groups organize clips visually without affecting edit behavior.
445
+ * Unlike link groups, groups are for organization only.
446
+ */
447
+ /**
448
+ * Group - organizes clips visually
449
+ */
450
+ interface Group {
451
+ id: string;
452
+ name: string;
453
+ clipIds: string[];
454
+ parentGroupId?: string;
455
+ color?: string;
456
+ collapsed?: boolean;
457
+ }
458
+
459
+ /**
460
+ * MARKER TYPES
461
+ *
462
+ * Pure metadata for timeline navigation and organization.
463
+ * Markers do not affect clip timing or validation.
464
+ */
465
+
466
+ /**
467
+ * Timeline marker - marks a specific frame on the timeline
468
+ */
469
+ interface TimelineMarker {
470
+ id: string;
471
+ type: 'timeline';
472
+ frame: Frame;
473
+ label: string;
474
+ color?: string;
475
+ }
476
+ /**
477
+ * Clip marker - marks a frame relative to clip start
478
+ */
479
+ interface ClipMarker {
480
+ id: string;
481
+ type: 'clip';
482
+ clipId: string;
483
+ frame: Frame;
484
+ label: string;
485
+ color?: string;
486
+ }
487
+ /**
488
+ * Region marker - marks a frame range
489
+ */
490
+ interface RegionMarker {
491
+ id: string;
492
+ type: 'region';
493
+ startFrame: Frame;
494
+ endFrame: Frame;
495
+ label: string;
496
+ color?: string;
497
+ }
498
+ /**
499
+ * Work area - defines the active editing region
500
+ */
501
+ interface WorkArea {
502
+ startFrame: Frame;
503
+ endFrame: Frame;
504
+ }
505
+ /**
506
+ * Union type for all marker types
507
+ */
508
+ type Marker = TimelineMarker | ClipMarker | RegionMarker;
509
+
510
+ /**
511
+ * TIMELINE STATE
512
+ *
513
+ * This is the complete state shape for the timeline engine.
514
+ *
515
+ * WHAT IS TIMELINE STATE?
516
+ * - The root state object that contains everything
517
+ * - Timeline (tracks and clips)
518
+ * - Assets (media metadata)
519
+ *
520
+ * WHY SEPARATE STATE?
521
+ * - Clear separation between timeline structure and asset registry
522
+ * - Assets can be shared across multiple clips
523
+ * - State is the single source of truth
524
+ *
525
+ * EXAMPLE:
526
+ * ```typescript
527
+ * const state: TimelineState = {
528
+ * timeline: {
529
+ * id: 'timeline_1',
530
+ * name: 'My Project',
531
+ * fps: frameRate(30),
532
+ * duration: frame(9000),
533
+ * tracks: [],
534
+ * },
535
+ * assets: new Map([
536
+ * ['asset_1', { id: 'asset_1', type: 'video', duration: frame(3600), sourceUrl: '...' }],
537
+ * ]),
538
+ * };
539
+ * ```
540
+ *
541
+ * IMMUTABILITY:
542
+ * All operations on TimelineState must return a NEW state object.
543
+ * Never mutate the existing state.
544
+ */
545
+
546
+ /**
547
+ * TimelineState - The complete state for the timeline engine
548
+ *
549
+ * Phase 2 additions:
550
+ * - linkGroups: Synchronized editing groups
551
+ * - groups: Visual organization groups
552
+ * - markers: Timeline/clip/region markers
553
+ * - workArea: Active editing region
554
+ */
555
+ interface TimelineState {
556
+ /** The timeline (tracks and clips) */
557
+ timeline: Timeline;
558
+ /** Asset registry (media metadata) */
559
+ assets: Map<string, Asset>;
560
+ /** Link groups for synchronized editing */
561
+ linkGroups: Map<string, LinkGroup>;
562
+ /** Groups for visual organization */
563
+ groups: Map<string, Group>;
564
+ /** Markers for navigation and annotation */
565
+ markers: {
566
+ timeline: TimelineMarker[];
567
+ clips: ClipMarker[];
568
+ regions: RegionMarker[];
569
+ };
570
+ /** Optional work area definition */
571
+ workArea?: WorkArea;
572
+ }
573
+ /**
574
+ * Create a new timeline state
575
+ *
576
+ * @param params - State parameters
577
+ * @returns A new TimelineState object
578
+ */
579
+ declare function createTimelineState(params: {
580
+ timeline: Timeline;
581
+ assets?: Map<string, Asset>;
582
+ linkGroups?: Map<string, LinkGroup>;
583
+ groups?: Map<string, Group>;
584
+ markers?: {
585
+ timeline: TimelineMarker[];
586
+ clips: ClipMarker[];
587
+ regions: RegionMarker[];
588
+ };
589
+ workArea?: WorkArea;
590
+ }): TimelineState;
591
+
592
+ /**
593
+ * HISTORY ENGINE
594
+ *
595
+ * Snapshot-based undo/redo system for timeline state.
596
+ *
597
+ * WHAT IS THE HISTORY ENGINE?
598
+ * - Stores immutable snapshots of timeline state
599
+ * - Provides undo/redo functionality
600
+ * - Prevents state corruption
601
+ *
602
+ * HOW IT WORKS:
603
+ * - past: Array of previous states
604
+ * - present: Current state
605
+ * - future: Array of states that can be redone
606
+ *
607
+ * WHY SNAPSHOTS?
608
+ * - Simple and reliable (no complex diffing)
609
+ * - Guaranteed to restore exact state
610
+ * - No risk of partial corruption
611
+ * - Easy to implement and test
612
+ *
613
+ * USAGE:
614
+ * ```typescript
615
+ * let history = createHistory(initialState);
616
+ * history = pushHistory(history, newState);
617
+ * history = undo(history);
618
+ * history = redo(history);
619
+ * ```
620
+ *
621
+ * ALL FUNCTIONS ARE PURE:
622
+ * - Take history as input
623
+ * - Return new history as output
624
+ * - Never mutate input
625
+ */
626
+
627
+ /**
628
+ * HistoryState - The history container
629
+ *
630
+ * Contains:
631
+ * - past: Array of previous states (oldest first)
632
+ * - present: Current state
633
+ * - future: Array of states that can be redone (newest first)
634
+ * - limit: Maximum number of past states to keep
635
+ */
636
+ interface HistoryState {
637
+ past: TimelineState[];
638
+ present: TimelineState;
639
+ future: TimelineState[];
640
+ limit: number;
641
+ }
642
+
643
+ /**
644
+ * VALIDATION SYSTEM TYPES
645
+ *
646
+ * This file defines the structure for validation results throughout the engine.
647
+ *
648
+ * WHY STRUCTURED VALIDATION?
649
+ * - Operations can fail gracefully (no exceptions thrown)
650
+ * - Errors are descriptive and actionable
651
+ * - Multiple errors can be collected and reported together
652
+ * - Validation logic is separated from business logic
653
+ *
654
+ * USAGE:
655
+ * ```typescript
656
+ * const result = validateClip(state, clip);
657
+ * if (!result.valid) {
658
+ * console.error('Validation failed:', result.errors);
659
+ * return;
660
+ * }
661
+ * ```
662
+ */
663
+ /**
664
+ * ValidationError - A single validation error
665
+ *
666
+ * Contains:
667
+ * - code: Machine-readable error code (e.g., "CLIP_OVERLAP")
668
+ * - message: Human-readable error message
669
+ * - context: Optional additional data about the error
670
+ */
671
+ interface ValidationError {
672
+ /** Machine-readable error code */
673
+ code: string;
674
+ /** Human-readable error message */
675
+ message: string;
676
+ /** Optional context data for debugging */
677
+ context?: Record<string, unknown>;
678
+ }
679
+ /**
680
+ * ValidationResult - The result of a validation operation
681
+ *
682
+ * Contains:
683
+ * - valid: Whether the validation passed
684
+ * - errors: Array of validation errors (empty if valid)
685
+ */
686
+ interface ValidationResult {
687
+ /** Whether the validation passed */
688
+ valid: boolean;
689
+ /** Array of validation errors (empty if valid) */
690
+ errors: ValidationError[];
691
+ }
692
+ /**
693
+ * Create a successful validation result
694
+ *
695
+ * @returns A ValidationResult indicating success
696
+ */
697
+ declare function validResult(): ValidationResult;
698
+ /**
699
+ * Create a failed validation result with a single error
700
+ *
701
+ * @param code - Error code
702
+ * @param message - Error message
703
+ * @param context - Optional context data
704
+ * @returns A ValidationResult indicating failure
705
+ */
706
+ declare function invalidResult(code: string, message: string, context?: Record<string, unknown>): ValidationResult;
707
+ /**
708
+ * Create a failed validation result with multiple errors
709
+ *
710
+ * @param errors - Array of validation errors
711
+ * @returns A ValidationResult indicating failure
712
+ */
713
+ declare function invalidResults(errors: ValidationError[]): ValidationResult;
714
+ /**
715
+ * Combine multiple validation results
716
+ *
717
+ * If any result is invalid, the combined result is invalid.
718
+ * All errors are collected together.
719
+ *
720
+ * @param results - Array of validation results to combine
721
+ * @returns A combined ValidationResult
722
+ */
723
+ declare function combineResults(...results: ValidationResult[]): ValidationResult;
724
+
725
+ /**
726
+ * DISPATCH ORCHESTRATION
727
+ *
728
+ * Coordinates operation execution, validation, and history management.
729
+ *
730
+ * WHAT IS THE DISPATCHER?
731
+ * - Executes operations on state
732
+ * - Validates the resulting state
733
+ * - Commits valid states to history
734
+ * - Rejects invalid states with errors
735
+ *
736
+ * WHY A DISPATCHER?
737
+ * - Enforces validation before mutation
738
+ * - Prevents invalid states from entering history
739
+ * - Provides consistent error handling
740
+ * - Separates concerns (operations vs validation vs history)
741
+ *
742
+ * CRITICAL FLOW:
743
+ * 1. Execute operation (pure function)
744
+ * 2. Validate resulting state
745
+ * 3. If valid: push to history, return success
746
+ * 4. If invalid: return errors, state unchanged
747
+ *
748
+ * USAGE:
749
+ * ```typescript
750
+ * const result = dispatch(history, (state) => addClip(state, trackId, clip));
751
+ * if (result.success) {
752
+ * history = result.history;
753
+ * } else {
754
+ * console.error('Operation failed:', result.errors);
755
+ * }
756
+ * ```
757
+ */
758
+
759
+ /**
760
+ * DispatchResult - The result of a dispatch operation
761
+ *
762
+ * Contains:
763
+ * - success: Whether the operation succeeded
764
+ * - history: Updated history (if success) or unchanged (if failure)
765
+ * - errors: Validation errors (if failure)
766
+ */
767
+ interface DispatchResult {
768
+ /** Whether the operation succeeded */
769
+ success: boolean;
770
+ /** Updated history state */
771
+ history: HistoryState;
772
+ /** Validation errors (if operation failed) */
773
+ errors?: ValidationError[];
774
+ }
775
+
776
+ /**
777
+ * TIMELINE ENGINE
778
+ *
779
+ * The main public API for the timeline editing kernel.
780
+ *
781
+ * WHAT IS THE TIMELINE ENGINE?
782
+ * - A thin wrapper around the history and dispatch systems
783
+ * - Provides a convenient, object-oriented API
784
+ * - Manages internal state
785
+ * - Coordinates operations, validation, and history
786
+ *
787
+ * WHY A CLASS?
788
+ * - Encapsulates state management
789
+ * - Provides a clean API for users
790
+ * - Hides complexity of history and dispatch
791
+ * - Familiar OOP interface for most developers
792
+ *
793
+ * USAGE:
794
+ * ```typescript
795
+ * const engine = new TimelineEngine(initialState);
796
+ *
797
+ * // Add a clip
798
+ * const result = engine.addClip(trackId, clip);
799
+ * if (!result.success) {
800
+ * console.error('Failed to add clip:', result.errors);
801
+ * }
802
+ *
803
+ * // Undo/redo
804
+ * engine.undo();
805
+ * engine.redo();
806
+ *
807
+ * // Query state
808
+ * const clip = engine.findClipById('clip_1');
809
+ * const state = engine.getState();
810
+ * ```
811
+ *
812
+ * DESIGN PHILOSOPHY:
813
+ * - Business logic lives in pure modules (operations, validation, etc.)
814
+ * - Engine is just a thin orchestration layer
815
+ * - Easy to test (can test pure functions independently)
816
+ */
817
+
818
+ /**
819
+ * TimelineEngine - The main timeline editing engine
820
+ *
821
+ * Provides a high-level API for timeline editing with built-in
822
+ * undo/redo, validation, and state management.
823
+ */
824
+ declare class TimelineEngine {
825
+ private history;
826
+ private listeners;
827
+ /**
828
+ * Create a new timeline engine
829
+ *
830
+ * @param initialState - Initial timeline state
831
+ * @param historyLimit - Maximum number of undo steps (default: 50)
832
+ */
833
+ constructor(initialState: TimelineState, historyLimit?: number);
834
+ /**
835
+ * Subscribe to state changes
836
+ *
837
+ * The listener will be called whenever the timeline state changes,
838
+ * with the new state passed as an argument.
839
+ * This is used by framework adapters (e.g., React) to trigger re-renders.
840
+ *
841
+ * @param listener - Function to call on state changes, receives new state
842
+ * @returns Unsubscribe function
843
+ *
844
+ * @example
845
+ * ```typescript
846
+ * const unsubscribe = engine.subscribe((state) => {
847
+ * console.log('State changed:', state);
848
+ * });
849
+ *
850
+ * // Later...
851
+ * unsubscribe();
852
+ * ```
853
+ */
854
+ subscribe(listener: (state: TimelineState) => void): () => void;
855
+ /**
856
+ * Notify all subscribers of a state change
857
+ *
858
+ * This is called internally after any operation that modifies state.
859
+ * Framework adapters use this to trigger re-renders.
860
+ */
861
+ private notify;
862
+ /**
863
+ * Get the current timeline state
864
+ *
865
+ * @returns Current timeline state
866
+ */
867
+ getState(): TimelineState;
868
+ /**
869
+ * Register an asset
870
+ *
871
+ * @param asset - Asset to register
872
+ * @returns Dispatch result
873
+ */
874
+ registerAsset(asset: Asset): DispatchResult;
875
+ /**
876
+ * Get an asset by ID
877
+ *
878
+ * @param assetId - Asset ID
879
+ * @returns The asset, or undefined if not found
880
+ */
881
+ getAsset(assetId: string): Asset | undefined;
882
+ /**
883
+ * Add a clip to a track
884
+ *
885
+ * @param trackId - ID of the track to add to
886
+ * @param clip - Clip to add
887
+ * @returns Dispatch result
888
+ */
889
+ addClip(trackId: string, clip: Clip): DispatchResult;
890
+ /**
891
+ * Remove a clip
892
+ *
893
+ * @param clipId - ID of the clip to remove
894
+ * @returns Dispatch result
895
+ */
896
+ removeClip(clipId: string): DispatchResult;
897
+ /**
898
+ * Move a clip to a new timeline position
899
+ *
900
+ * @param clipId - ID of the clip to move
901
+ * @param newStart - New timeline start frame
902
+ * @returns Dispatch result
903
+ */
904
+ moveClip(clipId: string, newStart: Frame): DispatchResult;
905
+ /**
906
+ * Resize a clip
907
+ *
908
+ * @param clipId - ID of the clip to resize
909
+ * @param newStart - New timeline start frame
910
+ * @param newEnd - New timeline end frame
911
+ * @returns Dispatch result
912
+ */
913
+ resizeClip(clipId: string, newStart: Frame, newEnd: Frame): DispatchResult;
914
+ /**
915
+ * Trim a clip (change media bounds)
916
+ *
917
+ * @param clipId - ID of the clip to trim
918
+ * @param newMediaIn - New media in frame
919
+ * @param newMediaOut - New media out frame
920
+ * @returns Dispatch result
921
+ */
922
+ trimClip(clipId: string, newMediaIn: Frame, newMediaOut: Frame): DispatchResult;
923
+ /**
924
+ * Move a clip to a different track
925
+ *
926
+ * @param clipId - ID of the clip to move
927
+ * @param targetTrackId - ID of the target track
928
+ * @returns Dispatch result
929
+ */
930
+ moveClipToTrack(clipId: string, targetTrackId: string): DispatchResult;
931
+ /**
932
+ * Add a track
933
+ *
934
+ * @param track - Track to add
935
+ * @returns Dispatch result
936
+ */
937
+ addTrack(track: Track): DispatchResult;
938
+ /**
939
+ * Remove a track
940
+ *
941
+ * @param trackId - ID of the track to remove
942
+ * @returns Dispatch result
943
+ */
944
+ removeTrack(trackId: string): DispatchResult;
945
+ /**
946
+ * Move a track to a new position
947
+ *
948
+ * @param trackId - ID of the track to move
949
+ * @param newIndex - New index position
950
+ * @returns Dispatch result
951
+ */
952
+ moveTrack(trackId: string, newIndex: number): DispatchResult;
953
+ /**
954
+ * Toggle track mute
955
+ *
956
+ * @param trackId - ID of the track
957
+ * @returns Dispatch result
958
+ */
959
+ toggleTrackMute(trackId: string): DispatchResult;
960
+ /**
961
+ * Toggle track lock
962
+ *
963
+ * @param trackId - ID of the track
964
+ * @returns Dispatch result
965
+ */
966
+ toggleTrackLock(trackId: string): DispatchResult;
967
+ /**
968
+ * Toggle track solo
969
+ *
970
+ * @param trackId - ID of the track
971
+ * @returns Dispatch result
972
+ */
973
+ toggleTrackSolo(trackId: string): DispatchResult;
974
+ /**
975
+ * Set track height
976
+ *
977
+ * @param trackId - ID of the track
978
+ * @param height - New height in pixels
979
+ * @returns Dispatch result
980
+ */
981
+ setTrackHeight(trackId: string, height: number): DispatchResult;
982
+ /**
983
+ * Set timeline duration
984
+ *
985
+ * @param duration - New duration in frames
986
+ * @returns Dispatch result
987
+ */
988
+ setTimelineDuration(duration: Frame): DispatchResult;
989
+ /**
990
+ * Set timeline name
991
+ *
992
+ * @param name - New timeline name
993
+ * @returns Dispatch result
994
+ */
995
+ setTimelineName(name: string): DispatchResult;
996
+ /**
997
+ * Undo the last action
998
+ *
999
+ * @returns true if undo was performed
1000
+ */
1001
+ undo(): boolean;
1002
+ /**
1003
+ * Redo the last undone action
1004
+ *
1005
+ * @returns true if redo was performed
1006
+ */
1007
+ redo(): boolean;
1008
+ /**
1009
+ * Check if undo is available
1010
+ *
1011
+ * @returns true if undo is available
1012
+ */
1013
+ canUndo(): boolean;
1014
+ /**
1015
+ * Check if redo is available
1016
+ *
1017
+ * @returns true if redo is available
1018
+ */
1019
+ canRedo(): boolean;
1020
+ /**
1021
+ * Find a clip by ID
1022
+ *
1023
+ * @param clipId - Clip ID
1024
+ * @returns The clip, or undefined if not found
1025
+ */
1026
+ findClipById(clipId: string): Clip | undefined;
1027
+ /**
1028
+ * Find a track by ID
1029
+ *
1030
+ * @param trackId - Track ID
1031
+ * @returns The track, or undefined if not found
1032
+ */
1033
+ findTrackById(trackId: string): Track | undefined;
1034
+ /**
1035
+ * Get all clips on a track
1036
+ *
1037
+ * @param trackId - Track ID
1038
+ * @returns Array of clips on the track
1039
+ */
1040
+ getClipsOnTrack(trackId: string): Clip[];
1041
+ /**
1042
+ * Get all clips at a specific frame
1043
+ *
1044
+ * @param frame - Frame to check
1045
+ * @returns Array of clips at that frame
1046
+ */
1047
+ getClipsAtFrame(frame: Frame): Clip[];
1048
+ /**
1049
+ * Get all clips in a frame range
1050
+ *
1051
+ * @param start - Start frame
1052
+ * @param end - End frame
1053
+ * @returns Array of clips in the range
1054
+ */
1055
+ getClipsInRange(start: Frame, end: Frame): Clip[];
1056
+ /**
1057
+ * Get all clips in the timeline
1058
+ *
1059
+ * @returns Array of all clips
1060
+ */
1061
+ getAllClips(): Clip[];
1062
+ /**
1063
+ * Get all tracks in the timeline
1064
+ *
1065
+ * @returns Array of all tracks
1066
+ */
1067
+ getAllTracks(): Track[];
1068
+ }
1069
+
1070
+ /**
1071
+ * FRAME UTILITIES
1072
+ *
1073
+ * Pure functions for working with frame-based time values.
1074
+ *
1075
+ * These utilities handle:
1076
+ * - Converting between frames and seconds
1077
+ * - Formatting frames as timecode (HH:MM:SS:FF)
1078
+ * - Frame arithmetic (clamping, rounding)
1079
+ *
1080
+ * CRITICAL RULES:
1081
+ * - All conversions must quantize to whole frames
1082
+ * - No floating-point frame values allowed
1083
+ * - Always round/floor/ceil explicitly
1084
+ *
1085
+ * USAGE:
1086
+ * ```typescript
1087
+ * const fps = frameRate(30);
1088
+ * const frames = secondsToFrames(5.5, fps); // 165 frames
1089
+ * const seconds = framesToSeconds(frames, fps); // 5.5 seconds
1090
+ * const timecode = framesToTimecode(frames, fps); // "00:00:05:15"
1091
+ * ```
1092
+ */
1093
+
1094
+ /**
1095
+ * Convert frames to seconds
1096
+ *
1097
+ * @param frames - Frame number
1098
+ * @param fps - Frames per second
1099
+ * @returns Time in seconds (may be fractional)
1100
+ */
1101
+ declare function framesToSeconds(frames: Frame, fps: FrameRate): number;
1102
+ /**
1103
+ * Convert seconds to frames
1104
+ *
1105
+ * IMPORTANT: This rounds to the nearest frame.
1106
+ * If you need different rounding behavior, use Math.floor or Math.ceil explicitly.
1107
+ *
1108
+ * @param seconds - Time in seconds
1109
+ * @param fps - Frames per second
1110
+ * @returns Frame number (rounded to nearest frame)
1111
+ */
1112
+ declare function secondsToFrames(seconds: number, fps: FrameRate): Frame;
1113
+ /**
1114
+ * Convert frames to timecode format (HH:MM:SS:FF)
1115
+ *
1116
+ * Example: 3825 frames at 30fps = "00:02:07:15"
1117
+ *
1118
+ * @param frames - Frame number
1119
+ * @param fps - Frames per second
1120
+ * @returns Timecode string
1121
+ */
1122
+ declare function framesToTimecode(frames: Frame, fps: FrameRate): string;
1123
+ /**
1124
+ * Convert frames to simple MM:SS format
1125
+ *
1126
+ * Example: 3825 frames at 30fps = "02:07"
1127
+ *
1128
+ * @param frames - Frame number
1129
+ * @param fps - Frames per second
1130
+ * @returns Time string in MM:SS format
1131
+ */
1132
+ declare function framesToMinutesSeconds(frames: Frame, fps: FrameRate): string;
1133
+ /**
1134
+ * Clamp a frame value between min and max
1135
+ *
1136
+ * @param value - Frame to clamp
1137
+ * @param min - Minimum frame (inclusive)
1138
+ * @param max - Maximum frame (inclusive)
1139
+ * @returns Clamped frame value
1140
+ */
1141
+ declare function clampFrame(value: Frame, min: Frame, max: Frame): Frame;
1142
+ /**
1143
+ * Add two frame values
1144
+ *
1145
+ * @param a - First frame
1146
+ * @param b - Second frame
1147
+ * @returns Sum of frames
1148
+ */
1149
+ declare function addFrames(a: Frame, b: Frame): Frame;
1150
+ /**
1151
+ * Subtract two frame values
1152
+ *
1153
+ * @param a - First frame
1154
+ * @param b - Second frame (subtracted from a)
1155
+ * @returns Difference of frames (clamped to 0 if negative)
1156
+ */
1157
+ declare function subtractFrames(a: Frame, b: Frame): Frame;
1158
+ /**
1159
+ * Calculate duration between two frames
1160
+ *
1161
+ * @param start - Start frame
1162
+ * @param end - End frame
1163
+ * @returns Duration in frames (end - start)
1164
+ */
1165
+ declare function frameDuration(start: Frame, end: Frame): Frame;
1166
+
1167
+ export { type Asset as A, getClipMediaDuration as B, type Clip as C, type DispatchResult as D, invalidResult as E, type Frame as F, type Group as G, invalidResults as H, isValidFrame as I, isValidFrameRate as J, secondsToFrames as K, type LinkGroup as L, type Marker as M, sortTrackClips as N, subtractFrames as O, validResult as P, type RegionMarker as R, type TimelineState as T, type ValidationResult as V, type WorkArea as W, type Track as a, type ClipMarker as b, type TimelineMarker as c, type AssetType as d, type FrameRate as e, type Timeline as f, TimelineEngine as g, type TrackType as h, type ValidationError as i, addFrames as j, clampFrame as k, clipContainsFrame as l, clipsOverlap as m, combineResults as n, createAsset as o, createClip as p, createTimeline as q, createTimelineState as r, createTrack as s, frame as t, frameDuration as u, frameRate as v, framesToMinutesSeconds as w, framesToSeconds as x, framesToTimecode as y, getClipDuration as z };