@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,1145 @@
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
+ /** Optional metadata for custom use cases */
263
+ metadata?: Record<string, unknown>;
264
+ }
265
+ /**
266
+ * Create a new track
267
+ *
268
+ * @param params - Track parameters
269
+ * @returns A new Track object
270
+ */
271
+ declare function createTrack(params: {
272
+ id: string;
273
+ name: string;
274
+ type: TrackType;
275
+ clips?: Clip[];
276
+ locked?: boolean;
277
+ muted?: boolean;
278
+ metadata?: Record<string, unknown>;
279
+ }): Track;
280
+ /**
281
+ * Sort clips on a track by timeline start frame
282
+ *
283
+ * This is useful for maintaining clip order and improving
284
+ * query performance.
285
+ *
286
+ * @param track - The track to sort
287
+ * @returns A new track with sorted clips
288
+ */
289
+ declare function sortTrackClips(track: Track): Track;
290
+
291
+ /**
292
+ * TIMELINE MODEL
293
+ *
294
+ * The Timeline is the root container for the entire editing project.
295
+ *
296
+ * WHAT IS A TIMELINE?
297
+ * - The top-level data structure for a project
298
+ * - Contains all tracks (which contain all clips)
299
+ * - Defines the frame rate (FPS) for the entire project
300
+ * - Defines the total duration of the project
301
+ *
302
+ * WHY A TIMELINE?
303
+ * - Single source of truth for all timeline data
304
+ * - Defines the temporal bounds of the project
305
+ * - Provides a consistent frame rate for all time calculations
306
+ *
307
+ * EXAMPLE:
308
+ * ```typescript
309
+ * const timeline: Timeline = {
310
+ * id: 'timeline_1',
311
+ * name: 'My Project',
312
+ * fps: frameRate(30),
313
+ * duration: frame(9000), // 5 minutes at 30fps
314
+ * tracks: [],
315
+ * };
316
+ * ```
317
+ *
318
+ * INVARIANTS:
319
+ * - FPS is immutable after timeline creation
320
+ * - Duration must be positive
321
+ * - All tracks must have unique IDs
322
+ */
323
+
324
+ /**
325
+ * Timeline - The root container for a timeline project
326
+ */
327
+ interface Timeline {
328
+ /** Unique identifier */
329
+ id: string;
330
+ /** Human-readable name */
331
+ name: string;
332
+ /** Frames per second (immutable after creation) */
333
+ fps: FrameRate;
334
+ /** Total duration of the timeline in frames */
335
+ duration: Frame;
336
+ /** Tracks in the timeline (ordered bottom-to-top) */
337
+ tracks: Track[];
338
+ /** Optional metadata for custom use cases */
339
+ metadata?: Record<string, unknown>;
340
+ }
341
+ /**
342
+ * Create a new timeline
343
+ *
344
+ * @param params - Timeline parameters
345
+ * @returns A new Timeline object
346
+ */
347
+ declare function createTimeline(params: {
348
+ id: string;
349
+ name: string;
350
+ fps: FrameRate;
351
+ duration: Frame;
352
+ tracks?: Track[];
353
+ metadata?: Record<string, unknown>;
354
+ }): Timeline;
355
+
356
+ /**
357
+ * ASSET MODEL
358
+ *
359
+ * An Asset represents immutable metadata about a media file.
360
+ *
361
+ * WHAT IS AN ASSET?
362
+ * - A reference to source media (video, audio, image)
363
+ * - Contains metadata like duration and type
364
+ * - Immutable once registered (duration never changes)
365
+ *
366
+ * WHY SEPARATE ASSETS FROM CLIPS?
367
+ * - Multiple clips can reference the same asset
368
+ * - Asset duration is the source of truth
369
+ * - Clips can trim/slice the asset without modifying it
370
+ *
371
+ * EXAMPLE:
372
+ * ```typescript
373
+ * const asset: Asset = {
374
+ * id: 'asset_1',
375
+ * type: 'video',
376
+ * duration: frame(3600), // 2 minutes at 30fps
377
+ * sourceUrl: 'https://example.com/video.mp4',
378
+ * };
379
+ * ```
380
+ *
381
+ * INVARIANTS:
382
+ * - Asset ID must be unique
383
+ * - Duration must be positive
384
+ * - Duration is immutable after registration
385
+ */
386
+
387
+ /**
388
+ * AssetType - The kind of media this asset represents
389
+ */
390
+ type AssetType = 'video' | 'audio' | 'image';
391
+ /**
392
+ * Asset - Immutable metadata about a media file
393
+ */
394
+ interface Asset {
395
+ /** Unique identifier */
396
+ id: string;
397
+ /** Type of media */
398
+ type: AssetType;
399
+ /** Total duration of the asset in frames */
400
+ duration: Frame;
401
+ /** Source URL or file path */
402
+ sourceUrl: string;
403
+ /** Optional metadata for custom use cases */
404
+ metadata?: Record<string, unknown>;
405
+ }
406
+ /**
407
+ * Create a new asset
408
+ *
409
+ * @param params - Asset parameters
410
+ * @returns A new Asset object
411
+ */
412
+ declare function createAsset(params: {
413
+ id: string;
414
+ type: AssetType;
415
+ duration: Frame;
416
+ sourceUrl: string;
417
+ metadata?: Record<string, unknown>;
418
+ }): Asset;
419
+
420
+ /**
421
+ * LINKING TYPES
422
+ *
423
+ * Link groups synchronize edits across multiple clips.
424
+ * When one clip in a link group is moved/deleted, all linked clips are affected.
425
+ */
426
+ /**
427
+ * Link group - relates clips for synchronized editing
428
+ */
429
+ interface LinkGroup {
430
+ id: string;
431
+ clipIds: string[];
432
+ createdAt?: number;
433
+ }
434
+
435
+ /**
436
+ * GROUPING TYPES
437
+ *
438
+ * Groups organize clips visually without affecting edit behavior.
439
+ * Unlike link groups, groups are for organization only.
440
+ */
441
+ /**
442
+ * Group - organizes clips visually
443
+ */
444
+ interface Group {
445
+ id: string;
446
+ name: string;
447
+ clipIds: string[];
448
+ parentGroupId?: string;
449
+ color?: string;
450
+ collapsed?: boolean;
451
+ }
452
+
453
+ /**
454
+ * MARKER TYPES
455
+ *
456
+ * Pure metadata for timeline navigation and organization.
457
+ * Markers do not affect clip timing or validation.
458
+ */
459
+
460
+ /**
461
+ * Timeline marker - marks a specific frame on the timeline
462
+ */
463
+ interface TimelineMarker {
464
+ id: string;
465
+ type: 'timeline';
466
+ frame: Frame;
467
+ label: string;
468
+ color?: string;
469
+ }
470
+ /**
471
+ * Clip marker - marks a frame relative to clip start
472
+ */
473
+ interface ClipMarker {
474
+ id: string;
475
+ type: 'clip';
476
+ clipId: string;
477
+ frame: Frame;
478
+ label: string;
479
+ color?: string;
480
+ }
481
+ /**
482
+ * Region marker - marks a frame range
483
+ */
484
+ interface RegionMarker {
485
+ id: string;
486
+ type: 'region';
487
+ startFrame: Frame;
488
+ endFrame: Frame;
489
+ label: string;
490
+ color?: string;
491
+ }
492
+ /**
493
+ * Work area - defines the active editing region
494
+ */
495
+ interface WorkArea {
496
+ startFrame: Frame;
497
+ endFrame: Frame;
498
+ }
499
+ /**
500
+ * Union type for all marker types
501
+ */
502
+ type Marker = TimelineMarker | ClipMarker | RegionMarker;
503
+
504
+ /**
505
+ * TIMELINE STATE
506
+ *
507
+ * This is the complete state shape for the timeline engine.
508
+ *
509
+ * WHAT IS TIMELINE STATE?
510
+ * - The root state object that contains everything
511
+ * - Timeline (tracks and clips)
512
+ * - Assets (media metadata)
513
+ *
514
+ * WHY SEPARATE STATE?
515
+ * - Clear separation between timeline structure and asset registry
516
+ * - Assets can be shared across multiple clips
517
+ * - State is the single source of truth
518
+ *
519
+ * EXAMPLE:
520
+ * ```typescript
521
+ * const state: TimelineState = {
522
+ * timeline: {
523
+ * id: 'timeline_1',
524
+ * name: 'My Project',
525
+ * fps: frameRate(30),
526
+ * duration: frame(9000),
527
+ * tracks: [],
528
+ * },
529
+ * assets: new Map([
530
+ * ['asset_1', { id: 'asset_1', type: 'video', duration: frame(3600), sourceUrl: '...' }],
531
+ * ]),
532
+ * };
533
+ * ```
534
+ *
535
+ * IMMUTABILITY:
536
+ * All operations on TimelineState must return a NEW state object.
537
+ * Never mutate the existing state.
538
+ */
539
+
540
+ /**
541
+ * TimelineState - The complete state for the timeline engine
542
+ *
543
+ * Phase 2 additions:
544
+ * - linkGroups: Synchronized editing groups
545
+ * - groups: Visual organization groups
546
+ * - markers: Timeline/clip/region markers
547
+ * - workArea: Active editing region
548
+ */
549
+ interface TimelineState {
550
+ /** The timeline (tracks and clips) */
551
+ timeline: Timeline;
552
+ /** Asset registry (media metadata) */
553
+ assets: Map<string, Asset>;
554
+ /** Link groups for synchronized editing */
555
+ linkGroups: Map<string, LinkGroup>;
556
+ /** Groups for visual organization */
557
+ groups: Map<string, Group>;
558
+ /** Markers for navigation and annotation */
559
+ markers: {
560
+ timeline: TimelineMarker[];
561
+ clips: ClipMarker[];
562
+ regions: RegionMarker[];
563
+ };
564
+ /** Optional work area definition */
565
+ workArea?: WorkArea;
566
+ }
567
+ /**
568
+ * Create a new timeline state
569
+ *
570
+ * @param params - State parameters
571
+ * @returns A new TimelineState object
572
+ */
573
+ declare function createTimelineState(params: {
574
+ timeline: Timeline;
575
+ assets?: Map<string, Asset>;
576
+ linkGroups?: Map<string, LinkGroup>;
577
+ groups?: Map<string, Group>;
578
+ markers?: {
579
+ timeline: TimelineMarker[];
580
+ clips: ClipMarker[];
581
+ regions: RegionMarker[];
582
+ };
583
+ workArea?: WorkArea;
584
+ }): TimelineState;
585
+
586
+ /**
587
+ * HISTORY ENGINE
588
+ *
589
+ * Snapshot-based undo/redo system for timeline state.
590
+ *
591
+ * WHAT IS THE HISTORY ENGINE?
592
+ * - Stores immutable snapshots of timeline state
593
+ * - Provides undo/redo functionality
594
+ * - Prevents state corruption
595
+ *
596
+ * HOW IT WORKS:
597
+ * - past: Array of previous states
598
+ * - present: Current state
599
+ * - future: Array of states that can be redone
600
+ *
601
+ * WHY SNAPSHOTS?
602
+ * - Simple and reliable (no complex diffing)
603
+ * - Guaranteed to restore exact state
604
+ * - No risk of partial corruption
605
+ * - Easy to implement and test
606
+ *
607
+ * USAGE:
608
+ * ```typescript
609
+ * let history = createHistory(initialState);
610
+ * history = pushHistory(history, newState);
611
+ * history = undo(history);
612
+ * history = redo(history);
613
+ * ```
614
+ *
615
+ * ALL FUNCTIONS ARE PURE:
616
+ * - Take history as input
617
+ * - Return new history as output
618
+ * - Never mutate input
619
+ */
620
+
621
+ /**
622
+ * HistoryState - The history container
623
+ *
624
+ * Contains:
625
+ * - past: Array of previous states (oldest first)
626
+ * - present: Current state
627
+ * - future: Array of states that can be redone (newest first)
628
+ * - limit: Maximum number of past states to keep
629
+ */
630
+ interface HistoryState {
631
+ past: TimelineState[];
632
+ present: TimelineState;
633
+ future: TimelineState[];
634
+ limit: number;
635
+ }
636
+
637
+ /**
638
+ * VALIDATION SYSTEM TYPES
639
+ *
640
+ * This file defines the structure for validation results throughout the engine.
641
+ *
642
+ * WHY STRUCTURED VALIDATION?
643
+ * - Operations can fail gracefully (no exceptions thrown)
644
+ * - Errors are descriptive and actionable
645
+ * - Multiple errors can be collected and reported together
646
+ * - Validation logic is separated from business logic
647
+ *
648
+ * USAGE:
649
+ * ```typescript
650
+ * const result = validateClip(state, clip);
651
+ * if (!result.valid) {
652
+ * console.error('Validation failed:', result.errors);
653
+ * return;
654
+ * }
655
+ * ```
656
+ */
657
+ /**
658
+ * ValidationError - A single validation error
659
+ *
660
+ * Contains:
661
+ * - code: Machine-readable error code (e.g., "CLIP_OVERLAP")
662
+ * - message: Human-readable error message
663
+ * - context: Optional additional data about the error
664
+ */
665
+ interface ValidationError {
666
+ /** Machine-readable error code */
667
+ code: string;
668
+ /** Human-readable error message */
669
+ message: string;
670
+ /** Optional context data for debugging */
671
+ context?: Record<string, unknown>;
672
+ }
673
+ /**
674
+ * ValidationResult - The result of a validation operation
675
+ *
676
+ * Contains:
677
+ * - valid: Whether the validation passed
678
+ * - errors: Array of validation errors (empty if valid)
679
+ */
680
+ interface ValidationResult {
681
+ /** Whether the validation passed */
682
+ valid: boolean;
683
+ /** Array of validation errors (empty if valid) */
684
+ errors: ValidationError[];
685
+ }
686
+ /**
687
+ * Create a successful validation result
688
+ *
689
+ * @returns A ValidationResult indicating success
690
+ */
691
+ declare function validResult(): ValidationResult;
692
+ /**
693
+ * Create a failed validation result with a single error
694
+ *
695
+ * @param code - Error code
696
+ * @param message - Error message
697
+ * @param context - Optional context data
698
+ * @returns A ValidationResult indicating failure
699
+ */
700
+ declare function invalidResult(code: string, message: string, context?: Record<string, unknown>): ValidationResult;
701
+ /**
702
+ * Create a failed validation result with multiple errors
703
+ *
704
+ * @param errors - Array of validation errors
705
+ * @returns A ValidationResult indicating failure
706
+ */
707
+ declare function invalidResults(errors: ValidationError[]): ValidationResult;
708
+ /**
709
+ * Combine multiple validation results
710
+ *
711
+ * If any result is invalid, the combined result is invalid.
712
+ * All errors are collected together.
713
+ *
714
+ * @param results - Array of validation results to combine
715
+ * @returns A combined ValidationResult
716
+ */
717
+ declare function combineResults(...results: ValidationResult[]): ValidationResult;
718
+
719
+ /**
720
+ * DISPATCH ORCHESTRATION
721
+ *
722
+ * Coordinates operation execution, validation, and history management.
723
+ *
724
+ * WHAT IS THE DISPATCHER?
725
+ * - Executes operations on state
726
+ * - Validates the resulting state
727
+ * - Commits valid states to history
728
+ * - Rejects invalid states with errors
729
+ *
730
+ * WHY A DISPATCHER?
731
+ * - Enforces validation before mutation
732
+ * - Prevents invalid states from entering history
733
+ * - Provides consistent error handling
734
+ * - Separates concerns (operations vs validation vs history)
735
+ *
736
+ * CRITICAL FLOW:
737
+ * 1. Execute operation (pure function)
738
+ * 2. Validate resulting state
739
+ * 3. If valid: push to history, return success
740
+ * 4. If invalid: return errors, state unchanged
741
+ *
742
+ * USAGE:
743
+ * ```typescript
744
+ * const result = dispatch(history, (state) => addClip(state, trackId, clip));
745
+ * if (result.success) {
746
+ * history = result.history;
747
+ * } else {
748
+ * console.error('Operation failed:', result.errors);
749
+ * }
750
+ * ```
751
+ */
752
+
753
+ /**
754
+ * DispatchResult - The result of a dispatch operation
755
+ *
756
+ * Contains:
757
+ * - success: Whether the operation succeeded
758
+ * - history: Updated history (if success) or unchanged (if failure)
759
+ * - errors: Validation errors (if failure)
760
+ */
761
+ interface DispatchResult {
762
+ /** Whether the operation succeeded */
763
+ success: boolean;
764
+ /** Updated history state */
765
+ history: HistoryState;
766
+ /** Validation errors (if operation failed) */
767
+ errors?: ValidationError[];
768
+ }
769
+
770
+ /**
771
+ * TIMELINE ENGINE
772
+ *
773
+ * The main public API for the timeline editing kernel.
774
+ *
775
+ * WHAT IS THE TIMELINE ENGINE?
776
+ * - A thin wrapper around the history and dispatch systems
777
+ * - Provides a convenient, object-oriented API
778
+ * - Manages internal state
779
+ * - Coordinates operations, validation, and history
780
+ *
781
+ * WHY A CLASS?
782
+ * - Encapsulates state management
783
+ * - Provides a clean API for users
784
+ * - Hides complexity of history and dispatch
785
+ * - Familiar OOP interface for most developers
786
+ *
787
+ * USAGE:
788
+ * ```typescript
789
+ * const engine = new TimelineEngine(initialState);
790
+ *
791
+ * // Add a clip
792
+ * const result = engine.addClip(trackId, clip);
793
+ * if (!result.success) {
794
+ * console.error('Failed to add clip:', result.errors);
795
+ * }
796
+ *
797
+ * // Undo/redo
798
+ * engine.undo();
799
+ * engine.redo();
800
+ *
801
+ * // Query state
802
+ * const clip = engine.findClipById('clip_1');
803
+ * const state = engine.getState();
804
+ * ```
805
+ *
806
+ * DESIGN PHILOSOPHY:
807
+ * - Business logic lives in pure modules (operations, validation, etc.)
808
+ * - Engine is just a thin orchestration layer
809
+ * - Easy to test (can test pure functions independently)
810
+ */
811
+
812
+ /**
813
+ * TimelineEngine - The main timeline editing engine
814
+ *
815
+ * Provides a high-level API for timeline editing with built-in
816
+ * undo/redo, validation, and state management.
817
+ */
818
+ declare class TimelineEngine {
819
+ private history;
820
+ private listeners;
821
+ /**
822
+ * Create a new timeline engine
823
+ *
824
+ * @param initialState - Initial timeline state
825
+ * @param historyLimit - Maximum number of undo steps (default: 50)
826
+ */
827
+ constructor(initialState: TimelineState, historyLimit?: number);
828
+ /**
829
+ * Subscribe to state changes
830
+ *
831
+ * The listener will be called whenever the timeline state changes.
832
+ * This is used by framework adapters (e.g., React) to trigger re-renders.
833
+ *
834
+ * @param listener - Function to call on state changes
835
+ * @returns Unsubscribe function
836
+ *
837
+ * @example
838
+ * ```typescript
839
+ * const unsubscribe = engine.subscribe(() => {
840
+ * console.log('State changed:', engine.getState());
841
+ * });
842
+ *
843
+ * // Later...
844
+ * unsubscribe();
845
+ * ```
846
+ */
847
+ subscribe(listener: () => void): () => void;
848
+ /**
849
+ * Notify all subscribers of a state change
850
+ *
851
+ * This is called internally after any operation that modifies state.
852
+ * Framework adapters use this to trigger re-renders.
853
+ */
854
+ private notify;
855
+ /**
856
+ * Get the current timeline state
857
+ *
858
+ * @returns Current timeline state
859
+ */
860
+ getState(): TimelineState;
861
+ /**
862
+ * Register an asset
863
+ *
864
+ * @param asset - Asset to register
865
+ * @returns Dispatch result
866
+ */
867
+ registerAsset(asset: Asset): DispatchResult;
868
+ /**
869
+ * Get an asset by ID
870
+ *
871
+ * @param assetId - Asset ID
872
+ * @returns The asset, or undefined if not found
873
+ */
874
+ getAsset(assetId: string): Asset | undefined;
875
+ /**
876
+ * Add a clip to a track
877
+ *
878
+ * @param trackId - ID of the track to add to
879
+ * @param clip - Clip to add
880
+ * @returns Dispatch result
881
+ */
882
+ addClip(trackId: string, clip: Clip): DispatchResult;
883
+ /**
884
+ * Remove a clip
885
+ *
886
+ * @param clipId - ID of the clip to remove
887
+ * @returns Dispatch result
888
+ */
889
+ removeClip(clipId: string): DispatchResult;
890
+ /**
891
+ * Move a clip to a new timeline position
892
+ *
893
+ * @param clipId - ID of the clip to move
894
+ * @param newStart - New timeline start frame
895
+ * @returns Dispatch result
896
+ */
897
+ moveClip(clipId: string, newStart: Frame): DispatchResult;
898
+ /**
899
+ * Resize a clip
900
+ *
901
+ * @param clipId - ID of the clip to resize
902
+ * @param newStart - New timeline start frame
903
+ * @param newEnd - New timeline end frame
904
+ * @returns Dispatch result
905
+ */
906
+ resizeClip(clipId: string, newStart: Frame, newEnd: Frame): DispatchResult;
907
+ /**
908
+ * Trim a clip (change media bounds)
909
+ *
910
+ * @param clipId - ID of the clip to trim
911
+ * @param newMediaIn - New media in frame
912
+ * @param newMediaOut - New media out frame
913
+ * @returns Dispatch result
914
+ */
915
+ trimClip(clipId: string, newMediaIn: Frame, newMediaOut: Frame): DispatchResult;
916
+ /**
917
+ * Move a clip to a different track
918
+ *
919
+ * @param clipId - ID of the clip to move
920
+ * @param targetTrackId - ID of the target track
921
+ * @returns Dispatch result
922
+ */
923
+ moveClipToTrack(clipId: string, targetTrackId: string): DispatchResult;
924
+ /**
925
+ * Add a track
926
+ *
927
+ * @param track - Track to add
928
+ * @returns Dispatch result
929
+ */
930
+ addTrack(track: Track): DispatchResult;
931
+ /**
932
+ * Remove a track
933
+ *
934
+ * @param trackId - ID of the track to remove
935
+ * @returns Dispatch result
936
+ */
937
+ removeTrack(trackId: string): DispatchResult;
938
+ /**
939
+ * Move a track to a new position
940
+ *
941
+ * @param trackId - ID of the track to move
942
+ * @param newIndex - New index position
943
+ * @returns Dispatch result
944
+ */
945
+ moveTrack(trackId: string, newIndex: number): DispatchResult;
946
+ /**
947
+ * Toggle track mute
948
+ *
949
+ * @param trackId - ID of the track
950
+ * @returns Dispatch result
951
+ */
952
+ toggleTrackMute(trackId: string): DispatchResult;
953
+ /**
954
+ * Toggle track lock
955
+ *
956
+ * @param trackId - ID of the track
957
+ * @returns Dispatch result
958
+ */
959
+ toggleTrackLock(trackId: string): DispatchResult;
960
+ /**
961
+ * Set timeline duration
962
+ *
963
+ * @param duration - New duration in frames
964
+ * @returns Dispatch result
965
+ */
966
+ setTimelineDuration(duration: Frame): DispatchResult;
967
+ /**
968
+ * Set timeline name
969
+ *
970
+ * @param name - New timeline name
971
+ * @returns Dispatch result
972
+ */
973
+ setTimelineName(name: string): DispatchResult;
974
+ /**
975
+ * Undo the last action
976
+ *
977
+ * @returns true if undo was performed
978
+ */
979
+ undo(): boolean;
980
+ /**
981
+ * Redo the last undone action
982
+ *
983
+ * @returns true if redo was performed
984
+ */
985
+ redo(): boolean;
986
+ /**
987
+ * Check if undo is available
988
+ *
989
+ * @returns true if undo is available
990
+ */
991
+ canUndo(): boolean;
992
+ /**
993
+ * Check if redo is available
994
+ *
995
+ * @returns true if redo is available
996
+ */
997
+ canRedo(): boolean;
998
+ /**
999
+ * Find a clip by ID
1000
+ *
1001
+ * @param clipId - Clip ID
1002
+ * @returns The clip, or undefined if not found
1003
+ */
1004
+ findClipById(clipId: string): Clip | undefined;
1005
+ /**
1006
+ * Find a track by ID
1007
+ *
1008
+ * @param trackId - Track ID
1009
+ * @returns The track, or undefined if not found
1010
+ */
1011
+ findTrackById(trackId: string): Track | undefined;
1012
+ /**
1013
+ * Get all clips on a track
1014
+ *
1015
+ * @param trackId - Track ID
1016
+ * @returns Array of clips on the track
1017
+ */
1018
+ getClipsOnTrack(trackId: string): Clip[];
1019
+ /**
1020
+ * Get all clips at a specific frame
1021
+ *
1022
+ * @param frame - Frame to check
1023
+ * @returns Array of clips at that frame
1024
+ */
1025
+ getClipsAtFrame(frame: Frame): Clip[];
1026
+ /**
1027
+ * Get all clips in a frame range
1028
+ *
1029
+ * @param start - Start frame
1030
+ * @param end - End frame
1031
+ * @returns Array of clips in the range
1032
+ */
1033
+ getClipsInRange(start: Frame, end: Frame): Clip[];
1034
+ /**
1035
+ * Get all clips in the timeline
1036
+ *
1037
+ * @returns Array of all clips
1038
+ */
1039
+ getAllClips(): Clip[];
1040
+ /**
1041
+ * Get all tracks in the timeline
1042
+ *
1043
+ * @returns Array of all tracks
1044
+ */
1045
+ getAllTracks(): Track[];
1046
+ }
1047
+
1048
+ /**
1049
+ * FRAME UTILITIES
1050
+ *
1051
+ * Pure functions for working with frame-based time values.
1052
+ *
1053
+ * These utilities handle:
1054
+ * - Converting between frames and seconds
1055
+ * - Formatting frames as timecode (HH:MM:SS:FF)
1056
+ * - Frame arithmetic (clamping, rounding)
1057
+ *
1058
+ * CRITICAL RULES:
1059
+ * - All conversions must quantize to whole frames
1060
+ * - No floating-point frame values allowed
1061
+ * - Always round/floor/ceil explicitly
1062
+ *
1063
+ * USAGE:
1064
+ * ```typescript
1065
+ * const fps = frameRate(30);
1066
+ * const frames = secondsToFrames(5.5, fps); // 165 frames
1067
+ * const seconds = framesToSeconds(frames, fps); // 5.5 seconds
1068
+ * const timecode = framesToTimecode(frames, fps); // "00:00:05:15"
1069
+ * ```
1070
+ */
1071
+
1072
+ /**
1073
+ * Convert frames to seconds
1074
+ *
1075
+ * @param frames - Frame number
1076
+ * @param fps - Frames per second
1077
+ * @returns Time in seconds (may be fractional)
1078
+ */
1079
+ declare function framesToSeconds(frames: Frame, fps: FrameRate): number;
1080
+ /**
1081
+ * Convert seconds to frames
1082
+ *
1083
+ * IMPORTANT: This rounds to the nearest frame.
1084
+ * If you need different rounding behavior, use Math.floor or Math.ceil explicitly.
1085
+ *
1086
+ * @param seconds - Time in seconds
1087
+ * @param fps - Frames per second
1088
+ * @returns Frame number (rounded to nearest frame)
1089
+ */
1090
+ declare function secondsToFrames(seconds: number, fps: FrameRate): Frame;
1091
+ /**
1092
+ * Convert frames to timecode format (HH:MM:SS:FF)
1093
+ *
1094
+ * Example: 3825 frames at 30fps = "00:02:07:15"
1095
+ *
1096
+ * @param frames - Frame number
1097
+ * @param fps - Frames per second
1098
+ * @returns Timecode string
1099
+ */
1100
+ declare function framesToTimecode(frames: Frame, fps: FrameRate): string;
1101
+ /**
1102
+ * Convert frames to simple MM:SS format
1103
+ *
1104
+ * Example: 3825 frames at 30fps = "02:07"
1105
+ *
1106
+ * @param frames - Frame number
1107
+ * @param fps - Frames per second
1108
+ * @returns Time string in MM:SS format
1109
+ */
1110
+ declare function framesToMinutesSeconds(frames: Frame, fps: FrameRate): string;
1111
+ /**
1112
+ * Clamp a frame value between min and max
1113
+ *
1114
+ * @param value - Frame to clamp
1115
+ * @param min - Minimum frame (inclusive)
1116
+ * @param max - Maximum frame (inclusive)
1117
+ * @returns Clamped frame value
1118
+ */
1119
+ declare function clampFrame(value: Frame, min: Frame, max: Frame): Frame;
1120
+ /**
1121
+ * Add two frame values
1122
+ *
1123
+ * @param a - First frame
1124
+ * @param b - Second frame
1125
+ * @returns Sum of frames
1126
+ */
1127
+ declare function addFrames(a: Frame, b: Frame): Frame;
1128
+ /**
1129
+ * Subtract two frame values
1130
+ *
1131
+ * @param a - First frame
1132
+ * @param b - Second frame (subtracted from a)
1133
+ * @returns Difference of frames (clamped to 0 if negative)
1134
+ */
1135
+ declare function subtractFrames(a: Frame, b: Frame): Frame;
1136
+ /**
1137
+ * Calculate duration between two frames
1138
+ *
1139
+ * @param start - Start frame
1140
+ * @param end - End frame
1141
+ * @returns Duration in frames (end - start)
1142
+ */
1143
+ declare function frameDuration(start: Frame, end: Frame): Frame;
1144
+
1145
+ 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 };