@rsvp-engine/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/CONTRIBUTING.md +52 -0
  3. package/LICENSE +21 -0
  4. package/README.md +69 -0
  5. package/dist/engine/config.d.ts +30 -0
  6. package/dist/engine/config.d.ts.map +1 -0
  7. package/dist/engine/rsvp-engine.d.ts +90 -0
  8. package/dist/engine/rsvp-engine.d.ts.map +1 -0
  9. package/dist/engine/types.d.ts +28 -0
  10. package/dist/engine/types.d.ts.map +1 -0
  11. package/dist/engine/validation.d.ts +5 -0
  12. package/dist/engine/validation.d.ts.map +1 -0
  13. package/dist/errors.d.ts +30 -0
  14. package/dist/errors.d.ts.map +1 -0
  15. package/dist/events/event-emitter.d.ts +8 -0
  16. package/dist/events/event-emitter.d.ts.map +1 -0
  17. package/dist/events/types.d.ts +31 -0
  18. package/dist/events/types.d.ts.map +1 -0
  19. package/dist/index.cjs +2 -0
  20. package/dist/index.cjs.map +1 -0
  21. package/dist/index.d.cts +23 -0
  22. package/dist/index.d.ts +23 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +2 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/runtimeGlobal.d.ts +9 -0
  27. package/dist/runtimeGlobal.d.ts.map +1 -0
  28. package/dist/scheduler/drift-corrected-scheduler.d.ts +50 -0
  29. package/dist/scheduler/drift-corrected-scheduler.d.ts.map +1 -0
  30. package/dist/scheduler/system-time-driver.d.ts +11 -0
  31. package/dist/scheduler/system-time-driver.d.ts.map +1 -0
  32. package/dist/scheduler/types.d.ts +23 -0
  33. package/dist/scheduler/types.d.ts.map +1 -0
  34. package/dist/state/state-machine.d.ts +7 -0
  35. package/dist/state/state-machine.d.ts.map +1 -0
  36. package/dist/state/types.d.ts +5 -0
  37. package/dist/state/types.d.ts.map +1 -0
  38. package/dist/tokenizer/default-tokenizer.d.ts +28 -0
  39. package/dist/tokenizer/default-tokenizer.d.ts.map +1 -0
  40. package/dist/tokenizer/ovp.d.ts +12 -0
  41. package/dist/tokenizer/ovp.d.ts.map +1 -0
  42. package/dist/tokenizer/types.d.ts +27 -0
  43. package/dist/tokenizer/types.d.ts.map +1 -0
  44. package/package.json +77 -0
  45. package/scripts/check-architecture.js +24 -0
  46. package/scripts/check-bundle-size.js +12 -0
  47. package/scripts/check-package.js +24 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - bff614c: Initial release of the headless RSVP engine with drift-corrected playback, Unicode-aware tokenization, configurable OVP calculation, typed events, and dependency-injection boundaries.
@@ -0,0 +1,52 @@
1
+ # Contributing to `@rsvp-engine/core`
2
+
3
+ Follow the workspace workflow in [`../../CONTRIBUTING.md`](../../CONTRIBUTING.md) and the Core constraints in [`AGENTS.md`](./AGENTS.md). This guide covers only Core-specific practices.
4
+
5
+ ## TypeScript conventions
6
+
7
+ - Use `interface` for object contracts and `type` for unions and primitives.
8
+ - Declare explicit return types on public functions and methods to prevent accidental contract changes.
9
+ - Mark immutable fields and properties `readonly`.
10
+ - Use ECMAScript `#privateField` syntax for runtime encapsulation.
11
+ - Use `PascalCase` for classes and interfaces, `camelCase` for methods and variables, and `UPPER_SNAKE_CASE` for global constants.
12
+
13
+ ## Test strategy
14
+
15
+ Use Vitest and write the behavior-defining test before its implementation.
16
+
17
+ ### Required coverage
18
+
19
+ - Cover every valid state transition and invalid transition behavior.
20
+ - Use `vi.useFakeTimers()` for time-dependent tests. Simulate late timer callbacks to verify drift recovery.
21
+ - Cover tokenizer boundaries, including empty and whitespace-only input, punctuation, and non-string `Token<T>` arrays.
22
+ - Restore mocks and timers and remove event listeners after each test.
23
+
24
+ ## Verification
25
+
26
+ Run from the repository root:
27
+
28
+ ```bash
29
+ pnpm --filter @rsvp-engine/core build
30
+ pnpm --filter @rsvp-engine/core test
31
+ pnpm --filter @rsvp-engine/core test:coverage
32
+ pnpm lint
33
+ pnpm typecheck
34
+ ```
35
+
36
+ Before submitting a change, confirm that:
37
+
38
+ - the minified and gzipped bundle remains below 5 KB;
39
+ - line and branch coverage remain at least 95%;
40
+ - timing tests keep simulated one-minute drift below 10 ms;
41
+ - public interfaces, classes, and methods have TSDoc comments.
42
+
43
+ ## Packaging
44
+
45
+ `tsup` produces CommonJS, ESM, and declaration outputs. Before a release, verify these manifest entries:
46
+
47
+ - `main`: `./dist/index.cjs` (CJS)
48
+ - `module`: `./dist/index.js` (ESM)
49
+ - `types`: `./dist/index.d.ts` (Types)
50
+ - `files`: an explicit allowlist of consumer-facing artifacts; exclude source, tests, caches, and repository-only `docs/` content.
51
+
52
+ Use the root contribution guide for Changesets, commit, pull request, and release rules.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 RSVP Engine contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,69 @@
1
+ # `@rsvp-engine/core`
2
+
3
+ > A headless, zero-dependency engine for Rapid Serial Visual Presentation (RSVP).
4
+
5
+ `@rsvp-engine/core` provides deterministic playback state, drift-corrected scheduling, Unicode-aware tokenization, OVP calculation, and typed events without depending on a UI or DOM.
6
+
7
+ ## Features
8
+
9
+ - Runs in browsers, Node.js, Web Workers, and React Native.
10
+ - Ships ESM and CommonJS builds with TypeScript declarations.
11
+ - Emits the first item immediately and preserves remaining display time across pause/resume.
12
+ - Corrects ordinary timer drift and rebases after severe lag to avoid unreadable catch-up bursts.
13
+ - Uses `Intl.Segmenter` when available, with dependency-free fallbacks.
14
+ - Supports custom tokenizers, OVP strategies, schedulers, time drivers, and generic item types.
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ pnpm add @rsvp-engine/core
20
+ # npm install @rsvp-engine/core
21
+ # yarn add @rsvp-engine/core
22
+ ```
23
+
24
+ ## Quick start
25
+
26
+ ```typescript
27
+ import { RSVPEngine } from "@rsvp-engine/core";
28
+
29
+ const engine = new RSVPEngine({
30
+ data: "Rapid Serial Visual Presentation powers modern speed reading.",
31
+ wpm: 300,
32
+ });
33
+
34
+ engine.on("itemChange", ({ item, progress, reason }) => {
35
+ console.log(item.value, item.ovpIndex, progress, reason);
36
+ });
37
+
38
+ engine.on("complete", ({ totalItems }) => {
39
+ console.log(`Completed ${totalItems} items`);
40
+ });
41
+
42
+ engine.on("error", ({ error }) => {
43
+ console.error(error);
44
+ });
45
+
46
+ engine.play();
47
+ ```
48
+
49
+ The returned function unsubscribes a listener:
50
+
51
+ ```typescript
52
+ const unsubscribe = engine.on("stateChange", ({ current }) => console.log(current));
53
+ unsubscribe();
54
+ ```
55
+
56
+ Asynchronous tokenization stays outside the synchronous engine:
57
+
58
+ ```typescript
59
+ const tokens = await tokenizeWithYourService(text);
60
+ engine.loadTokens(tokens);
61
+ ```
62
+
63
+ ## Contributing
64
+
65
+ See the [contributor guidelines](./CONTRIBUTING.md) to develop and verify changes locally.
66
+
67
+ ## License
68
+
69
+ [MIT](./LICENSE)
@@ -0,0 +1,30 @@
1
+ import type { SchedulerStrategy, TimeDriver } from "../scheduler/types";
2
+ import type { TokenizerStrategy } from "../tokenizer/types";
3
+ /**
4
+ * Configuration options for the RSVP Engine.
5
+ */
6
+ export interface RSVPEngineOptions<T = string> {
7
+ /** Raw text string, single generic item, or generic item array. */
8
+ readonly data?: T | T[];
9
+ /** Target speed in Words Per Minute. Defaults to 300. */
10
+ readonly wpm?: number;
11
+ /** Direct ms-per-item override. Takes precedence over `wpm`. */
12
+ readonly msPerItem?: number;
13
+ /** Custom tokenizer strategy. Defaults to `DefaultTokenizer`. */
14
+ readonly tokenizer?: TokenizerStrategy<T>;
15
+ /** Custom scheduler strategy. Defaults to `DriftCorrectedScheduler`. */
16
+ readonly scheduler?: SchedulerStrategy;
17
+ /** Clock used to preserve remaining display time across pause/resume. */
18
+ readonly timeDriver?: TimeDriver;
19
+ }
20
+ /** Minimum allowed playback speed in Words Per Minute. */
21
+ export declare const MIN_WPM = 1;
22
+ /** Maximum allowed playback speed in Words Per Minute. */
23
+ export declare const MAX_WPM = 6000;
24
+ /** Minimum allowed display interval per item in milliseconds. */
25
+ export declare const MIN_MS_PER_ITEM: number;
26
+ /** Maximum allowed display interval per item in milliseconds. */
27
+ export declare const MAX_MS_PER_ITEM: number;
28
+ /** Default playback speed in Words Per Minute. */
29
+ export declare const DEFAULT_WPM = 300;
30
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/engine/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACxE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D;;GAEG;AACH,MAAM,WAAW,iBAAiB,CAAC,CAAC,GAAG,MAAM;IAC3C,mEAAmE;IACnE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;IACxB,yDAAyD;IACzD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,gEAAgE;IAChE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,iEAAiE;IACjE,QAAQ,CAAC,SAAS,CAAC,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC;IAC1C,wEAAwE;IACxE,QAAQ,CAAC,SAAS,CAAC,EAAE,iBAAiB,CAAC;IACvC,yEAAyE;IACzE,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC;CAClC;AAED,0DAA0D;AAC1D,eAAO,MAAM,OAAO,IAAI,CAAC;AACzB,0DAA0D;AAC1D,eAAO,MAAM,OAAO,OAAQ,CAAC;AAC7B,iEAAiE;AACjE,eAAO,MAAM,eAAe,QAAmB,CAAC;AAChD,iEAAiE;AACjE,eAAO,MAAM,eAAe,QAAmB,CAAC;AAChD,kDAAkD;AAClD,eAAO,MAAM,WAAW,MAAM,CAAC"}
@@ -0,0 +1,90 @@
1
+ import type { RSVPEngineOptions } from "./config";
2
+ import type { RSVPItem, RSVPSnapshot } from "./types";
3
+ import type { EventCallback, RSVPEventMap, RSVPEventType, UnsubscribeFn } from "../events/types";
4
+ import type { RSVPState } from "../state/types";
5
+ import type { Token } from "../tokenizer/types";
6
+ /**
7
+ * Headless RSVP Engine — orchestrates state machine, scheduler, tokenizer, and event emitter.
8
+ * @typeParam T - The type of items being presented (defaults to `string`).
9
+ */
10
+ export declare class RSVPEngine<T = string> {
11
+ #private;
12
+ constructor(options?: RSVPEngineOptions<T>);
13
+ /**
14
+ * Tokenizes and loads raw data into the engine using the configured tokenizer.
15
+ */
16
+ load(data: T | T[]): void;
17
+ /**
18
+ * Directly loads pre-tokenized items into the engine.
19
+ * Useful when tokenization is performed externally (e.g. AI or asynchronous tokenizers).
20
+ */
21
+ loadTokens(tokens: Token<T>[]): void;
22
+ /**
23
+ * Starts or resumes playback.
24
+ * From STOPPED/COMPLETED: resets index to 0 and starts from beginning.
25
+ * From PAUSED: resumes from current position.
26
+ */
27
+ play(): void;
28
+ /**
29
+ * Pauses playback, preserving the current index.
30
+ */
31
+ pause(): void;
32
+ /**
33
+ * Stops playback and resets the index to 0.
34
+ */
35
+ stop(): void;
36
+ /**
37
+ * Jumps to a specific token index. Transitions to PAUSED state.
38
+ * Only available from PAUSED, STOPPED, or COMPLETED states.
39
+ */
40
+ seek(index: number): void;
41
+ /**
42
+ * Advances to the next token. Only available from PAUSED state.
43
+ */
44
+ next(): void;
45
+ /**
46
+ * Retreats to the previous token. Only available from PAUSED state.
47
+ */
48
+ previous(): void;
49
+ /**
50
+ * Resets engine to IDLE state, clearing all data.
51
+ * Only valid from ERROR state.
52
+ */
53
+ reset(): void;
54
+ /**
55
+ * Clears all timers, removes listeners, and disposes the engine.
56
+ * Required for SPA component unmount cleanup.
57
+ */
58
+ destroy(): void;
59
+ /**
60
+ * Updates playback speed via WPM (live, mid-playback).
61
+ */
62
+ setSpeed(wpm: number): void;
63
+ /**
64
+ * Directly sets ms-per-item interval (live, mid-playback).
65
+ */
66
+ setMsPerItem(ms: number): void;
67
+ /** Current engine state. */
68
+ get state(): RSVPState;
69
+ /** Current WPM computed from msPerItem. */
70
+ get wpm(): number;
71
+ /** Current ms-per-item interval. */
72
+ get msPerItem(): number;
73
+ /** Current token index (0-based). */
74
+ get currentIndex(): number;
75
+ /** Current token/item being displayed, or null if none. */
76
+ get currentItem(): RSVPItem<T> | null;
77
+ /** Playback progress (0–1). */
78
+ get progress(): number;
79
+ /** Total number of tokens/items. */
80
+ get totalItems(): number;
81
+ /**
82
+ * Subscribes to an engine event. Returns an unsubscribe function.
83
+ */
84
+ on<K extends RSVPEventType>(event: K, callback: EventCallback<RSVPEventMap<T>[K]>): UnsubscribeFn;
85
+ /**
86
+ * Returns a frozen, readonly snapshot of the full engine state.
87
+ */
88
+ snapshot(): RSVPSnapshot<T>;
89
+ }
90
+ //# sourceMappingURL=rsvp-engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rsvp-engine.d.ts","sourceRoot":"","sources":["../../src/engine/rsvp-engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEjG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,KAAK,EAAqB,MAAM,oBAAoB,CAAC;AAYnE;;;GAGG;AACH,qBAAa,UAAU,CAAC,CAAC,GAAG,MAAM;;gBAcpB,OAAO,GAAE,iBAAiB,CAAC,CAAC,CAAM;IAuB9C;;OAEG;IACI,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI;IAchC;;;OAGG;IACI,UAAU,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI;IAyH3C;;;;OAIG;IACI,IAAI,IAAI,IAAI;IAyCnB;;OAEG;IACI,KAAK,IAAI,IAAI;IAuBpB;;OAEG;IACI,IAAI,IAAI,IAAI;IAwBnB;;;OAGG;IACI,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAoChC;;OAEG;IACI,IAAI,IAAI,IAAI;IAuBnB;;OAEG;IACI,QAAQ,IAAI,IAAI;IAuBvB;;;OAGG;IACI,KAAK,IAAI,IAAI;IAyBpB;;;OAGG;IACI,OAAO,IAAI,IAAI;IAatB;;OAEG;IACI,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAMlC;;OAEG;IACI,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAQrC,4BAA4B;IAC5B,IAAW,KAAK,IAAI,SAAS,CAE5B;IAED,2CAA2C;IAC3C,IAAW,GAAG,IAAI,MAAM,CAEvB;IAED,oCAAoC;IACpC,IAAW,SAAS,IAAI,MAAM,CAE7B;IAED,qCAAqC;IACrC,IAAW,YAAY,IAAI,MAAM,CAEhC;IAED,2DAA2D;IAC3D,IAAW,WAAW,IAAI,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI,CAE3C;IAED,+BAA+B;IAC/B,IAAW,QAAQ,IAAI,MAAM,CAK5B;IAED,oCAAoC;IACpC,IAAW,UAAU,IAAI,MAAM,CAE9B;IAID;;OAEG;IACI,EAAE,CAAC,CAAC,SAAS,aAAa,EAC/B,KAAK,EAAE,CAAC,EACR,QAAQ,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAC1C,aAAa;IAOhB;;OAEG;IACI,QAAQ,IAAI,YAAY,CAAC,CAAC,CAAC;CAYnC"}
@@ -0,0 +1,28 @@
1
+ import type { RSVPState } from "../state/types";
2
+ /**
3
+ * A single presentable item in the RSVP sequence.
4
+ * @typeParam T - The type of the item value (defaults to `string`).
5
+ */
6
+ export interface RSVPItem<T = string> {
7
+ /** The raw value of the item. */
8
+ readonly value: T;
9
+ /** Zero-based index within the token sequence. */
10
+ readonly index: number;
11
+ /** Optimal Viewing Position character index within the value. */
12
+ readonly ovpIndex: number;
13
+ /** Multiplier applied to msPerItem for this token (e.g. 2.0 for sentence-ending punctuation). */
14
+ readonly delayMultiplier: number;
15
+ }
16
+ /**
17
+ * A readonly snapshot of the full engine state at a point in time.
18
+ */
19
+ export interface RSVPSnapshot<T = string> {
20
+ readonly state: RSVPState;
21
+ readonly currentIndex: number;
22
+ readonly currentItem: RSVPItem<T> | null;
23
+ readonly progress: number;
24
+ readonly totalItems: number;
25
+ readonly wpm: number;
26
+ readonly msPerItem: number;
27
+ }
28
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/engine/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD;;;GAGG;AACH,MAAM,WAAW,QAAQ,CAAC,CAAC,GAAG,MAAM;IAClC,iCAAiC;IACjC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;IAClB,kDAAkD;IAClD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,iEAAiE;IACjE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,iGAAiG;IACjG,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,MAAM;IACtC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IACzC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B"}
@@ -0,0 +1,5 @@
1
+ import type { Token } from "../tokenizer/types";
2
+ export declare function validateTokens<T>(tokens: Token<T>[]): void;
3
+ export declare function validateWpm(wpm: number): void;
4
+ export declare function validateMsPerItem(ms: number): void;
5
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/engine/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAMhD,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,CAmB1D;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAI7C;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAMlD"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Thrown when an invalid state transition is attempted.
3
+ *
4
+ */
5
+ export declare class InvalidTransitionError extends Error {
6
+ readonly from: string;
7
+ readonly action: string;
8
+ constructor(from: string, action: string);
9
+ }
10
+ /**
11
+ * Thrown when a method is called on a destroyed engine instance.
12
+ */
13
+ export declare class EngineDestroyedError extends Error {
14
+ constructor();
15
+ }
16
+ /**
17
+ * Thrown when invalid input is provided to the engine (empty text, null data, etc.).
18
+ */
19
+ export declare class InvalidInputError extends Error {
20
+ constructor(message: string);
21
+ }
22
+ /**
23
+ * Thrown when a seek index is out of bounds.
24
+ */
25
+ export declare class IndexOutOfBoundsError extends Error {
26
+ readonly index: number;
27
+ readonly totalItems: number;
28
+ constructor(index: number, totalItems: number);
29
+ }
30
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,SAAgB,IAAI,EAAE,MAAM,CAAC;IAC7B,SAAgB,MAAM,EAAE,MAAM,CAAC;gBAEnB,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAMzC;AAED;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;;CAK9C;AAED;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,OAAO,EAAE,MAAM;CAI5B;AAED;;GAEG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,SAAgB,KAAK,EAAE,MAAM,CAAC;IAC9B,SAAgB,UAAU,EAAE,MAAM,CAAC;gBAEvB,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM;CAM9C"}
@@ -0,0 +1,8 @@
1
+ import type { EventCallback, RSVPEventMap, RSVPEventType, UnsubscribeFn } from "./types";
2
+ export declare class EventEmitter<T = string> {
3
+ #private;
4
+ on<K extends RSVPEventType>(event: K, callback: EventCallback<RSVPEventMap<T>[K]>): UnsubscribeFn;
5
+ emit<K extends RSVPEventType>(event: K, payload: RSVPEventMap<T>[K]): void;
6
+ removeAllListeners(): void;
7
+ }
8
+ //# sourceMappingURL=event-emitter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-emitter.d.ts","sourceRoot":"","sources":["../../src/events/event-emitter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAMzF,qBAAa,YAAY,CAAC,CAAC,GAAG,MAAM;;IAQ3B,EAAE,CAAC,CAAC,SAAS,aAAa,EAC/B,KAAK,EAAE,CAAC,EACR,QAAQ,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAC1C,aAAa;IAST,IAAI,CAAC,CAAC,SAAS,aAAa,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI;IAkB1E,kBAAkB,IAAI,IAAI;CAKlC"}
@@ -0,0 +1,31 @@
1
+ import type { RSVPItem } from "../engine/types";
2
+ import type { RSVPState } from "../state/types";
3
+ /** Function that removes an event listener when called. */
4
+ export type UnsubscribeFn = () => void;
5
+ export type RSVPEventType = "stateChange" | "itemChange" | "complete" | "error";
6
+ export interface StateChangePayload {
7
+ readonly previous: RSVPState;
8
+ readonly current: RSVPState;
9
+ }
10
+ export type ItemChangeReason = "playback" | "seek" | "next" | "previous";
11
+ export interface ItemChangePayload<T = string> {
12
+ readonly item: RSVPItem<T>;
13
+ readonly index: number;
14
+ readonly progress: number;
15
+ readonly reason: ItemChangeReason;
16
+ }
17
+ export interface CompletePayload<T = string> {
18
+ readonly item: RSVPItem<T>;
19
+ readonly totalItems: number;
20
+ }
21
+ export interface ErrorPayload {
22
+ readonly error: Error;
23
+ }
24
+ export interface RSVPEventMap<T = string> {
25
+ stateChange: StateChangePayload;
26
+ itemChange: ItemChangePayload<T>;
27
+ complete: CompletePayload<T>;
28
+ error: ErrorPayload;
29
+ }
30
+ export type EventCallback<P = unknown> = (payload: P) => void;
31
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/events/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,2DAA2D;AAC3D,MAAM,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC;AAEvC,MAAM,MAAM,aAAa,GAAG,aAAa,GAAG,YAAY,GAAG,UAAU,GAAG,OAAO,CAAC;AAEhF,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC;CAC7B;AAED,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,CAAC;AAEzE,MAAM,WAAW,iBAAiB,CAAC,CAAC,GAAG,MAAM;IAC3C,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;CACnC;AAED,MAAM,WAAW,eAAe,CAAC,CAAC,GAAG,MAAM;IACzC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;CACvB;AAED,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,MAAM;IACtC,WAAW,EAAE,kBAAkB,CAAC;IAChC,UAAU,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC;IACjC,QAAQ,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC;IAC7B,KAAK,EAAE,YAAY,CAAC;CACrB;AAED,MAAM,MAAM,aAAa,CAAC,CAAC,GAAG,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,IAAI,CAAC"}
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ 'use strict';var v=1,S=6e3,T=10,b=6e4,E=300;var u=class extends Error{from;action;constructor(e,t){super(`Invalid transition: cannot perform '${t}' from '${e}' state.`),this.name="InvalidTransitionError",this.from=e,this.action=t;}},p=class extends Error{constructor(){super("Cannot interact with a destroyed engine instance."),this.name="EngineDestroyedError";}},o=class extends Error{constructor(e){super(e),this.name="InvalidInputError";}},m=class extends Error{index;totalItems;constructor(e,t){super(`Index ${e} is out of bounds (0..${t-1}).`),this.name="IndexOutOfBoundsError",this.index=e,this.totalItems=t;}};var c=class{#e={stateChange:new Set,itemChange:new Set,complete:new Set,error:new Set};on(e,t){let r=this.#e[e];return r.add(t),()=>{r.delete(t);}}emit(e,t){let r=this.#e[e],n=Array.from(r);for(let s of n)try{s(t);}catch(l){if(e!=="error"){let a=l instanceof Error?l:new Error(String(l));this.emit("error",{error:a});}}}removeAllListeners(){for(let e of Object.values(this.#e))e.clear();}};var h=class{now(){let e=globalThis;return typeof e.performance?.now=="function"?e.performance.now():Date.now()}setTimeout(e,t){let r=globalThis;if(typeof r.setTimeout!="function")throw new TypeError("The runtime does not provide setTimeout.");return r.setTimeout(e,t)}clearTimeout(e){let t=globalThis;if(typeof t.clearTimeout!="function")throw new TypeError("The runtime does not provide clearTimeout.");t.clearTimeout(e);}};var d=class{#e;#t=null;#n=null;constructor(e=new h){this.#e=e;}schedule(e,t){if(!Number.isFinite(t)||t<=0)throw new RangeError("delayMs must be a positive finite number.");this.#t!==null&&this.cancel();let r=this.#e.now(),n=t;if(this.#n===null)this.#n=r+t;else {let s=r-this.#n;s>=t?this.#n=r+t:(this.#n+=t,n=Math.max(t/2,t-s));}this.#t=this.#e.setTimeout(()=>{this.#t=null,e();},n);}cancel(){this.#u(),this.#n=null;}#u(){this.#t!==null&&(this.#e.clearTimeout(this.#t),this.#t=null);}};var f=class{#e="IDLE";#t={IDLE:{load:"IDLE",play:"PLAYING",error:"ERROR"},PLAYING:{pause:"PAUSED",stop:"STOPPED",complete:"COMPLETED",error:"ERROR"},PAUSED:{load:"IDLE",play:"PLAYING",stop:"STOPPED",seek:"PAUSED",error:"ERROR"},STOPPED:{load:"IDLE",play:"PLAYING",seek:"PAUSED",error:"ERROR"},COMPLETED:{load:"IDLE",play:"PLAYING",seek:"PAUSED",error:"ERROR"},ERROR:{reset:"IDLE"}};get state(){return this.#e}transition(e){let r=this.#t[this.#e][e];if(!r)throw new u(this.#e,e);return this.#e=r,this.#e}};var y=class{calculate(e){if(!e)return 0;let t=w(e),r=t.filter(({segment:s})=>!x(s));if(r.length===0)return t[0].index;let n=r.length<=3?0:Math.floor(r.length*.25);return r[n].index}};function w(i){let e=globalThis.Intl?.Segmenter;if(typeof e=="function")return Array.from(new e(void 0,{granularity:"grapheme"}).segment(i));let t=[],r=0;for(let n of i){let s=t.at(-1);s&&(/^[\p{M}\uFE0F]$/u.test(n)||n==="\u200D"||s.segment.endsWith("\u200D"))?t[t.length-1]={index:s.index,segment:s.segment+n}:t.push({index:r,segment:n}),r+=n.length;}return t}function x(i){return /^[\p{P}\p{Z}]+$/u.test(i)}var g=class{#e;#t;#n;#u;#c;constructor(e={}){this.#e=e.sentenceDelay??2,this.#t=e.clauseDelay??1.5,this.#n=e.dashDelay??1.3,this.#u=e.nestedTokenize??false,this.#c=e.ovpStrategy??new y,this.#l();}tokenize(e){return e==null?[]:Array.isArray(e)?this.#i(e):typeof e=="string"?this.#r(e):[{value:e,ovpIndex:0,delayMultiplier:this.#s(e)}]}#i(e){return this.#u?e.flatMap(t=>this.tokenize(t)):e.map(t=>({value:t,ovpIndex:typeof t=="string"?this.#c.calculate(t):0,delayMultiplier:this.#s(t)}))}#r(e){let t=e.trim();return t?this.#o(t).map(n=>({value:n,ovpIndex:this.#c.calculate(n),delayMultiplier:this.#s(n)})):[]}#s(e){return typeof e=="string"?/[.!?。!?…]+[\p{Pe}\p{Pf}"']*$/u.test(e)?this.#e:/[,;:,、;:]+[\p{Pe}\p{Pf}"']*$/u.test(e)?this.#t:/\p{Pd}+[\p{Pe}\p{Pf}"']*$/u.test(e)?this.#n:1:1}#l(){let e=[["sentenceDelay",this.#e],["clauseDelay",this.#t],["dashDelay",this.#n]];for(let[t,r]of e)if(!Number.isFinite(r)||r<=0)throw new o(`${t} must be a positive finite number.`)}#o(e){let t=globalThis.Intl?.Segmenter;if(typeof t!="function")return e.split(/\s+/);let r=new t(void 0,{granularity:"word"}),n=[],s="",l=true;for(let a of r.segment(e))/^\s+$/u.test(a.segment)?(s&&n.push(s),s="",l=true):a.isWordLike||!M(a.segment)?(n.push(s+a.segment),s="",l=false):l||s||/^[\p{Ps}\p{Pi}]+$/u.test(a.segment)?s+=a.segment:n[n.length-1]+=a.segment;return s&&n.push(s),n}};function M(i){return /^\p{P}+$/u.test(i)}function I(i){if(!Array.isArray(i))throw new o("tokens must be an array.");for(let[e,t]of i.entries()){if(typeof t!="object"||t===null)throw new o(`Token ${e} must be an object.`);if(!Number.isInteger(t.ovpIndex)||t.ovpIndex<0)throw new o(`Token ${e} has an invalid ovpIndex.`);if(typeof t.value=="string"&&t.ovpIndex>t.value.length)throw new o(`Token ${e} has an out-of-range ovpIndex.`);if(!Number.isFinite(t.delayMultiplier)||t.delayMultiplier<=0)throw new o(`Token ${e} has an invalid delayMultiplier.`)}}function D(i){if(!Number.isFinite(i)||i<1||i>6e3)throw new o(`wpm must be a finite number between ${1} and ${6e3}.`)}function R(i){if(!Number.isFinite(i)||i<10||i>6e4)throw new o(`msPerItem must be a finite number between ${10} and ${6e4}.`)}var P=class{#e;#t;#n;#u;#c;#i=[];#r=0;#s=false;#l=null;#o=null;#h=6e4/300;#d=false;constructor(e={}){this.#e=new f,this.#t=new c,this.#u=e.timeDriver??new h,this.#n=e.scheduler??new d(this.#u),this.#c=e.tokenizer??new g,e.msPerItem!==void 0?this.setMsPerItem(e.msPerItem):e.wpm!==void 0&&this.setSpeed(e.wpm),e.data!==void 0&&this.load(e.data);}load(e){this.#a(),this.#f();try{this.loadTokens(this.#c.tokenize(e));}catch(t){throw t instanceof o||this.#y(this.#m(t)),t}}loadTokens(e){this.#a(),this.#f(),I(e);let t=this.#e.state;this.#e.transition("load"),this.#n.cancel(),this.#S(e),this.#r=0,this.#s=false,this.#l=null,this.#o=null,t!=="IDLE"&&this.#t.emit("stateChange",{previous:t,current:"IDLE"});}#f(){if(this.#e.state==="PLAYING"||this.#e.state==="ERROR")throw new o(`Cannot load data while engine is ${this.#e.state}.`)}#S(e){this.#i=e.map((t,r)=>({value:t.value,index:r,ovpIndex:t.ovpIndex,delayMultiplier:t.delayMultiplier}));}#y(e){let t=this.#e.state;try{this.#e.transition("error");}catch{this.#p(e);return}this.#n.cancel(),this.#l=null,this.#o=null,this.#p(e),this.#t.emit("stateChange",{previous:t,current:"ERROR"});}#p(e){this.#t.emit("error",{error:e});}#m(e){return e instanceof Error?e:new Error(String(e))}#a(){if(this.#d)throw new p}#T=()=>{if(this.#l=null,this.#o=null,this.#e.state==="PLAYING"){if(this.#r>=this.#i.length-1){this.#b();return}this.#r++,this.#g();}};#g(){let e=this.#i[this.#r];this.#s=true,this.#t.emit("itemChange",{item:e,index:this.#r,progress:this.progress,reason:"playback"}),this.#e.state==="PLAYING"&&this.#v(this.#h*e.delayMultiplier);}#v(e){this.#o=null,this.#l=this.#u.now()+e;try{this.#n.schedule(this.#T,e);}catch(t){this.#y(this.#m(t));}}#b(){this.#n.cancel();let e=this.#e.state;this.#e.transition("complete");let t=this.#i[this.#r];this.#t.emit("stateChange",{previous:e,current:"COMPLETED"}),this.#t.emit("complete",{item:t,totalItems:this.#i.length});}play(){this.#a(),this.#P();}#P(){if(this.#i.length===0){this.#p(new o("Cannot play: no tokens loaded."));return}let e=this.#e.state;(e==="STOPPED"||e==="COMPLETED")&&(this.#r=0);try{this.#e.transition("play");}catch(t){this.#p(this.#m(t));return}if(this.#t.emit("stateChange",{previous:e,current:"PLAYING"}),e==="PAUSED"&&this.#s){let t=this.#i[this.#r];this.#v(this.#o??this.#h*t.delayMultiplier);return}this.#s=false,this.#g();}pause(){this.#a();let e=this.#e.state;try{this.#e.transition("pause");}catch(t){this.#p(this.#m(t));return}this.#o=this.#l===null?null:Math.max(0,this.#l-this.#u.now()),this.#n.cancel(),this.#l=null,this.#t.emit("stateChange",{previous:e,current:"PAUSED"});}stop(){this.#a();let e=this.#e.state;try{this.#e.transition("stop");}catch(t){this.#p(this.#m(t));return}this.#n.cancel(),this.#r=0,this.#s=false,this.#l=null,this.#o=null,this.#t.emit("stateChange",{previous:e,current:"STOPPED"});}seek(e){if(this.#a(),e<0||e>=this.#i.length){this.#p(new m(e,this.#i.length));return}let t=this.#e.state;try{this.#e.transition("seek");}catch(r){this.#p(this.#m(r));return}this.#r=e,this.#s=true,this.#o=this.#h*this.#i[e].delayMultiplier,t!=="PAUSED"&&this.#t.emit("stateChange",{previous:t,current:"PAUSED"}),this.#t.emit("itemChange",{item:this.#i[e],index:e,progress:this.progress,reason:"seek"});}next(){if(this.#a(),this.#e.state!=="PAUSED"){this.#t.emit("error",{error:new o("next() is only available in PAUSED state.")});return}this.#r<this.#i.length-1&&(this.#r++,this.#s=true,this.#o=this.#h*this.#i[this.#r].delayMultiplier,this.#t.emit("itemChange",{item:this.#i[this.#r],index:this.#r,progress:this.progress,reason:"next"}));}previous(){if(this.#a(),this.#e.state!=="PAUSED"){this.#t.emit("error",{error:new o("previous() is only available in PAUSED state.")});return}this.#r>0&&(this.#r--,this.#s=true,this.#o=this.#h*this.#i[this.#r].delayMultiplier,this.#t.emit("itemChange",{item:this.#i[this.#r],index:this.#r,progress:this.progress,reason:"previous"}));}reset(){this.#a();let e=this.#e.state;try{this.#e.transition("reset");}catch(t){this.#p(this.#m(t));return}this.#n.cancel(),this.#i=[],this.#r=0,this.#s=false,this.#l=null,this.#o=null,this.#t.emit("stateChange",{previous:e,current:"IDLE"});}destroy(){this.#d||(this.#n.cancel(),this.#l=null,this.#o=null,this.#t.removeAllListeners(),this.#d=true);}setSpeed(e){this.#a(),D(e),this.#h=6e4/e;}setMsPerItem(e){this.#a(),R(e),this.#h=e;}get state(){return this.#e.state}get wpm(){return 6e4/this.#h}get msPerItem(){return this.#h}get currentIndex(){return this.#r}get currentItem(){return this.#i[this.#r]??null}get progress(){return this.#i.length===0?0:this.#s?(this.#r+1)/this.#i.length:0}get totalItems(){return this.#i.length}on(e,t){return this.#a(),this.#t.on(e,t)}snapshot(){return this.#a(),Object.freeze({state:this.state,currentIndex:this.#r,currentItem:this.currentItem,progress:this.progress,totalItems:this.totalItems,wpm:this.wpm,msPerItem:this.#h})}};exports.DEFAULT_WPM=E;exports.DefaultOVPStrategy=y;exports.DefaultTokenizer=g;exports.DriftCorrectedScheduler=d;exports.EngineDestroyedError=p;exports.EventEmitter=c;exports.IndexOutOfBoundsError=m;exports.InvalidInputError=o;exports.InvalidTransitionError=u;exports.MAX_MS_PER_ITEM=b;exports.MAX_WPM=S;exports.MIN_MS_PER_ITEM=T;exports.MIN_WPM=v;exports.RSVPEngine=P;exports.StateMachine=f;exports.SystemTimeDriver=h;//# sourceMappingURL=index.cjs.map
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/engine/config.ts","../src/errors.ts","../src/events/event-emitter.ts","../src/scheduler/system-time-driver.ts","../src/scheduler/drift-corrected-scheduler.ts","../src/state/state-machine.ts","../src/tokenizer/ovp.ts","../src/tokenizer/default-tokenizer.ts","../src/engine/validation.ts","../src/engine/rsvp-engine.ts"],"names":["MIN_WPM","MAX_WPM","MIN_MS_PER_ITEM","MAX_MS_PER_ITEM","DEFAULT_WPM","InvalidTransitionError","from","action","EngineDestroyedError","InvalidInputError","message","IndexOutOfBoundsError","index","totalItems","EventEmitter","#listenerRegistry","event","callback","eventListeners","payload","listenersSnapshot","listener","err","error","SystemTimeDriver","runtimeGlobal","ms","handle","DriftCorrectedScheduler","#timeDriver","#timeoutHandle","#expectedTime","timeDriver","task","delayMs","now","actualDelay","drift","#clearTimer","StateMachine","#state","#transitions","nextState","DefaultOVPStrategy","text","graphemes","segmentGraphemes","content","segment","isPunctuation","target","Segmenter","previous","value","DefaultTokenizer","#sentenceDelay","#clauseDelay","#dashDelay","#nestedTokenize","#ovpStrategy","options","#validateOptions","input","#tokenizeArray","#tokenizeText","#calculateDelay","items","item","trimmed","#segmentWords","word","name","segmenter","tokens","prefix","atBoundary","part","validateTokens","token","validateWpm","wpm","validateMsPerItem","RSVPEngine","#stateMachine","#emitter","#scheduler","#tokenizer","#tokens","#currentIndex","#hasPresentedCurrent","#deadline","#remainingDelay","#msPerItem","#destroyed","data","#assertNotDestroyed","#assertCanLoad","#enterFatalError","#toError","previousState","#setTokens","#reportError","#advance","#completePlayback","#presentCurrent","#scheduleAdvance","delay","#startPlayback"],"mappings":"aAsBO,IAAMA,CAAAA,CAAU,CAAA,CAEVC,CAAAA,CAAU,GAAA,CAEVC,CAAAA,CAAkB,EAAA,CAElBC,CAAAA,CAAkB,GAAA,CAElBC,CAAAA,CAAc,IC1BpB,IAAMC,CAAAA,CAAN,cAAqC,KAAM,CAChC,IAAA,CACA,MAAA,CAEhB,WAAA,CAAYC,CAAAA,CAAcC,CAAAA,CAAgB,CACxC,MAAM,CAAA,oCAAA,EAAuCA,CAAM,CAAA,QAAA,EAAWD,CAAI,CAAA,QAAA,CAAU,CAAA,CAC5E,IAAA,CAAK,IAAA,CAAO,yBACZ,IAAA,CAAK,IAAA,CAAOA,CAAAA,CACZ,IAAA,CAAK,MAAA,CAASC,EAChB,CACF,CAAA,CAKaC,CAAAA,CAAN,cAAmC,KAAM,CAC9C,WAAA,EAAc,CACZ,KAAA,CAAM,mDAAmD,EACzD,IAAA,CAAK,IAAA,CAAO,uBACd,CACF,CAAA,CAKaC,CAAAA,CAAN,cAAgC,KAAM,CAC3C,WAAA,CAAYC,CAAAA,CAAiB,CAC3B,KAAA,CAAMA,CAAO,CAAA,CACb,IAAA,CAAK,IAAA,CAAO,oBACd,CACF,CAAA,CAKaC,CAAAA,CAAN,cAAoC,KAAM,CAC/B,KAAA,CACA,UAAA,CAEhB,WAAA,CAAYC,CAAAA,CAAeC,CAAAA,CAAoB,CAC7C,KAAA,CAAM,CAAA,MAAA,EAASD,CAAK,CAAA,sBAAA,EAAyBC,EAAa,CAAC,CAAA,EAAA,CAAI,CAAA,CAC/D,IAAA,CAAK,IAAA,CAAO,uBAAA,CACZ,IAAA,CAAK,KAAA,CAAQD,EACb,IAAA,CAAK,UAAA,CAAaC,EACpB,CACF,EC3CO,IAAMC,CAAAA,CAAN,KAA+B,CAC3BC,EAAAA,CAAyC,CAChD,WAAA,CAAa,IAAI,GAAA,CACjB,UAAA,CAAY,IAAI,GAAA,CAChB,SAAU,IAAI,GAAA,CACd,KAAA,CAAO,IAAI,GACb,CAAA,CAEO,EAAA,CACLC,CAAAA,CACAC,EACe,CACf,IAAMC,CAAAA,CAAiB,IAAA,CAAKH,EAAAA,CAAkBC,CAAK,CAAA,CACnD,OAAAE,EAAe,GAAA,CAAID,CAAQ,CAAA,CAEpB,IAAM,CACXC,CAAAA,CAAe,MAAA,CAAOD,CAAQ,EAChC,CACF,CAEO,IAAA,CAA8BD,CAAAA,CAAUG,CAAAA,CAAmC,CAChF,IAAMD,CAAAA,CAAiB,IAAA,CAAKH,EAAAA,CAAkBC,CAAK,CAAA,CAG7CI,CAAAA,CAAoB,KAAA,CAAM,IAAA,CAAKF,CAAc,EAEnD,IAAA,IAAWG,CAAAA,IAAYD,CAAAA,CACrB,GAAI,CACFC,CAAAA,CAASF,CAAO,EAClB,OAASG,CAAAA,CAAK,CACZ,GAAIN,CAAAA,GAAU,OAAA,CAAS,CACrB,IAAMO,CAAAA,CAAQD,aAAe,KAAA,CAAQA,CAAAA,CAAM,IAAI,KAAA,CAAM,MAAA,CAAOA,CAAG,CAAC,CAAA,CAChE,KAAK,IAAA,CAAK,OAAA,CAAS,CAAE,KAAA,CAAAC,CAAM,CAAC,EAC9B,CACF,CAEJ,CAEO,kBAAA,EAA2B,CAChC,IAAA,IAAWL,CAAAA,IAAkB,MAAA,CAAO,MAAA,CAAO,IAAA,CAAKH,EAAiB,CAAA,CAC/DG,CAAAA,CAAe,KAAA,GAEnB,CACF,EC1CO,IAAMM,CAAAA,CAAN,KAA6C,CAC3C,GAAA,EAAc,CACnB,IAAMC,CAAAA,CAAgB,UAAA,CACtB,OAAI,OAAOA,CAAAA,CAAc,WAAA,EAAa,GAAA,EAAQ,UAAA,CACrCA,CAAAA,CAAc,WAAA,CAAY,GAAA,EAAI,CAEhC,KAAK,GAAA,EACd,CAEO,UAAA,CAAWR,CAAAA,CAAsBS,CAAAA,CAAqB,CAC3D,IAAMD,EAAgB,UAAA,CACtB,GAAI,OAAOA,CAAAA,CAAc,UAAA,EAAe,UAAA,CACtC,MAAM,IAAI,UAAU,0CAA0C,CAAA,CAEhE,OAAOA,CAAAA,CAAc,UAAA,CAAWR,CAAAA,CAAUS,CAAE,CAC9C,CAEO,YAAA,CAAaC,CAAAA,CAAuB,CACzC,IAAMF,CAAAA,CAAgB,UAAA,CACtB,GAAI,OAAOA,EAAc,YAAA,EAAiB,UAAA,CACxC,MAAM,IAAI,SAAA,CAAU,4CAA4C,CAAA,CAElEA,CAAAA,CAAc,aAAaE,CAAM,EACnC,CACF,ECNO,IAAMC,CAAAA,CAAN,KAA2D,CACvDC,GACTC,EAAAA,CAA0B,IAAA,CAC1BC,EAAAA,CAA+B,IAAA,CAO/B,WAAA,CAAYC,CAAAA,CAAyB,IAAIR,CAAAA,CAAoB,CAC3D,IAAA,CAAKK,EAAAA,CAAcG,EACrB,CAYO,QAAA,CAASC,CAAAA,CAAkBC,CAAAA,CAAuB,CACvD,GAAI,CAAC,MAAA,CAAO,QAAA,CAASA,CAAO,CAAA,EAAKA,CAAAA,EAAW,CAAA,CAC1C,MAAM,IAAI,UAAA,CAAW,2CAA2C,CAAA,CAG9D,IAAA,CAAKJ,EAAAA,GAAmB,IAAA,EAC1B,IAAA,CAAK,QAAO,CAGd,IAAMK,CAAAA,CAAM,IAAA,CAAKN,EAAAA,CAAY,GAAA,EAAI,CAC7BO,CAAAA,CAAcF,CAAAA,CAElB,GAAI,IAAA,CAAKH,EAAAA,GAAkB,IAAA,CACzB,IAAA,CAAKA,EAAAA,CAAgBI,CAAAA,CAAMD,OACtB,CACL,IAAMG,CAAAA,CAAQF,CAAAA,CAAM,IAAA,CAAKJ,EAAAA,CACrBM,CAAAA,EAASH,CAAAA,CAEX,KAAKH,EAAAA,CAAgBI,CAAAA,CAAMD,CAAAA,EAE3B,IAAA,CAAKH,EAAAA,EAAiBG,CAAAA,CACtBE,CAAAA,CAAc,IAAA,CAAK,IAAIF,CAAAA,CAAU,CAAA,CAAGA,CAAAA,CAAUG,CAAK,CAAA,EAEvD,CAEA,IAAA,CAAKP,EAAAA,CAAiB,IAAA,CAAKD,EAAAA,CAAY,UAAA,CAAW,IAAM,CACtD,IAAA,CAAKC,EAAAA,CAAiB,IAAA,CACtBG,IACF,CAAA,CAAGG,CAAW,EAChB,CAQO,MAAA,EAAe,CACpB,IAAA,CAAKE,IAAY,CACjB,IAAA,CAAKP,EAAAA,CAAgB,KACvB,CAEAO,EAAAA,EAAoB,CACd,IAAA,CAAKR,KAAmB,IAAA,GAC1B,IAAA,CAAKD,EAAAA,CAAY,YAAA,CAAa,IAAA,CAAKC,EAAc,CAAA,CACjD,IAAA,CAAKA,GAAiB,IAAA,EAE1B,CACF,EC7FO,IAAMS,CAAAA,CAAN,KAAmB,CACxBC,EAAAA,CAAoB,OAEXC,EAAAA,CAAkF,CACzF,IAAA,CAAM,CACJ,IAAA,CAAM,MAAA,CACN,IAAA,CAAM,SAAA,CACN,MAAO,OACT,CAAA,CACA,OAAA,CAAS,CACP,KAAA,CAAO,QAAA,CACP,IAAA,CAAM,SAAA,CACN,SAAU,WAAA,CACV,KAAA,CAAO,OACT,CAAA,CACA,MAAA,CAAQ,CACN,IAAA,CAAM,MAAA,CACN,IAAA,CAAM,SAAA,CACN,IAAA,CAAM,SAAA,CACN,IAAA,CAAM,QAAA,CACN,KAAA,CAAO,OACT,EACA,OAAA,CAAS,CACP,IAAA,CAAM,MAAA,CACN,IAAA,CAAM,SAAA,CACN,IAAA,CAAM,QAAA,CACN,MAAO,OACT,CAAA,CACA,SAAA,CAAW,CACT,IAAA,CAAM,MAAA,CACN,IAAA,CAAM,SAAA,CACN,KAAM,QAAA,CACN,KAAA,CAAO,OACT,CAAA,CACA,KAAA,CAAO,CACL,KAAA,CAAO,MACT,CACF,CAAA,CAEA,IAAW,KAAA,EAAmB,CAC5B,OAAO,IAAA,CAAKD,EACd,CAEO,WAAWjC,CAAAA,CAAuC,CAEvD,IAAMmC,CAAAA,CADqB,IAAA,CAAKD,EAAAA,CAAa,IAAA,CAAKD,EAAM,EACnBjC,CAAM,CAAA,CAE3C,GAAI,CAACmC,CAAAA,CACH,MAAM,IAAIrC,CAAAA,CAAuB,KAAKmC,EAAAA,CAAQjC,CAAM,CAAA,CAGtD,OAAA,IAAA,CAAKiC,EAAAA,CAASE,CAAAA,CACP,IAAA,CAAKF,EACd,CACF,ECrDO,IAAMG,CAAAA,CAAN,KAAgD,CAK9C,SAAA,CAAUC,CAAAA,CAAsB,CACrC,GAAI,CAACA,CAAAA,CACH,OAAO,CAAA,CAGT,IAAMC,CAAAA,CAAYC,CAAAA,CAAiBF,CAAI,CAAA,CACjCG,CAAAA,CAAUF,CAAAA,CAAU,MAAA,CAAO,CAAC,CAAE,OAAA,CAAAG,CAAQ,IAAM,CAACC,CAAAA,CAAcD,CAAO,CAAC,CAAA,CAEzE,GAAID,CAAAA,CAAQ,MAAA,GAAW,EACrB,OAAOF,CAAAA,CAAU,CAAC,CAAA,CAAE,KAAA,CAGtB,IAAMK,CAAAA,CAASH,CAAAA,CAAQ,QAAU,CAAA,CAAI,CAAA,CAAI,IAAA,CAAK,KAAA,CAAMA,CAAAA,CAAQ,MAAA,CAAS,GAAI,CAAA,CACzE,OAAOA,CAAAA,CAAQG,CAAM,CAAA,CAAE,KACzB,CACF,EAOA,SAASJ,CAAAA,CAAiBF,EAA0B,CAClD,IAAMO,CAAAA,CAAY,UAAA,CAAW,IAAA,EAAM,SAAA,CACnC,GAAI,OAAOA,CAAAA,EAAc,UAAA,CACvB,OAAO,KAAA,CAAM,IAAA,CAAK,IAAIA,CAAAA,CAAU,MAAA,CAAW,CAAE,WAAA,CAAa,UAAW,CAAC,CAAA,CAAE,OAAA,CAAQP,CAAI,CAAC,CAAA,CAGvF,IAAMC,CAAAA,CAAwB,EAAC,CAC3BjC,CAAAA,CAAQ,CAAA,CACZ,IAAA,IAAWoC,CAAAA,IAAWJ,CAAAA,CAAM,CAC1B,IAAMQ,CAAAA,CAAWP,CAAAA,CAAU,EAAA,CAAG,EAAE,CAAA,CAE9BO,CAAAA,GACC,kBAAA,CAAmB,IAAA,CAAKJ,CAAO,CAAA,EAC9BA,CAAAA,GAAY,QAAA,EACZI,CAAAA,CAAS,OAAA,CAAQ,QAAA,CAAS,QAAQ,CAAA,CAAA,CAEpCP,CAAAA,CAAUA,CAAAA,CAAU,MAAA,CAAS,CAAC,CAAA,CAAI,CAChC,KAAA,CAAOO,EAAS,KAAA,CAChB,OAAA,CAASA,CAAAA,CAAS,OAAA,CAAUJ,CAC9B,CAAA,CAEAH,CAAAA,CAAU,IAAA,CAAK,CAAE,KAAA,CAAAjC,CAAAA,CAAO,OAAA,CAAAoC,CAAQ,CAAC,CAAA,CAEnCpC,CAAAA,EAASoC,CAAAA,CAAQ,OACnB,CACA,OAAOH,CACT,CAEA,SAASI,CAAAA,CAAcI,CAAAA,CAAwB,CAC7C,OAAO,kBAAA,CAAmB,IAAA,CAAKA,CAAK,CACtC,CClCO,IAAMC,CAAAA,CAAN,KAA0D,CACtDC,EAAAA,CACAC,EAAAA,CACAC,EAAAA,CACAC,EAAAA,CACAC,EAAAA,CAET,WAAA,CAAYC,CAAAA,CAAmC,EAAC,CAAG,CACjD,IAAA,CAAKL,EAAAA,CAAiBK,CAAAA,CAAQ,aAAA,EAAiB,CAAA,CAC/C,IAAA,CAAKJ,GAAeI,CAAAA,CAAQ,WAAA,EAAe,GAAA,CAC3C,IAAA,CAAKH,EAAAA,CAAaG,CAAAA,CAAQ,SAAA,EAAa,GAAA,CACvC,KAAKF,EAAAA,CAAkBE,CAAAA,CAAQ,cAAA,EAAkB,KAAA,CACjD,IAAA,CAAKD,EAAAA,CAAeC,CAAAA,CAAQ,WAAA,EAAe,IAAIjB,CAAAA,CAC/C,IAAA,CAAKkB,EAAAA,GACP,CAEO,QAAA,CAASC,CAAAA,CAA4B,CAC1C,OAAIA,CAAAA,EAAU,IAAA,CACL,EAAC,CAGN,KAAA,CAAM,OAAA,CAAQA,CAAK,CAAA,CACd,IAAA,CAAKC,EAAAA,CAAeD,CAAK,CAAA,CAG9B,OAAOA,CAAAA,EAAU,QAAA,CACZ,IAAA,CAAKE,GAAcF,CAAK,CAAA,CAG1B,CACL,CACE,KAAA,CAAOA,CAAAA,CACP,QAAA,CAAU,CAAA,CACV,gBAAiB,IAAA,CAAKG,EAAAA,CAAgBH,CAAK,CAC7C,CACF,CACF,CAEAC,EAAAA,CAAeG,EAAwB,CACrC,OAAI,IAAA,CAAKR,EAAAA,CACAQ,CAAAA,CAAM,OAAA,CAASC,CAAAA,EAAS,IAAA,CAAK,SAASA,CAAI,CAAC,CAAA,CAG7CD,CAAAA,CAAM,GAAA,CAAKC,CAAAA,GAAU,CAC1B,KAAA,CAAOA,EACP,QAAA,CAAU,OAAOA,CAAAA,EAAS,QAAA,CAAW,IAAA,CAAKR,EAAAA,CAAa,SAAA,CAAUQ,CAAI,EAAI,CAAA,CACzE,eAAA,CAAiB,IAAA,CAAKF,EAAAA,CAAgBE,CAAI,CAC5C,CAAA,CAAE,CACJ,CAEAH,EAAAA,CAAcpB,CAAAA,CAA+B,CAC3C,IAAMwB,CAAAA,CAAUxB,CAAAA,CAAK,IAAA,EAAK,CAC1B,OAAKwB,CAAAA,CAIS,IAAA,CAAKC,EAAAA,CAAcD,CAAO,CAAA,CAC3B,GAAA,CAAKE,CAAAA,GAAU,CAC1B,KAAA,CAAOA,CAAAA,CACP,QAAA,CAAU,IAAA,CAAKX,EAAAA,CAAa,SAAA,CAAUW,CAAI,CAAA,CAC1C,gBAAiB,IAAA,CAAKL,EAAAA,CAAgBK,CAAI,CAC5C,CAAA,CAAE,CAAA,CARO,EASX,CAEAL,EAAAA,CAAgBE,CAAAA,CAAuB,CACrC,OAAI,OAAOA,CAAAA,EAAS,QAAA,CACd,+BAAA,CAAgC,KAAKA,CAAI,CAAA,CACpC,IAAA,CAAKZ,EAAAA,CAEV,+BAAA,CAAgC,IAAA,CAAKY,CAAI,CAAA,CACpC,KAAKX,EAAAA,CAEV,4BAAA,CAA6B,IAAA,CAAKW,CAAI,CAAA,CACjC,IAAA,CAAKV,EAAAA,CAEP,CAAA,CAGF,CACT,CAEAI,EAAAA,EAAyB,CACvB,IAAMD,CAAAA,CAAU,CACd,CAAC,eAAA,CAAiB,KAAKL,EAAc,CAAA,CACrC,CAAC,aAAA,CAAe,IAAA,CAAKC,EAAY,CAAA,CACjC,CAAC,WAAA,CAAa,IAAA,CAAKC,EAAU,CAC/B,CAAA,CACA,IAAA,GAAW,CAACc,CAAAA,CAAMlB,CAAK,CAAA,GAAKO,CAAAA,CAC1B,GAAI,CAAC,MAAA,CAAO,QAAA,CAASP,CAAK,CAAA,EAAKA,GAAS,CAAA,CACtC,MAAM,IAAI5C,CAAAA,CAAkB,CAAA,EAAG8D,CAAI,CAAA,kCAAA,CAAoC,CAG7E,CAEAF,EAAAA,CAAczB,CAAAA,CAAwB,CACpC,IAAMO,CAAAA,CAAY,UAAA,CAAW,IAAA,EAAM,SAAA,CACnC,GAAI,OAAOA,CAAAA,EAAc,UAAA,CACvB,OAAOP,CAAAA,CAAK,KAAA,CAAM,KAAK,EAGzB,IAAM4B,CAAAA,CAAY,IAAIrB,CAAAA,CAAU,MAAA,CAAW,CAAE,WAAA,CAAa,MAAO,CAAC,CAAA,CAC5DsB,CAAAA,CAAmB,EAAC,CACtBC,CAAAA,CAAS,EAAA,CACTC,CAAAA,CAAa,IAAA,CACjB,QAAWC,CAAAA,IAAQJ,CAAAA,CAAU,OAAA,CAAQ5B,CAAI,CAAA,CACnC,QAAA,CAAS,IAAA,CAAKgC,CAAAA,CAAK,OAAO,CAAA,EACxBF,CAAAA,EACFD,CAAAA,CAAO,IAAA,CAAKC,CAAM,CAAA,CAEpBA,CAAAA,CAAS,GACTC,CAAAA,CAAa,IAAA,EACJC,CAAAA,CAAK,UAAA,EAAc,CAAC3B,CAAAA,CAAc2B,CAAAA,CAAK,OAAO,GACvDH,CAAAA,CAAO,IAAA,CAAKC,CAAAA,CAASE,CAAAA,CAAK,OAAO,CAAA,CACjCF,CAAAA,CAAS,EAAA,CACTC,EAAa,KAAA,EACJA,CAAAA,EAAcD,CAAAA,EAAU,oBAAA,CAAqB,IAAA,CAAKE,CAAAA,CAAK,OAAO,CAAA,CACvEF,GAAUE,CAAAA,CAAK,OAAA,CAEfH,CAAAA,CAAOA,CAAAA,CAAO,MAAA,CAAS,CAAC,CAAA,EAAKG,CAAAA,CAAK,QAGtC,OAAIF,CAAAA,EACFD,CAAAA,CAAO,IAAA,CAAKC,CAAM,CAAA,CAEbD,CACT,CACF,EAEA,SAASxB,CAAAA,CAAcI,CAAAA,CAAwB,CAC7C,OAAO,WAAA,CAAY,IAAA,CAAKA,CAAK,CAC/B,CCxJO,SAASwB,CAAAA,CAAkBJ,CAAAA,CAA0B,CAC1D,GAAI,CAAC,KAAA,CAAM,OAAA,CAAQA,CAAM,CAAA,CACvB,MAAM,IAAIhE,CAAAA,CAAkB,0BAA0B,CAAA,CAGxD,OAAW,CAACG,CAAAA,CAAOkE,CAAK,CAAA,GAAKL,CAAAA,CAAO,OAAA,EAAQ,CAAG,CAC7C,GAAI,OAAOK,CAAAA,EAAU,QAAA,EAAYA,CAAAA,GAAU,IAAA,CACzC,MAAM,IAAIrE,CAAAA,CAAkB,SAASG,CAAK,CAAA,mBAAA,CAAqB,CAAA,CAEjE,GAAI,CAAC,MAAA,CAAO,SAAA,CAAUkE,CAAAA,CAAM,QAAQ,CAAA,EAAKA,CAAAA,CAAM,QAAA,CAAW,CAAA,CACxD,MAAM,IAAIrE,CAAAA,CAAkB,CAAA,MAAA,EAASG,CAAK,CAAA,yBAAA,CAA2B,CAAA,CAEvE,GAAI,OAAOkE,CAAAA,CAAM,KAAA,EAAU,QAAA,EAAYA,CAAAA,CAAM,SAAWA,CAAAA,CAAM,KAAA,CAAM,MAAA,CAClE,MAAM,IAAIrE,CAAAA,CAAkB,CAAA,MAAA,EAASG,CAAK,gCAAgC,CAAA,CAE5E,GAAI,CAAC,MAAA,CAAO,QAAA,CAASkE,CAAAA,CAAM,eAAe,CAAA,EAAKA,CAAAA,CAAM,eAAA,EAAmB,CAAA,CACtE,MAAM,IAAIrE,CAAAA,CAAkB,CAAA,MAAA,EAASG,CAAK,kCAAkC,CAEhF,CACF,CAEO,SAASmE,CAAAA,CAAYC,CAAAA,CAAmB,CAC7C,GAAI,CAAC,MAAA,CAAO,QAAA,CAASA,CAAG,CAAA,EAAKA,CAAAA,CAAM,CAAA,EAAWA,CAAAA,CAAM,GAAA,CAClD,MAAM,IAAIvE,CAAAA,CAAkB,CAAA,oCAAA,EAAuC,CAAO,CAAA,KAAA,EAAQ,GAAO,CAAA,CAAA,CAAG,CAEhG,CAEO,SAASwE,CAAAA,CAAkBvD,CAAAA,CAAkB,CAClD,GAAI,CAAC,MAAA,CAAO,QAAA,CAASA,CAAE,CAAA,EAAKA,CAAAA,CAAK,EAAA,EAAmBA,CAAAA,CAAK,GAAA,CACvD,MAAM,IAAIjB,CAAAA,CACR,6CAA6C,EAAe,CAAA,KAAA,EAAQ,GAAe,CAAA,CAAA,CACrF,CAEJ,CClBO,IAAMyE,CAAAA,CAAN,KAA6B,CACzBC,EAAAA,CACAC,EAAAA,CACAC,EAAAA,CACAxD,EAAAA,CACAyD,EAAAA,CACTC,EAAAA,CAAyB,EAAC,CAC1BC,EAAAA,CAAgB,CAAA,CAChBC,EAAAA,CAAuB,KAAA,CACvBC,EAAAA,CAA2B,IAAA,CAC3BC,EAAAA,CAAiC,KACjCC,EAAAA,CAAqB,GAAA,CAAS,GAAA,CAC9BC,EAAAA,CAAa,KAAA,CAEb,WAAA,CAAYjC,CAAAA,CAAgC,GAAI,CAC9C,IAAA,CAAKuB,EAAAA,CAAgB,IAAI5C,CAAAA,CACzB,IAAA,CAAK6C,EAAAA,CAAW,IAAItE,EAEpB,IAAA,CAAKe,EAAAA,CAAc+B,CAAAA,CAAQ,UAAA,EAAc,IAAIpC,CAAAA,CAC7C,IAAA,CAAK6D,EAAAA,CAAazB,CAAAA,CAAQ,SAAA,EAAa,IAAIhC,CAAAA,CAAwB,IAAA,CAAKC,EAAW,CAAA,CACnF,IAAA,CAAKyD,GAAa1B,CAAAA,CAAQ,SAAA,EAAa,IAAIN,CAAAA,CAGvCM,CAAAA,CAAQ,SAAA,GAAc,MAAA,CACxB,IAAA,CAAK,aAAaA,CAAAA,CAAQ,SAAS,CAAA,CAC1BA,CAAAA,CAAQ,GAAA,GAAQ,MAAA,EACzB,IAAA,CAAK,QAAA,CAASA,EAAQ,GAAG,CAAA,CAIvBA,CAAAA,CAAQ,IAAA,GAAS,MAAA,EACnB,IAAA,CAAK,IAAA,CAAKA,CAAAA,CAAQ,IAAI,EAE1B,CAOO,IAAA,CAAKkC,CAAAA,CAAqB,CAC/B,IAAA,CAAKC,EAAAA,GACL,IAAA,CAAKC,EAAAA,EAAe,CACpB,GAAI,CACF,IAAA,CAAK,UAAA,CAAW,IAAA,CAAKV,GAAW,QAAA,CAASQ,CAAI,CAAC,EAChD,CAAA,MAASvE,CAAAA,CAAO,CACd,MAAIA,aAAiBd,CAAAA,EAGrB,IAAA,CAAKwF,EAAAA,CAAiB,IAAA,CAAKC,EAAAA,CAAS3E,CAAK,CAAC,CAAA,CACpCA,CACR,CACF,CAMO,UAAA,CAAWkD,CAAAA,CAA0B,CAC1C,IAAA,CAAKsB,EAAAA,EAAoB,CACzB,KAAKC,EAAAA,EAAe,CACpBnB,CAAAA,CAAeJ,CAAM,CAAA,CACrB,IAAM0B,CAAAA,CAAgB,IAAA,CAAKhB,GAAc,KAAA,CACzC,IAAA,CAAKA,EAAAA,CAAc,UAAA,CAAW,MAAM,CAAA,CACpC,IAAA,CAAKE,EAAAA,CAAW,QAAO,CACvB,IAAA,CAAKe,EAAAA,CAAW3B,CAAM,CAAA,CACtB,IAAA,CAAKe,EAAAA,CAAgB,CAAA,CACrB,IAAA,CAAKC,EAAAA,CAAuB,KAAA,CAC5B,IAAA,CAAKC,EAAAA,CAAY,IAAA,CACjB,IAAA,CAAKC,EAAAA,CAAkB,KACnBQ,CAAAA,GAAkB,MAAA,EACpB,IAAA,CAAKf,EAAAA,CAAS,IAAA,CAAK,aAAA,CAAe,CAAE,QAAA,CAAUe,EAAe,OAAA,CAAS,MAAO,CAAC,EAElF,CAEAH,EAAAA,EAAuB,CACrB,GAAI,KAAKb,EAAAA,CAAc,KAAA,GAAU,SAAA,EAAa,IAAA,CAAKA,EAAAA,CAAc,KAAA,GAAU,OAAA,CACzE,MAAM,IAAI1E,CAAAA,CAAkB,CAAA,iCAAA,EAAoC,IAAA,CAAK0E,EAAAA,CAAc,KAAK,CAAA,CAAA,CAAG,CAE/F,CAEAiB,GAAW3B,CAAAA,CAA0B,CACnC,IAAA,CAAKc,EAAAA,CAAUd,CAAAA,CAAO,GAAA,CAAI,CAACK,CAAAA,CAAOlE,KAAW,CAC3C,KAAA,CAAOkE,CAAAA,CAAM,KAAA,CACb,KAAA,CAAAlE,CAAAA,CACA,QAAA,CAAUkE,CAAAA,CAAM,SAChB,eAAA,CAAiBA,CAAAA,CAAM,eACzB,CAAA,CAAE,EACJ,CAIAmB,EAAAA,CAAiB1E,CAAAA,CAAoB,CACnC,IAAM4E,CAAAA,CAAgB,IAAA,CAAKhB,EAAAA,CAAc,KAAA,CACzC,GAAI,CACF,KAAKA,EAAAA,CAAc,UAAA,CAAW,OAAO,EACvC,CAAA,KAAQ,CACN,IAAA,CAAKkB,EAAAA,CAAa9E,CAAK,CAAA,CACvB,MACF,CACA,IAAA,CAAK8D,EAAAA,CAAW,MAAA,EAAO,CACvB,IAAA,CAAKK,GAAY,IAAA,CACjB,IAAA,CAAKC,EAAAA,CAAkB,IAAA,CACvB,IAAA,CAAKU,EAAAA,CAAa9E,CAAK,CAAA,CACvB,KAAK6D,EAAAA,CAAS,IAAA,CAAK,aAAA,CAAe,CAAE,QAAA,CAAUe,CAAAA,CAAe,OAAA,CAAS,OAAQ,CAAC,EACjF,CAEAE,EAAAA,CAAa9E,CAAAA,CAAoB,CAC/B,IAAA,CAAK6D,EAAAA,CAAS,IAAA,CAAK,QAAS,CAAE,KAAA,CAAA7D,CAAM,CAAC,EACvC,CAEA2E,EAAAA,CAAS3E,CAAAA,CAAuB,CAC9B,OAAOA,CAAAA,YAAiB,KAAA,CAAQA,CAAAA,CAAQ,IAAI,KAAA,CAAM,MAAA,CAAOA,CAAK,CAAC,CACjE,CAIAwE,EAAAA,EAA4B,CAC1B,GAAI,IAAA,CAAKF,EAAAA,CACP,MAAM,IAAIrF,CAEd,CAIS8F,EAAAA,CAAW,IAAY,CAG9B,GAFA,IAAA,CAAKZ,GAAY,IAAA,CACjB,IAAA,CAAKC,EAAAA,CAAkB,IAAA,CACnB,IAAA,CAAKR,EAAAA,CAAc,KAAA,GAAU,SAAA,CAIjC,IAAI,IAAA,CAAKK,EAAAA,EAAiB,IAAA,CAAKD,EAAAA,CAAQ,MAAA,CAAS,CAAA,CAAG,CACjD,IAAA,CAAKgB,EAAAA,EAAkB,CACvB,MACF,CAEA,IAAA,CAAKf,EAAAA,EAAAA,CACL,IAAA,CAAKgB,EAAAA,IACP,CAAA,CAEAA,EAAAA,EAAwB,CACtB,IAAMrC,CAAAA,CAAO,IAAA,CAAKoB,EAAAA,CAAQ,IAAA,CAAKC,EAAa,CAAA,CAC5C,IAAA,CAAKC,EAAAA,CAAuB,IAAA,CAE5B,IAAA,CAAKL,EAAAA,CAAS,IAAA,CAAK,YAAA,CAAc,CAC/B,IAAA,CAAAjB,CAAAA,CACA,KAAA,CAAO,IAAA,CAAKqB,EAAAA,CACZ,QAAA,CAAU,IAAA,CAAK,QAAA,CACf,MAAA,CAAQ,UACV,CAAC,CAAA,CACG,IAAA,CAAKL,EAAAA,CAAc,KAAA,GAAU,SAAA,EAIjC,KAAKsB,EAAAA,CAAiB,IAAA,CAAKb,EAAAA,CAAazB,CAAAA,CAAK,eAAe,EAC9D,CAEAsC,EAAAA,CAAiBC,EAAqB,CACpC,IAAA,CAAKf,EAAAA,CAAkB,IAAA,CACvB,IAAA,CAAKD,EAAAA,CAAY,IAAA,CAAK7D,EAAAA,CAAY,KAAI,CAAI6E,CAAAA,CAC1C,GAAI,CACF,IAAA,CAAKrB,EAAAA,CAAW,QAAA,CAAS,IAAA,CAAKiB,GAAUI,CAAK,EAC/C,CAAA,MAASnF,CAAAA,CAAO,CACd,IAAA,CAAK0E,EAAAA,CAAiB,IAAA,CAAKC,GAAS3E,CAAK,CAAC,EAC5C,CACF,CAEAgF,EAAAA,EAA0B,CACxB,IAAA,CAAKlB,GAAW,MAAA,EAAO,CACvB,IAAMc,CAAAA,CAAgB,IAAA,CAAKhB,EAAAA,CAAc,KAAA,CACzC,IAAA,CAAKA,GAAc,UAAA,CAAW,UAAU,CAAA,CACxC,IAAMhB,CAAAA,CAAO,IAAA,CAAKoB,EAAAA,CAAQ,IAAA,CAAKC,EAAa,CAAA,CAC5C,IAAA,CAAKJ,EAAAA,CAAS,IAAA,CAAK,aAAA,CAAe,CAAE,QAAA,CAAUe,EAAe,OAAA,CAAS,WAAY,CAAC,CAAA,CACnF,IAAA,CAAKf,EAAAA,CAAS,IAAA,CAAK,UAAA,CAAY,CAAE,IAAA,CAAAjB,CAAAA,CAAM,UAAA,CAAY,IAAA,CAAKoB,EAAAA,CAAQ,MAAO,CAAC,EAC1E,CASO,IAAA,EAAa,CAClB,IAAA,CAAKQ,EAAAA,EAAoB,CACzB,IAAA,CAAKY,EAAAA,GACP,CAEAA,EAAAA,EAAuB,CAErB,GAAI,IAAA,CAAKpB,EAAAA,CAAQ,MAAA,GAAW,CAAA,CAAG,CAC7B,KAAKc,EAAAA,CAAa,IAAI5F,CAAAA,CAAkB,gCAAgC,CAAC,CAAA,CACzE,MACF,CAEA,IAAM0F,CAAAA,CAAgB,IAAA,CAAKhB,EAAAA,CAAc,KAAA,CAAA,CAGrCgB,CAAAA,GAAkB,SAAA,EAAaA,CAAAA,GAAkB,WAAA,IACnD,KAAKX,EAAAA,CAAgB,CAAA,CAAA,CAGvB,GAAI,CACF,IAAA,CAAKL,EAAAA,CAAc,UAAA,CAAW,MAAM,EACtC,CAAA,MAAS7D,CAAAA,CAAK,CACZ,IAAA,CAAK+E,EAAAA,CAAa,IAAA,CAAKH,EAAAA,CAAS5E,CAAG,CAAC,CAAA,CACpC,MACF,CAOA,GALA,IAAA,CAAK8D,EAAAA,CAAS,IAAA,CAAK,cAAe,CAChC,QAAA,CAAUe,CAAAA,CACV,OAAA,CAAS,SACX,CAAC,CAAA,CAEGA,CAAAA,GAAkB,UAAY,IAAA,CAAKV,EAAAA,CAAsB,CAC3D,IAAMtB,CAAAA,CAAO,IAAA,CAAKoB,EAAAA,CAAQ,IAAA,CAAKC,EAAa,CAAA,CAC5C,IAAA,CAAKiB,EAAAA,CAAiB,IAAA,CAAKd,EAAAA,EAAmB,IAAA,CAAKC,EAAAA,CAAazB,CAAAA,CAAK,eAAe,CAAA,CACpF,MACF,CAEA,IAAA,CAAKsB,EAAAA,CAAuB,KAAA,CAC5B,IAAA,CAAKe,EAAAA,GACP,CAKO,KAAA,EAAc,CACnB,IAAA,CAAKT,EAAAA,EAAoB,CAEzB,IAAMI,CAAAA,CAAgB,KAAKhB,EAAAA,CAAc,KAAA,CAEzC,GAAI,CACF,IAAA,CAAKA,EAAAA,CAAc,UAAA,CAAW,OAAO,EACvC,CAAA,MAAS7D,CAAAA,CAAK,CACZ,IAAA,CAAK+E,EAAAA,CAAa,IAAA,CAAKH,EAAAA,CAAS5E,CAAG,CAAC,CAAA,CACpC,MACF,CAEA,IAAA,CAAKqE,EAAAA,CACH,IAAA,CAAKD,EAAAA,GAAc,KAAO,IAAA,CAAO,IAAA,CAAK,GAAA,CAAI,CAAA,CAAG,IAAA,CAAKA,EAAAA,CAAY,IAAA,CAAK7D,EAAAA,CAAY,KAAK,CAAA,CACtF,IAAA,CAAKwD,EAAAA,CAAW,MAAA,EAAO,CACvB,IAAA,CAAKK,EAAAA,CAAY,IAAA,CAEjB,IAAA,CAAKN,EAAAA,CAAS,IAAA,CAAK,aAAA,CAAe,CAChC,QAAA,CAAUe,CAAAA,CACV,QAAS,QACX,CAAC,EACH,CAKO,IAAA,EAAa,CAClB,IAAA,CAAKJ,EAAAA,GAEL,IAAMI,CAAAA,CAAgB,IAAA,CAAKhB,EAAAA,CAAc,KAAA,CAEzC,GAAI,CACF,IAAA,CAAKA,GAAc,UAAA,CAAW,MAAM,EACtC,CAAA,MAAS7D,CAAAA,CAAK,CACZ,IAAA,CAAK+E,EAAAA,CAAa,IAAA,CAAKH,EAAAA,CAAS5E,CAAG,CAAC,CAAA,CACpC,MACF,CAEA,IAAA,CAAK+D,GAAW,MAAA,EAAO,CACvB,IAAA,CAAKG,EAAAA,CAAgB,CAAA,CACrB,IAAA,CAAKC,EAAAA,CAAuB,KAAA,CAC5B,KAAKC,EAAAA,CAAY,IAAA,CACjB,IAAA,CAAKC,EAAAA,CAAkB,IAAA,CAEvB,IAAA,CAAKP,EAAAA,CAAS,IAAA,CAAK,cAAe,CAChC,QAAA,CAAUe,CAAAA,CACV,OAAA,CAAS,SACX,CAAC,EACH,CAMO,KAAKvF,CAAAA,CAAqB,CAG/B,GAFA,IAAA,CAAKmF,EAAAA,EAAoB,CAErBnF,CAAAA,CAAQ,CAAA,EAAKA,GAAS,IAAA,CAAK2E,EAAAA,CAAQ,MAAA,CAAQ,CAC7C,IAAA,CAAKc,EAAAA,CAAa,IAAI1F,CAAAA,CAAsBC,EAAO,IAAA,CAAK2E,EAAAA,CAAQ,MAAM,CAAC,CAAA,CACvE,MACF,CAEA,IAAMY,EAAgB,IAAA,CAAKhB,EAAAA,CAAc,KAAA,CAEzC,GAAI,CACF,IAAA,CAAKA,EAAAA,CAAc,UAAA,CAAW,MAAM,EACtC,CAAA,MAAS7D,CAAAA,CAAK,CACZ,IAAA,CAAK+E,EAAAA,CAAa,IAAA,CAAKH,GAAS5E,CAAG,CAAC,CAAA,CACpC,MACF,CAEA,IAAA,CAAKkE,EAAAA,CAAgB5E,CAAAA,CACrB,KAAK6E,EAAAA,CAAuB,IAAA,CAC5B,IAAA,CAAKE,EAAAA,CAAkB,IAAA,CAAKC,EAAAA,CAAa,IAAA,CAAKL,EAAAA,CAAQ3E,CAAK,CAAA,CAAE,eAAA,CAEzDuF,CAAAA,GAAkB,QAAA,EACpB,IAAA,CAAKf,EAAAA,CAAS,IAAA,CAAK,aAAA,CAAe,CAChC,QAAA,CAAUe,CAAAA,CACV,OAAA,CAAS,QACX,CAAC,CAAA,CAGH,IAAA,CAAKf,EAAAA,CAAS,KAAK,YAAA,CAAc,CAC/B,IAAA,CAAM,IAAA,CAAKG,EAAAA,CAAQ3E,CAAK,CAAA,CACxB,KAAA,CAAAA,EACA,QAAA,CAAU,IAAA,CAAK,QAAA,CACf,MAAA,CAAQ,MACV,CAAC,EACH,CAKO,MAAa,CAGlB,GAFA,IAAA,CAAKmF,EAAAA,EAAoB,CAErB,IAAA,CAAKZ,EAAAA,CAAc,KAAA,GAAU,QAAA,CAAU,CACzC,IAAA,CAAKC,EAAAA,CAAS,IAAA,CAAK,OAAA,CAAS,CAC1B,KAAA,CAAO,IAAI3E,CAAAA,CAAkB,2CAA2C,CAC1E,CAAC,CAAA,CACD,MACF,CAEI,IAAA,CAAK+E,GAAgB,IAAA,CAAKD,EAAAA,CAAQ,MAAA,CAAS,CAAA,GAC7C,IAAA,CAAKC,EAAAA,EAAAA,CACL,IAAA,CAAKC,EAAAA,CAAuB,KAC5B,IAAA,CAAKE,EAAAA,CAAkB,IAAA,CAAKC,EAAAA,CAAa,IAAA,CAAKL,EAAAA,CAAQ,IAAA,CAAKC,EAAa,EAAE,eAAA,CAC1E,IAAA,CAAKJ,EAAAA,CAAS,IAAA,CAAK,YAAA,CAAc,CAC/B,IAAA,CAAM,IAAA,CAAKG,GAAQ,IAAA,CAAKC,EAAa,CAAA,CACrC,KAAA,CAAO,IAAA,CAAKA,EAAAA,CACZ,QAAA,CAAU,IAAA,CAAK,SACf,MAAA,CAAQ,MACV,CAAC,CAAA,EAEL,CAKO,QAAA,EAAiB,CAGtB,GAFA,KAAKO,EAAAA,EAAoB,CAErB,IAAA,CAAKZ,EAAAA,CAAc,KAAA,GAAU,QAAA,CAAU,CACzC,IAAA,CAAKC,EAAAA,CAAS,IAAA,CAAK,OAAA,CAAS,CAC1B,KAAA,CAAO,IAAI3E,CAAAA,CAAkB,+CAA+C,CAC9E,CAAC,CAAA,CACD,MACF,CAEI,IAAA,CAAK+E,EAAAA,CAAgB,CAAA,GACvB,IAAA,CAAKA,KACL,IAAA,CAAKC,EAAAA,CAAuB,IAAA,CAC5B,IAAA,CAAKE,EAAAA,CAAkB,IAAA,CAAKC,EAAAA,CAAa,IAAA,CAAKL,GAAQ,IAAA,CAAKC,EAAa,CAAA,CAAE,eAAA,CAC1E,IAAA,CAAKJ,EAAAA,CAAS,IAAA,CAAK,YAAA,CAAc,CAC/B,IAAA,CAAM,IAAA,CAAKG,EAAAA,CAAQ,IAAA,CAAKC,EAAa,CAAA,CACrC,KAAA,CAAO,IAAA,CAAKA,GACZ,QAAA,CAAU,IAAA,CAAK,QAAA,CACf,MAAA,CAAQ,UACV,CAAC,CAAA,EAEL,CAMO,OAAc,CACnB,IAAA,CAAKO,EAAAA,EAAoB,CAEzB,IAAMI,CAAAA,CAAgB,IAAA,CAAKhB,EAAAA,CAAc,MAEzC,GAAI,CACF,IAAA,CAAKA,EAAAA,CAAc,UAAA,CAAW,OAAO,EACvC,CAAA,MAAS7D,CAAAA,CAAK,CACZ,IAAA,CAAK+E,EAAAA,CAAa,IAAA,CAAKH,EAAAA,CAAS5E,CAAG,CAAC,EACpC,MACF,CAEA,IAAA,CAAK+D,EAAAA,CAAW,MAAA,EAAO,CACvB,IAAA,CAAKE,EAAAA,CAAU,EAAC,CAChB,IAAA,CAAKC,EAAAA,CAAgB,CAAA,CACrB,IAAA,CAAKC,EAAAA,CAAuB,KAAA,CAC5B,IAAA,CAAKC,GAAY,IAAA,CACjB,IAAA,CAAKC,EAAAA,CAAkB,IAAA,CAEvB,IAAA,CAAKP,EAAAA,CAAS,IAAA,CAAK,aAAA,CAAe,CAChC,QAAA,CAAUe,CAAAA,CACV,OAAA,CAAS,MACX,CAAC,EACH,CAMO,OAAA,EAAgB,CACjB,IAAA,CAAKN,EAAAA,GAGT,IAAA,CAAKR,EAAAA,CAAW,MAAA,EAAO,CACvB,IAAA,CAAKK,EAAAA,CAAY,KACjB,IAAA,CAAKC,EAAAA,CAAkB,IAAA,CACvB,IAAA,CAAKP,EAAAA,CAAS,kBAAA,EAAmB,CACjC,IAAA,CAAKS,GAAa,IAAA,EACpB,CAOO,QAAA,CAASb,CAAAA,CAAmB,CACjC,IAAA,CAAKe,EAAAA,EAAoB,CACzBhB,CAAAA,CAAYC,CAAG,CAAA,CACf,IAAA,CAAKY,EAAAA,CAAa,GAAA,CAASZ,EAC7B,CAKO,aAAatD,CAAAA,CAAkB,CACpC,IAAA,CAAKqE,EAAAA,EAAoB,CACzBd,CAAAA,CAAkBvD,CAAE,CAAA,CACpB,KAAKkE,EAAAA,CAAalE,EACpB,CAKA,IAAW,KAAA,EAAmB,CAC5B,OAAO,IAAA,CAAKyD,GAAc,KAC5B,CAGA,IAAW,GAAA,EAAc,CACvB,OAAO,GAAA,CAAS,IAAA,CAAKS,EACvB,CAGA,IAAW,SAAA,EAAoB,CAC7B,OAAO,IAAA,CAAKA,EACd,CAGA,IAAW,YAAA,EAAuB,CAChC,OAAO,IAAA,CAAKJ,EACd,CAGA,IAAW,WAAA,EAAkC,CAC3C,OAAO,IAAA,CAAKD,EAAAA,CAAQ,IAAA,CAAKC,EAAa,CAAA,EAAK,IAC7C,CAGA,IAAW,QAAA,EAAmB,CAC5B,OAAI,IAAA,CAAKD,EAAAA,CAAQ,MAAA,GAAW,CAAA,CACnB,CAAA,CAEF,IAAA,CAAKE,EAAAA,CAAAA,CAAwB,IAAA,CAAKD,EAAAA,CAAgB,CAAA,EAAK,IAAA,CAAKD,EAAAA,CAAQ,MAAA,CAAS,CACtF,CAGA,IAAW,UAAA,EAAqB,CAC9B,OAAO,IAAA,CAAKA,EAAAA,CAAQ,MACtB,CAOO,EAAA,CACLvE,CAAAA,CACAC,CAAAA,CACe,CACf,OAAA,IAAA,CAAK8E,EAAAA,EAAoB,CAClB,IAAA,CAAKX,GAAS,EAAA,CAAGpE,CAAAA,CAAOC,CAAQ,CACzC,CAOO,QAAA,EAA4B,CACjC,OAAA,IAAA,CAAK8E,IAAoB,CAClB,MAAA,CAAO,MAAA,CAAO,CACnB,KAAA,CAAO,IAAA,CAAK,KAAA,CACZ,YAAA,CAAc,KAAKP,EAAAA,CACnB,WAAA,CAAa,IAAA,CAAK,WAAA,CAClB,QAAA,CAAU,IAAA,CAAK,QAAA,CACf,UAAA,CAAY,KAAK,UAAA,CACjB,GAAA,CAAK,IAAA,CAAK,GAAA,CACV,SAAA,CAAW,IAAA,CAAKI,EAClB,CAAC,CACH,CACF","file":"index.cjs","sourcesContent":["import type { SchedulerStrategy, TimeDriver } from \"../scheduler/types\";\nimport type { TokenizerStrategy } from \"../tokenizer/types\";\n\n/**\n * Configuration options for the RSVP Engine.\n */\nexport interface RSVPEngineOptions<T = string> {\n /** Raw text string, single generic item, or generic item array. */\n readonly data?: T | T[];\n /** Target speed in Words Per Minute. Defaults to 300. */\n readonly wpm?: number;\n /** Direct ms-per-item override. Takes precedence over `wpm`. */\n readonly msPerItem?: number;\n /** Custom tokenizer strategy. Defaults to `DefaultTokenizer`. */\n readonly tokenizer?: TokenizerStrategy<T>;\n /** Custom scheduler strategy. Defaults to `DriftCorrectedScheduler`. */\n readonly scheduler?: SchedulerStrategy;\n /** Clock used to preserve remaining display time across pause/resume. */\n readonly timeDriver?: TimeDriver;\n}\n\n/** Minimum allowed playback speed in Words Per Minute. */\nexport const MIN_WPM = 1;\n/** Maximum allowed playback speed in Words Per Minute. */\nexport const MAX_WPM = 6_000;\n/** Minimum allowed display interval per item in milliseconds. */\nexport const MIN_MS_PER_ITEM = 60_000 / MAX_WPM;\n/** Maximum allowed display interval per item in milliseconds. */\nexport const MAX_MS_PER_ITEM = 60_000 / MIN_WPM;\n/** Default playback speed in Words Per Minute. */\nexport const DEFAULT_WPM = 300;\n","/**\n * Thrown when an invalid state transition is attempted.\n *\n */\nexport class InvalidTransitionError extends Error {\n public readonly from: string;\n public readonly action: string;\n\n constructor(from: string, action: string) {\n super(`Invalid transition: cannot perform '${action}' from '${from}' state.`);\n this.name = \"InvalidTransitionError\";\n this.from = from;\n this.action = action;\n }\n}\n\n/**\n * Thrown when a method is called on a destroyed engine instance.\n */\nexport class EngineDestroyedError extends Error {\n constructor() {\n super(\"Cannot interact with a destroyed engine instance.\");\n this.name = \"EngineDestroyedError\";\n }\n}\n\n/**\n * Thrown when invalid input is provided to the engine (empty text, null data, etc.).\n */\nexport class InvalidInputError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidInputError\";\n }\n}\n\n/**\n * Thrown when a seek index is out of bounds.\n */\nexport class IndexOutOfBoundsError extends Error {\n public readonly index: number;\n public readonly totalItems: number;\n\n constructor(index: number, totalItems: number) {\n super(`Index ${index} is out of bounds (0..${totalItems - 1}).`);\n this.name = \"IndexOutOfBoundsError\";\n this.index = index;\n this.totalItems = totalItems;\n }\n}\n","import type { EventCallback, RSVPEventMap, RSVPEventType, UnsubscribeFn } from \"./types\";\n\ntype ListenerRegistry<T> = {\n [K in RSVPEventType]: Set<EventCallback<RSVPEventMap<T>[K]>>;\n};\n\nexport class EventEmitter<T = string> {\n readonly #listenerRegistry: ListenerRegistry<T> = {\n stateChange: new Set(),\n itemChange: new Set(),\n complete: new Set(),\n error: new Set(),\n };\n\n public on<K extends RSVPEventType>(\n event: K,\n callback: EventCallback<RSVPEventMap<T>[K]>,\n ): UnsubscribeFn {\n const eventListeners = this.#listenerRegistry[event];\n eventListeners.add(callback);\n\n return () => {\n eventListeners.delete(callback);\n };\n }\n\n public emit<K extends RSVPEventType>(event: K, payload: RSVPEventMap<T>[K]): void {\n const eventListeners = this.#listenerRegistry[event];\n\n // Copy to array to handle safe iteration if unsubscribed during emit\n const listenersSnapshot = Array.from(eventListeners);\n\n for (const listener of listenersSnapshot) {\n try {\n listener(payload);\n } catch (err) {\n if (event !== \"error\") {\n const error = err instanceof Error ? err : new Error(String(err));\n this.emit(\"error\", { error });\n }\n }\n }\n }\n\n public removeAllListeners(): void {\n for (const eventListeners of Object.values(this.#listenerRegistry)) {\n eventListeners.clear();\n }\n }\n}\n","import type { TimeDriver } from \"./types\";\nimport type { RuntimeGlobal } from \"../runtimeGlobal\";\n\n/**\n * Default implementation of `TimeDriver` utilizing platform globals\n * (`performance.now` / `Date.now` and `setTimeout` / `clearTimeout`).\n */\nexport class SystemTimeDriver implements TimeDriver {\n public now(): number {\n const runtimeGlobal = globalThis as RuntimeGlobal;\n if (typeof runtimeGlobal.performance?.now === \"function\") {\n return runtimeGlobal.performance.now();\n }\n return Date.now();\n }\n\n public setTimeout(callback: () => void, ms: number): unknown {\n const runtimeGlobal = globalThis as RuntimeGlobal;\n if (typeof runtimeGlobal.setTimeout !== \"function\") {\n throw new TypeError(\"The runtime does not provide setTimeout.\");\n }\n return runtimeGlobal.setTimeout(callback, ms);\n }\n\n public clearTimeout(handle: unknown): void {\n const runtimeGlobal = globalThis as RuntimeGlobal;\n if (typeof runtimeGlobal.clearTimeout !== \"function\") {\n throw new TypeError(\"The runtime does not provide clearTimeout.\");\n }\n runtimeGlobal.clearTimeout(handle);\n }\n}\n","import type { SchedulerStrategy, TimeDriver } from \"./types\";\n\nimport { SystemTimeDriver } from \"./system-time-driver\";\n\n/**\n * High-precision, drift-corrected task scheduler.\n *\n * Implements the {@link SchedulerStrategy} interface by tracking an ideal target timeline\n * (`#expectedTime`) across consecutive task executions. Any latency or jitter introduced\n * by the JavaScript event loop (timer drift) is calculated and subtracted from subsequent\n * intervals, ensuring stable WPM presentation without cumulative time slippage.\n *\n * @example\n * ```ts\n * const scheduler = new DriftCorrectedScheduler();\n *\n * // Schedule a task after 200ms\n * scheduler.schedule(() => {\n * console.log(\"Tick!\");\n * }, 200);\n *\n * // Cancel any pending task\n * scheduler.cancel();\n * ```\n */\nexport class DriftCorrectedScheduler implements SchedulerStrategy {\n readonly #timeDriver: TimeDriver;\n #timeoutHandle: unknown = null;\n #expectedTime: number | null = null;\n\n /**\n * Creates a new instance of `DriftCorrectedScheduler`.\n *\n * @param timeDriver - The underlying time and timer provider. Defaults to {@link SystemTimeDriver}.\n */\n constructor(timeDriver: TimeDriver = new SystemTimeDriver()) {\n this.#timeDriver = timeDriver;\n }\n\n /**\n * Schedules a task to execute after the given delay, compensating for accumulated drift.\n *\n * If a previous task is currently pending, it is automatically cancelled before scheduling\n * the new one. When part of an active session, the actual timeout duration is adjusted\n * by subtracting any detected execution delay (`drift = now - expectedTime`).\n *\n * @param task - The callback function to execute.\n * @param delayMs - The target delay in milliseconds.\n */\n public schedule(task: () => void, delayMs: number): void {\n if (!Number.isFinite(delayMs) || delayMs <= 0) {\n throw new RangeError(\"delayMs must be a positive finite number.\");\n }\n\n if (this.#timeoutHandle !== null) {\n this.cancel();\n }\n\n const now = this.#timeDriver.now();\n let actualDelay = delayMs;\n\n if (this.#expectedTime === null) {\n this.#expectedTime = now + delayMs;\n } else {\n const drift = now - this.#expectedTime;\n if (drift >= delayMs) {\n // Large catch-up bursts make RSVP items unreadable; start a fresh timeline instead.\n this.#expectedTime = now + delayMs;\n } else {\n this.#expectedTime += delayMs;\n actualDelay = Math.max(delayMs / 2, delayMs - drift);\n }\n }\n\n this.#timeoutHandle = this.#timeDriver.setTimeout(() => {\n this.#timeoutHandle = null;\n task();\n }, actualDelay);\n }\n\n /**\n * Cancels any pending scheduled task and resets the session timeline.\n *\n * Calling this method halts the drift compensation timeline so that the next\n * call to {@link schedule} starts a fresh session without previous drift history.\n */\n public cancel(): void {\n this.#clearTimer();\n this.#expectedTime = null;\n }\n\n #clearTimer(): void {\n if (this.#timeoutHandle !== null) {\n this.#timeDriver.clearTimeout(this.#timeoutHandle);\n this.#timeoutHandle = null;\n }\n }\n}\n","import type { RSVPState, StateMachineAction } from \"./types\";\n\nimport { InvalidTransitionError } from \"../errors\";\n\nexport class StateMachine {\n #state: RSVPState = \"IDLE\";\n\n readonly #transitions: Record<RSVPState, Partial<Record<StateMachineAction, RSVPState>>> = {\n IDLE: {\n load: \"IDLE\",\n play: \"PLAYING\",\n error: \"ERROR\",\n },\n PLAYING: {\n pause: \"PAUSED\",\n stop: \"STOPPED\",\n complete: \"COMPLETED\",\n error: \"ERROR\",\n },\n PAUSED: {\n load: \"IDLE\",\n play: \"PLAYING\",\n stop: \"STOPPED\",\n seek: \"PAUSED\",\n error: \"ERROR\",\n },\n STOPPED: {\n load: \"IDLE\",\n play: \"PLAYING\",\n seek: \"PAUSED\",\n error: \"ERROR\",\n },\n COMPLETED: {\n load: \"IDLE\",\n play: \"PLAYING\",\n seek: \"PAUSED\",\n error: \"ERROR\",\n },\n ERROR: {\n reset: \"IDLE\",\n },\n };\n\n public get state(): RSVPState {\n return this.#state;\n }\n\n public transition(action: StateMachineAction): RSVPState {\n const allowedTransitions = this.#transitions[this.#state];\n const nextState = allowedTransitions[action];\n\n if (!nextState) {\n throw new InvalidTransitionError(this.#state, action);\n }\n\n this.#state = nextState;\n return this.#state;\n }\n}\n","import type { OVPStrategy } from \"./types\";\n\n/**\n * Default grapheme-aware Optimal Viewing Position strategy.\n */\nexport class DefaultOVPStrategy implements OVPStrategy {\n /**\n * Calculates the Optimal Viewing Position for text.\n * @returns The zero-based UTF-16 offset of the OVP grapheme.\n */\n public calculate(text: string): number {\n if (!text) {\n return 0;\n }\n\n const graphemes = segmentGraphemes(text);\n const content = graphemes.filter(({ segment }) => !isPunctuation(segment));\n\n if (content.length === 0) {\n return graphemes[0].index;\n }\n\n const target = content.length <= 3 ? 0 : Math.floor(content.length * 0.25);\n return content[target].index;\n }\n}\n\ninterface Grapheme {\n readonly index: number;\n readonly segment: string;\n}\n\nfunction segmentGraphemes(text: string): Grapheme[] {\n const Segmenter = globalThis.Intl?.Segmenter;\n if (typeof Segmenter === \"function\") {\n return Array.from(new Segmenter(undefined, { granularity: \"grapheme\" }).segment(text));\n }\n\n const graphemes: Grapheme[] = [];\n let index = 0;\n for (const segment of text) {\n const previous = graphemes.at(-1);\n if (\n previous &&\n (/^[\\p{M}\\uFE0F]$/u.test(segment) ||\n segment === \"\\u200D\" ||\n previous.segment.endsWith(\"\\u200D\"))\n ) {\n graphemes[graphemes.length - 1] = {\n index: previous.index,\n segment: previous.segment + segment,\n };\n } else {\n graphemes.push({ index, segment });\n }\n index += segment.length;\n }\n return graphemes;\n}\n\nfunction isPunctuation(value: string): boolean {\n return /^[\\p{P}\\p{Z}]+$/u.test(value);\n}\n","import type { OVPStrategy, Token, TokenizerStrategy } from \"./types\";\n\nimport { InvalidInputError } from \"../errors\";\n\nimport { DefaultOVPStrategy } from \"./ovp\";\n\n/**\n * Options for configuring `DefaultTokenizer`.\n */\nexport interface DefaultTokenizerOptions {\n /** Multiplier for sentence-ending punctuation (`.`, `!`, `?`). Defaults to `2.0`. */\n readonly sentenceDelay?: number;\n /** Multiplier for clause-ending punctuation (`,`, `;`, `:`). Defaults to `1.5`. */\n readonly clauseDelay?: number;\n /** Multiplier for dashes (`—`, `–`). Defaults to `1.3`. */\n readonly dashDelay?: number;\n /** Whether array elements and nested inputs should be recursively tokenized. Defaults to `false`. */\n readonly nestedTokenize?: boolean;\n /** Strategy used to calculate viewing positions for string tokens. */\n readonly ovpStrategy?: OVPStrategy;\n}\n\n/**\n * Default configurable tokenizer for text, items, and array inputs.\n * Splits text on whitespace and assigns delay multipliers based on punctuation.\n * Wraps custom generic items or arrays into presentable tokens.\n * @typeParam T - The type of items being presented (defaults to `string`).\n */\nexport class DefaultTokenizer<T> implements TokenizerStrategy<T> {\n readonly #sentenceDelay: number;\n readonly #clauseDelay: number;\n readonly #dashDelay: number;\n readonly #nestedTokenize: boolean;\n readonly #ovpStrategy: OVPStrategy;\n\n constructor(options: DefaultTokenizerOptions = {}) {\n this.#sentenceDelay = options.sentenceDelay ?? 2.0;\n this.#clauseDelay = options.clauseDelay ?? 1.5;\n this.#dashDelay = options.dashDelay ?? 1.3;\n this.#nestedTokenize = options.nestedTokenize ?? false;\n this.#ovpStrategy = options.ovpStrategy ?? new DefaultOVPStrategy();\n this.#validateOptions();\n }\n\n public tokenize(input: T | T[]): Token<T>[] {\n if (input === null || input === undefined) {\n return [];\n }\n\n if (Array.isArray(input)) {\n return this.#tokenizeArray(input);\n }\n\n if (typeof input === \"string\") {\n return this.#tokenizeText(input) as Token<T>[];\n }\n\n return [\n {\n value: input,\n ovpIndex: 0,\n delayMultiplier: this.#calculateDelay(input),\n },\n ];\n }\n\n #tokenizeArray(items: T[]): Token<T>[] {\n if (this.#nestedTokenize) {\n return items.flatMap((item) => this.tokenize(item));\n }\n\n return items.map((item) => ({\n value: item,\n ovpIndex: typeof item === \"string\" ? this.#ovpStrategy.calculate(item) : 0,\n delayMultiplier: this.#calculateDelay(item),\n }));\n }\n\n #tokenizeText(text: string): Token<string>[] {\n const trimmed = text.trim();\n if (!trimmed) {\n return [];\n }\n\n const words = this.#segmentWords(trimmed);\n return words.map((word) => ({\n value: word,\n ovpIndex: this.#ovpStrategy.calculate(word),\n delayMultiplier: this.#calculateDelay(word),\n }));\n }\n\n #calculateDelay(item: unknown): number {\n if (typeof item === \"string\") {\n if (/[.!?。!?…]+[\\p{Pe}\\p{Pf}\"']*$/u.test(item)) {\n return this.#sentenceDelay;\n }\n if (/[,;:,、;:]+[\\p{Pe}\\p{Pf}\"']*$/u.test(item)) {\n return this.#clauseDelay;\n }\n if (/\\p{Pd}+[\\p{Pe}\\p{Pf}\"']*$/u.test(item)) {\n return this.#dashDelay;\n }\n return 1.0;\n }\n\n return 1.0;\n }\n\n #validateOptions(): void {\n const options = [\n [\"sentenceDelay\", this.#sentenceDelay],\n [\"clauseDelay\", this.#clauseDelay],\n [\"dashDelay\", this.#dashDelay],\n ] as const;\n for (const [name, value] of options) {\n if (!Number.isFinite(value) || value <= 0) {\n throw new InvalidInputError(`${name} must be a positive finite number.`);\n }\n }\n }\n\n #segmentWords(text: string): string[] {\n const Segmenter = globalThis.Intl?.Segmenter;\n if (typeof Segmenter !== \"function\") {\n return text.split(/\\s+/);\n }\n\n const segmenter = new Segmenter(undefined, { granularity: \"word\" });\n const tokens: string[] = [];\n let prefix = \"\";\n let atBoundary = true;\n for (const part of segmenter.segment(text)) {\n if (/^\\s+$/u.test(part.segment)) {\n if (prefix) {\n tokens.push(prefix);\n }\n prefix = \"\";\n atBoundary = true;\n } else if (part.isWordLike || !isPunctuation(part.segment)) {\n tokens.push(prefix + part.segment);\n prefix = \"\";\n atBoundary = false;\n } else if (atBoundary || prefix || /^[\\p{Ps}\\p{Pi}]+$/u.test(part.segment)) {\n prefix += part.segment;\n } else {\n tokens[tokens.length - 1] += part.segment;\n }\n }\n if (prefix) {\n tokens.push(prefix);\n }\n return tokens;\n }\n}\n\nfunction isPunctuation(value: string): boolean {\n return /^\\p{P}+$/u.test(value);\n}\n","import type { Token } from \"../tokenizer/types\";\n\nimport { InvalidInputError } from \"../errors\";\n\nimport { MAX_MS_PER_ITEM, MAX_WPM, MIN_MS_PER_ITEM, MIN_WPM } from \"./config\";\n\nexport function validateTokens<T>(tokens: Token<T>[]): void {\n if (!Array.isArray(tokens)) {\n throw new InvalidInputError(\"tokens must be an array.\");\n }\n\n for (const [index, token] of tokens.entries()) {\n if (typeof token !== \"object\" || token === null) {\n throw new InvalidInputError(`Token ${index} must be an object.`);\n }\n if (!Number.isInteger(token.ovpIndex) || token.ovpIndex < 0) {\n throw new InvalidInputError(`Token ${index} has an invalid ovpIndex.`);\n }\n if (typeof token.value === \"string\" && token.ovpIndex > token.value.length) {\n throw new InvalidInputError(`Token ${index} has an out-of-range ovpIndex.`);\n }\n if (!Number.isFinite(token.delayMultiplier) || token.delayMultiplier <= 0) {\n throw new InvalidInputError(`Token ${index} has an invalid delayMultiplier.`);\n }\n }\n}\n\nexport function validateWpm(wpm: number): void {\n if (!Number.isFinite(wpm) || wpm < MIN_WPM || wpm > MAX_WPM) {\n throw new InvalidInputError(`wpm must be a finite number between ${MIN_WPM} and ${MAX_WPM}.`);\n }\n}\n\nexport function validateMsPerItem(ms: number): void {\n if (!Number.isFinite(ms) || ms < MIN_MS_PER_ITEM || ms > MAX_MS_PER_ITEM) {\n throw new InvalidInputError(\n `msPerItem must be a finite number between ${MIN_MS_PER_ITEM} and ${MAX_MS_PER_ITEM}.`,\n );\n }\n}\n","import type { RSVPEngineOptions } from \"./config\";\nimport type { RSVPItem, RSVPSnapshot } from \"./types\";\nimport type { EventCallback, RSVPEventMap, RSVPEventType, UnsubscribeFn } from \"../events/types\";\nimport type { SchedulerStrategy, TimeDriver } from \"../scheduler/types\";\nimport type { RSVPState } from \"../state/types\";\nimport type { Token, TokenizerStrategy } from \"../tokenizer/types\";\n\nimport { EngineDestroyedError, IndexOutOfBoundsError, InvalidInputError } from \"../errors\";\nimport { EventEmitter } from \"../events/event-emitter\";\nimport { DriftCorrectedScheduler } from \"../scheduler/drift-corrected-scheduler\";\nimport { SystemTimeDriver } from \"../scheduler/system-time-driver\";\nimport { StateMachine } from \"../state/state-machine\";\nimport { DefaultTokenizer } from \"../tokenizer/default-tokenizer\";\n\nimport { DEFAULT_WPM } from \"./config\";\nimport { validateMsPerItem, validateTokens, validateWpm } from \"./validation\";\n\n/**\n * Headless RSVP Engine — orchestrates state machine, scheduler, tokenizer, and event emitter.\n * @typeParam T - The type of items being presented (defaults to `string`).\n */\nexport class RSVPEngine<T = string> {\n readonly #stateMachine: StateMachine;\n readonly #emitter: EventEmitter<T>;\n readonly #scheduler: SchedulerStrategy;\n readonly #timeDriver: TimeDriver;\n readonly #tokenizer: TokenizerStrategy<T>;\n #tokens: RSVPItem<T>[] = [];\n #currentIndex = 0;\n #hasPresentedCurrent = false;\n #deadline: number | null = null;\n #remainingDelay: number | null = null;\n #msPerItem: number = 60_000 / DEFAULT_WPM;\n #destroyed = false;\n\n constructor(options: RSVPEngineOptions<T> = {}) {\n this.#stateMachine = new StateMachine();\n this.#emitter = new EventEmitter<T>();\n\n this.#timeDriver = options.timeDriver ?? new SystemTimeDriver();\n this.#scheduler = options.scheduler ?? new DriftCorrectedScheduler(this.#timeDriver);\n this.#tokenizer = options.tokenizer ?? new DefaultTokenizer<T>();\n\n // msPerItem takes precedence over wpm\n if (options.msPerItem !== undefined) {\n this.setMsPerItem(options.msPerItem);\n } else if (options.wpm !== undefined) {\n this.setSpeed(options.wpm);\n }\n\n // Constructor failures must remain observable because listeners cannot be attached yet.\n if (options.data !== undefined) {\n this.load(options.data);\n }\n }\n\n // ────────── Tokenization & Loading ──────────\n\n /**\n * Tokenizes and loads raw data into the engine using the configured tokenizer.\n */\n public load(data: T | T[]): void {\n this.#assertNotDestroyed();\n this.#assertCanLoad();\n try {\n this.loadTokens(this.#tokenizer.tokenize(data));\n } catch (error) {\n if (error instanceof InvalidInputError) {\n throw error;\n }\n this.#enterFatalError(this.#toError(error));\n throw error;\n }\n }\n\n /**\n * Directly loads pre-tokenized items into the engine.\n * Useful when tokenization is performed externally (e.g. AI or asynchronous tokenizers).\n */\n public loadTokens(tokens: Token<T>[]): void {\n this.#assertNotDestroyed();\n this.#assertCanLoad();\n validateTokens(tokens);\n const previousState = this.#stateMachine.state;\n this.#stateMachine.transition(\"load\");\n this.#scheduler.cancel();\n this.#setTokens(tokens);\n this.#currentIndex = 0;\n this.#hasPresentedCurrent = false;\n this.#deadline = null;\n this.#remainingDelay = null;\n if (previousState !== \"IDLE\") {\n this.#emitter.emit(\"stateChange\", { previous: previousState, current: \"IDLE\" });\n }\n }\n\n #assertCanLoad(): void {\n if (this.#stateMachine.state === \"PLAYING\" || this.#stateMachine.state === \"ERROR\") {\n throw new InvalidInputError(`Cannot load data while engine is ${this.#stateMachine.state}.`);\n }\n }\n\n #setTokens(tokens: Token<T>[]): void {\n this.#tokens = tokens.map((token, index) => ({\n value: token.value,\n index,\n ovpIndex: token.ovpIndex,\n delayMultiplier: token.delayMultiplier,\n }));\n }\n\n // ────────── Error handling ──────────\n\n #enterFatalError(error: Error): void {\n const previousState = this.#stateMachine.state;\n try {\n this.#stateMachine.transition(\"error\");\n } catch {\n this.#reportError(error);\n return;\n }\n this.#scheduler.cancel();\n this.#deadline = null;\n this.#remainingDelay = null;\n this.#reportError(error);\n this.#emitter.emit(\"stateChange\", { previous: previousState, current: \"ERROR\" });\n }\n\n #reportError(error: Error): void {\n this.#emitter.emit(\"error\", { error });\n }\n\n #toError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n }\n\n // ────────── Destroyed guard ──────────\n\n #assertNotDestroyed(): void {\n if (this.#destroyed) {\n throw new EngineDestroyedError();\n }\n }\n\n // ────────── Tick loop ──────────\n\n readonly #advance = (): void => {\n this.#deadline = null;\n this.#remainingDelay = null;\n if (this.#stateMachine.state !== \"PLAYING\") {\n return;\n }\n\n if (this.#currentIndex >= this.#tokens.length - 1) {\n this.#completePlayback();\n return;\n }\n\n this.#currentIndex++;\n this.#presentCurrent();\n };\n\n #presentCurrent(): void {\n const item = this.#tokens[this.#currentIndex];\n this.#hasPresentedCurrent = true;\n\n this.#emitter.emit(\"itemChange\", {\n item,\n index: this.#currentIndex,\n progress: this.progress,\n reason: \"playback\",\n });\n if (this.#stateMachine.state !== \"PLAYING\") {\n return;\n }\n\n this.#scheduleAdvance(this.#msPerItem * item.delayMultiplier);\n }\n\n #scheduleAdvance(delay: number): void {\n this.#remainingDelay = null;\n this.#deadline = this.#timeDriver.now() + delay;\n try {\n this.#scheduler.schedule(this.#advance, delay);\n } catch (error) {\n this.#enterFatalError(this.#toError(error));\n }\n }\n\n #completePlayback(): void {\n this.#scheduler.cancel();\n const previousState = this.#stateMachine.state;\n this.#stateMachine.transition(\"complete\");\n const item = this.#tokens[this.#currentIndex];\n this.#emitter.emit(\"stateChange\", { previous: previousState, current: \"COMPLETED\" });\n this.#emitter.emit(\"complete\", { item, totalItems: this.#tokens.length });\n }\n\n // ────────── Playback Control ──────────\n\n /**\n * Starts or resumes playback.\n * From STOPPED/COMPLETED: resets index to 0 and starts from beginning.\n * From PAUSED: resumes from current position.\n */\n public play(): void {\n this.#assertNotDestroyed();\n this.#startPlayback();\n }\n\n #startPlayback(): void {\n // Validate we have tokens\n if (this.#tokens.length === 0) {\n this.#reportError(new InvalidInputError(\"Cannot play: no tokens loaded.\"));\n return;\n }\n\n const previousState = this.#stateMachine.state;\n\n // Reset index if coming from STOPPED or COMPLETED\n if (previousState === \"STOPPED\" || previousState === \"COMPLETED\") {\n this.#currentIndex = 0;\n }\n\n try {\n this.#stateMachine.transition(\"play\");\n } catch (err) {\n this.#reportError(this.#toError(err));\n return;\n }\n\n this.#emitter.emit(\"stateChange\", {\n previous: previousState,\n current: \"PLAYING\",\n });\n\n if (previousState === \"PAUSED\" && this.#hasPresentedCurrent) {\n const item = this.#tokens[this.#currentIndex];\n this.#scheduleAdvance(this.#remainingDelay ?? this.#msPerItem * item.delayMultiplier);\n return;\n }\n\n this.#hasPresentedCurrent = false;\n this.#presentCurrent();\n }\n\n /**\n * Pauses playback, preserving the current index.\n */\n public pause(): void {\n this.#assertNotDestroyed();\n\n const previousState = this.#stateMachine.state;\n\n try {\n this.#stateMachine.transition(\"pause\");\n } catch (err) {\n this.#reportError(this.#toError(err));\n return;\n }\n\n this.#remainingDelay =\n this.#deadline === null ? null : Math.max(0, this.#deadline - this.#timeDriver.now());\n this.#scheduler.cancel();\n this.#deadline = null;\n\n this.#emitter.emit(\"stateChange\", {\n previous: previousState,\n current: \"PAUSED\",\n });\n }\n\n /**\n * Stops playback and resets the index to 0.\n */\n public stop(): void {\n this.#assertNotDestroyed();\n\n const previousState = this.#stateMachine.state;\n\n try {\n this.#stateMachine.transition(\"stop\");\n } catch (err) {\n this.#reportError(this.#toError(err));\n return;\n }\n\n this.#scheduler.cancel();\n this.#currentIndex = 0;\n this.#hasPresentedCurrent = false;\n this.#deadline = null;\n this.#remainingDelay = null;\n\n this.#emitter.emit(\"stateChange\", {\n previous: previousState,\n current: \"STOPPED\",\n });\n }\n\n /**\n * Jumps to a specific token index. Transitions to PAUSED state.\n * Only available from PAUSED, STOPPED, or COMPLETED states.\n */\n public seek(index: number): void {\n this.#assertNotDestroyed();\n\n if (index < 0 || index >= this.#tokens.length) {\n this.#reportError(new IndexOutOfBoundsError(index, this.#tokens.length));\n return;\n }\n\n const previousState = this.#stateMachine.state;\n\n try {\n this.#stateMachine.transition(\"seek\");\n } catch (err) {\n this.#reportError(this.#toError(err));\n return;\n }\n\n this.#currentIndex = index;\n this.#hasPresentedCurrent = true;\n this.#remainingDelay = this.#msPerItem * this.#tokens[index].delayMultiplier;\n\n if (previousState !== \"PAUSED\") {\n this.#emitter.emit(\"stateChange\", {\n previous: previousState,\n current: \"PAUSED\",\n });\n }\n\n this.#emitter.emit(\"itemChange\", {\n item: this.#tokens[index],\n index,\n progress: this.progress,\n reason: \"seek\",\n });\n }\n\n /**\n * Advances to the next token. Only available from PAUSED state.\n */\n public next(): void {\n this.#assertNotDestroyed();\n\n if (this.#stateMachine.state !== \"PAUSED\") {\n this.#emitter.emit(\"error\", {\n error: new InvalidInputError(\"next() is only available in PAUSED state.\"),\n });\n return;\n }\n\n if (this.#currentIndex < this.#tokens.length - 1) {\n this.#currentIndex++;\n this.#hasPresentedCurrent = true;\n this.#remainingDelay = this.#msPerItem * this.#tokens[this.#currentIndex].delayMultiplier;\n this.#emitter.emit(\"itemChange\", {\n item: this.#tokens[this.#currentIndex],\n index: this.#currentIndex,\n progress: this.progress,\n reason: \"next\",\n });\n }\n }\n\n /**\n * Retreats to the previous token. Only available from PAUSED state.\n */\n public previous(): void {\n this.#assertNotDestroyed();\n\n if (this.#stateMachine.state !== \"PAUSED\") {\n this.#emitter.emit(\"error\", {\n error: new InvalidInputError(\"previous() is only available in PAUSED state.\"),\n });\n return;\n }\n\n if (this.#currentIndex > 0) {\n this.#currentIndex--;\n this.#hasPresentedCurrent = true;\n this.#remainingDelay = this.#msPerItem * this.#tokens[this.#currentIndex].delayMultiplier;\n this.#emitter.emit(\"itemChange\", {\n item: this.#tokens[this.#currentIndex],\n index: this.#currentIndex,\n progress: this.progress,\n reason: \"previous\",\n });\n }\n }\n\n /**\n * Resets engine to IDLE state, clearing all data.\n * Only valid from ERROR state.\n */\n public reset(): void {\n this.#assertNotDestroyed();\n\n const previousState = this.#stateMachine.state;\n\n try {\n this.#stateMachine.transition(\"reset\");\n } catch (err) {\n this.#reportError(this.#toError(err));\n return;\n }\n\n this.#scheduler.cancel();\n this.#tokens = [];\n this.#currentIndex = 0;\n this.#hasPresentedCurrent = false;\n this.#deadline = null;\n this.#remainingDelay = null;\n\n this.#emitter.emit(\"stateChange\", {\n previous: previousState,\n current: \"IDLE\",\n });\n }\n\n /**\n * Clears all timers, removes listeners, and disposes the engine.\n * Required for SPA component unmount cleanup.\n */\n public destroy(): void {\n if (this.#destroyed) {\n return; // idempotent\n }\n this.#scheduler.cancel();\n this.#deadline = null;\n this.#remainingDelay = null;\n this.#emitter.removeAllListeners();\n this.#destroyed = true;\n }\n\n // ────────── Speed Control ──────────\n\n /**\n * Updates playback speed via WPM (live, mid-playback).\n */\n public setSpeed(wpm: number): void {\n this.#assertNotDestroyed();\n validateWpm(wpm);\n this.#msPerItem = 60_000 / wpm;\n }\n\n /**\n * Directly sets ms-per-item interval (live, mid-playback).\n */\n public setMsPerItem(ms: number): void {\n this.#assertNotDestroyed();\n validateMsPerItem(ms);\n this.#msPerItem = ms;\n }\n\n // ────────── State & Snapshot Getters ──────────\n\n /** Current engine state. */\n public get state(): RSVPState {\n return this.#stateMachine.state;\n }\n\n /** Current WPM computed from msPerItem. */\n public get wpm(): number {\n return 60_000 / this.#msPerItem;\n }\n\n /** Current ms-per-item interval. */\n public get msPerItem(): number {\n return this.#msPerItem;\n }\n\n /** Current token index (0-based). */\n public get currentIndex(): number {\n return this.#currentIndex;\n }\n\n /** Current token/item being displayed, or null if none. */\n public get currentItem(): RSVPItem<T> | null {\n return this.#tokens[this.#currentIndex] ?? null;\n }\n\n /** Playback progress (0–1). */\n public get progress(): number {\n if (this.#tokens.length === 0) {\n return 0;\n }\n return this.#hasPresentedCurrent ? (this.#currentIndex + 1) / this.#tokens.length : 0;\n }\n\n /** Total number of tokens/items. */\n public get totalItems(): number {\n return this.#tokens.length;\n }\n\n // ────────── Event System ──────────\n\n /**\n * Subscribes to an engine event. Returns an unsubscribe function.\n */\n public on<K extends RSVPEventType>(\n event: K,\n callback: EventCallback<RSVPEventMap<T>[K]>,\n ): UnsubscribeFn {\n this.#assertNotDestroyed();\n return this.#emitter.on(event, callback);\n }\n\n // ────────── Snapshot ──────────\n\n /**\n * Returns a frozen, readonly snapshot of the full engine state.\n */\n public snapshot(): RSVPSnapshot<T> {\n this.#assertNotDestroyed();\n return Object.freeze({\n state: this.state,\n currentIndex: this.#currentIndex,\n currentItem: this.currentItem,\n progress: this.progress,\n totalItems: this.totalItems,\n wpm: this.wpm,\n msPerItem: this.#msPerItem,\n });\n }\n}\n"]}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * \@rsvp-engine/core
3
+ * Headless, zero-dependency RSVP (Rapid Serial Visual Presentation) engine.
4
+ */
5
+ export { DEFAULT_WPM, MIN_WPM, MAX_WPM, MIN_MS_PER_ITEM, MAX_MS_PER_ITEM } from "./engine/config";
6
+ export type { RSVPEngineOptions } from "./engine/config";
7
+ export { RSVPEngine } from "./engine/rsvp-engine";
8
+ export type { RSVPItem, RSVPSnapshot } from "./engine/types";
9
+ export type { RSVPState } from "./state/types";
10
+ export type { UnsubscribeFn } from "./events/types";
11
+ export type { RSVPEventType, RSVPEventMap, StateChangePayload, ItemChangePayload, ItemChangeReason, ErrorPayload, EventCallback, CompletePayload, } from "./events/types";
12
+ export { EventEmitter } from "./events/event-emitter";
13
+ export { StateMachine } from "./state/state-machine";
14
+ export type { StateMachineAction } from "./state/types";
15
+ export type { OVPStrategy, Token, TokenizerStrategy } from "./tokenizer/types";
16
+ export { DefaultOVPStrategy } from "./tokenizer/ovp";
17
+ export { DefaultTokenizer } from "./tokenizer/default-tokenizer";
18
+ export type { DefaultTokenizerOptions } from "./tokenizer/default-tokenizer";
19
+ export type { SchedulerStrategy, TimeDriver } from "./scheduler/types";
20
+ export { SystemTimeDriver } from "./scheduler/system-time-driver";
21
+ export { DriftCorrectedScheduler } from "./scheduler/drift-corrected-scheduler";
22
+ export { InvalidTransitionError, EngineDestroyedError, InvalidInputError, IndexOutOfBoundsError, } from "./errors";
23
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,23 @@
1
+ /**
2
+ * \@rsvp-engine/core
3
+ * Headless, zero-dependency RSVP (Rapid Serial Visual Presentation) engine.
4
+ */
5
+ export { DEFAULT_WPM, MIN_WPM, MAX_WPM, MIN_MS_PER_ITEM, MAX_MS_PER_ITEM } from "./engine/config";
6
+ export type { RSVPEngineOptions } from "./engine/config";
7
+ export { RSVPEngine } from "./engine/rsvp-engine";
8
+ export type { RSVPItem, RSVPSnapshot } from "./engine/types";
9
+ export type { RSVPState } from "./state/types";
10
+ export type { UnsubscribeFn } from "./events/types";
11
+ export type { RSVPEventType, RSVPEventMap, StateChangePayload, ItemChangePayload, ItemChangeReason, ErrorPayload, EventCallback, CompletePayload, } from "./events/types";
12
+ export { EventEmitter } from "./events/event-emitter";
13
+ export { StateMachine } from "./state/state-machine";
14
+ export type { StateMachineAction } from "./state/types";
15
+ export type { OVPStrategy, Token, TokenizerStrategy } from "./tokenizer/types";
16
+ export { DefaultOVPStrategy } from "./tokenizer/ovp";
17
+ export { DefaultTokenizer } from "./tokenizer/default-tokenizer";
18
+ export type { DefaultTokenizerOptions } from "./tokenizer/default-tokenizer";
19
+ export type { SchedulerStrategy, TimeDriver } from "./scheduler/types";
20
+ export { SystemTimeDriver } from "./scheduler/system-time-driver";
21
+ export { DriftCorrectedScheduler } from "./scheduler/drift-corrected-scheduler";
22
+ export { InvalidTransitionError, EngineDestroyedError, InvalidInputError, IndexOutOfBoundsError, } from "./errors";
23
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClG,YAAY,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAGlD,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC7D,YAAY,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAGpD,YAAY,EACV,aAAa,EACb,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,eAAe,GAChB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAGtD,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,YAAY,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAGxD,YAAY,EAAE,WAAW,EAAE,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,YAAY,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AAG7E,YAAY,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAC;AAGhF,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,UAAU,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ var v=1,S=6e3,T=10,b=6e4,E=300;var u=class extends Error{from;action;constructor(e,t){super(`Invalid transition: cannot perform '${t}' from '${e}' state.`),this.name="InvalidTransitionError",this.from=e,this.action=t;}},p=class extends Error{constructor(){super("Cannot interact with a destroyed engine instance."),this.name="EngineDestroyedError";}},o=class extends Error{constructor(e){super(e),this.name="InvalidInputError";}},m=class extends Error{index;totalItems;constructor(e,t){super(`Index ${e} is out of bounds (0..${t-1}).`),this.name="IndexOutOfBoundsError",this.index=e,this.totalItems=t;}};var c=class{#e={stateChange:new Set,itemChange:new Set,complete:new Set,error:new Set};on(e,t){let r=this.#e[e];return r.add(t),()=>{r.delete(t);}}emit(e,t){let r=this.#e[e],n=Array.from(r);for(let s of n)try{s(t);}catch(l){if(e!=="error"){let a=l instanceof Error?l:new Error(String(l));this.emit("error",{error:a});}}}removeAllListeners(){for(let e of Object.values(this.#e))e.clear();}};var h=class{now(){let e=globalThis;return typeof e.performance?.now=="function"?e.performance.now():Date.now()}setTimeout(e,t){let r=globalThis;if(typeof r.setTimeout!="function")throw new TypeError("The runtime does not provide setTimeout.");return r.setTimeout(e,t)}clearTimeout(e){let t=globalThis;if(typeof t.clearTimeout!="function")throw new TypeError("The runtime does not provide clearTimeout.");t.clearTimeout(e);}};var d=class{#e;#t=null;#n=null;constructor(e=new h){this.#e=e;}schedule(e,t){if(!Number.isFinite(t)||t<=0)throw new RangeError("delayMs must be a positive finite number.");this.#t!==null&&this.cancel();let r=this.#e.now(),n=t;if(this.#n===null)this.#n=r+t;else {let s=r-this.#n;s>=t?this.#n=r+t:(this.#n+=t,n=Math.max(t/2,t-s));}this.#t=this.#e.setTimeout(()=>{this.#t=null,e();},n);}cancel(){this.#u(),this.#n=null;}#u(){this.#t!==null&&(this.#e.clearTimeout(this.#t),this.#t=null);}};var f=class{#e="IDLE";#t={IDLE:{load:"IDLE",play:"PLAYING",error:"ERROR"},PLAYING:{pause:"PAUSED",stop:"STOPPED",complete:"COMPLETED",error:"ERROR"},PAUSED:{load:"IDLE",play:"PLAYING",stop:"STOPPED",seek:"PAUSED",error:"ERROR"},STOPPED:{load:"IDLE",play:"PLAYING",seek:"PAUSED",error:"ERROR"},COMPLETED:{load:"IDLE",play:"PLAYING",seek:"PAUSED",error:"ERROR"},ERROR:{reset:"IDLE"}};get state(){return this.#e}transition(e){let r=this.#t[this.#e][e];if(!r)throw new u(this.#e,e);return this.#e=r,this.#e}};var y=class{calculate(e){if(!e)return 0;let t=w(e),r=t.filter(({segment:s})=>!x(s));if(r.length===0)return t[0].index;let n=r.length<=3?0:Math.floor(r.length*.25);return r[n].index}};function w(i){let e=globalThis.Intl?.Segmenter;if(typeof e=="function")return Array.from(new e(void 0,{granularity:"grapheme"}).segment(i));let t=[],r=0;for(let n of i){let s=t.at(-1);s&&(/^[\p{M}\uFE0F]$/u.test(n)||n==="\u200D"||s.segment.endsWith("\u200D"))?t[t.length-1]={index:s.index,segment:s.segment+n}:t.push({index:r,segment:n}),r+=n.length;}return t}function x(i){return /^[\p{P}\p{Z}]+$/u.test(i)}var g=class{#e;#t;#n;#u;#c;constructor(e={}){this.#e=e.sentenceDelay??2,this.#t=e.clauseDelay??1.5,this.#n=e.dashDelay??1.3,this.#u=e.nestedTokenize??false,this.#c=e.ovpStrategy??new y,this.#l();}tokenize(e){return e==null?[]:Array.isArray(e)?this.#i(e):typeof e=="string"?this.#r(e):[{value:e,ovpIndex:0,delayMultiplier:this.#s(e)}]}#i(e){return this.#u?e.flatMap(t=>this.tokenize(t)):e.map(t=>({value:t,ovpIndex:typeof t=="string"?this.#c.calculate(t):0,delayMultiplier:this.#s(t)}))}#r(e){let t=e.trim();return t?this.#o(t).map(n=>({value:n,ovpIndex:this.#c.calculate(n),delayMultiplier:this.#s(n)})):[]}#s(e){return typeof e=="string"?/[.!?。!?…]+[\p{Pe}\p{Pf}"']*$/u.test(e)?this.#e:/[,;:,、;:]+[\p{Pe}\p{Pf}"']*$/u.test(e)?this.#t:/\p{Pd}+[\p{Pe}\p{Pf}"']*$/u.test(e)?this.#n:1:1}#l(){let e=[["sentenceDelay",this.#e],["clauseDelay",this.#t],["dashDelay",this.#n]];for(let[t,r]of e)if(!Number.isFinite(r)||r<=0)throw new o(`${t} must be a positive finite number.`)}#o(e){let t=globalThis.Intl?.Segmenter;if(typeof t!="function")return e.split(/\s+/);let r=new t(void 0,{granularity:"word"}),n=[],s="",l=true;for(let a of r.segment(e))/^\s+$/u.test(a.segment)?(s&&n.push(s),s="",l=true):a.isWordLike||!M(a.segment)?(n.push(s+a.segment),s="",l=false):l||s||/^[\p{Ps}\p{Pi}]+$/u.test(a.segment)?s+=a.segment:n[n.length-1]+=a.segment;return s&&n.push(s),n}};function M(i){return /^\p{P}+$/u.test(i)}function I(i){if(!Array.isArray(i))throw new o("tokens must be an array.");for(let[e,t]of i.entries()){if(typeof t!="object"||t===null)throw new o(`Token ${e} must be an object.`);if(!Number.isInteger(t.ovpIndex)||t.ovpIndex<0)throw new o(`Token ${e} has an invalid ovpIndex.`);if(typeof t.value=="string"&&t.ovpIndex>t.value.length)throw new o(`Token ${e} has an out-of-range ovpIndex.`);if(!Number.isFinite(t.delayMultiplier)||t.delayMultiplier<=0)throw new o(`Token ${e} has an invalid delayMultiplier.`)}}function D(i){if(!Number.isFinite(i)||i<1||i>6e3)throw new o(`wpm must be a finite number between ${1} and ${6e3}.`)}function R(i){if(!Number.isFinite(i)||i<10||i>6e4)throw new o(`msPerItem must be a finite number between ${10} and ${6e4}.`)}var P=class{#e;#t;#n;#u;#c;#i=[];#r=0;#s=false;#l=null;#o=null;#h=6e4/300;#d=false;constructor(e={}){this.#e=new f,this.#t=new c,this.#u=e.timeDriver??new h,this.#n=e.scheduler??new d(this.#u),this.#c=e.tokenizer??new g,e.msPerItem!==void 0?this.setMsPerItem(e.msPerItem):e.wpm!==void 0&&this.setSpeed(e.wpm),e.data!==void 0&&this.load(e.data);}load(e){this.#a(),this.#f();try{this.loadTokens(this.#c.tokenize(e));}catch(t){throw t instanceof o||this.#y(this.#m(t)),t}}loadTokens(e){this.#a(),this.#f(),I(e);let t=this.#e.state;this.#e.transition("load"),this.#n.cancel(),this.#S(e),this.#r=0,this.#s=false,this.#l=null,this.#o=null,t!=="IDLE"&&this.#t.emit("stateChange",{previous:t,current:"IDLE"});}#f(){if(this.#e.state==="PLAYING"||this.#e.state==="ERROR")throw new o(`Cannot load data while engine is ${this.#e.state}.`)}#S(e){this.#i=e.map((t,r)=>({value:t.value,index:r,ovpIndex:t.ovpIndex,delayMultiplier:t.delayMultiplier}));}#y(e){let t=this.#e.state;try{this.#e.transition("error");}catch{this.#p(e);return}this.#n.cancel(),this.#l=null,this.#o=null,this.#p(e),this.#t.emit("stateChange",{previous:t,current:"ERROR"});}#p(e){this.#t.emit("error",{error:e});}#m(e){return e instanceof Error?e:new Error(String(e))}#a(){if(this.#d)throw new p}#T=()=>{if(this.#l=null,this.#o=null,this.#e.state==="PLAYING"){if(this.#r>=this.#i.length-1){this.#b();return}this.#r++,this.#g();}};#g(){let e=this.#i[this.#r];this.#s=true,this.#t.emit("itemChange",{item:e,index:this.#r,progress:this.progress,reason:"playback"}),this.#e.state==="PLAYING"&&this.#v(this.#h*e.delayMultiplier);}#v(e){this.#o=null,this.#l=this.#u.now()+e;try{this.#n.schedule(this.#T,e);}catch(t){this.#y(this.#m(t));}}#b(){this.#n.cancel();let e=this.#e.state;this.#e.transition("complete");let t=this.#i[this.#r];this.#t.emit("stateChange",{previous:e,current:"COMPLETED"}),this.#t.emit("complete",{item:t,totalItems:this.#i.length});}play(){this.#a(),this.#P();}#P(){if(this.#i.length===0){this.#p(new o("Cannot play: no tokens loaded."));return}let e=this.#e.state;(e==="STOPPED"||e==="COMPLETED")&&(this.#r=0);try{this.#e.transition("play");}catch(t){this.#p(this.#m(t));return}if(this.#t.emit("stateChange",{previous:e,current:"PLAYING"}),e==="PAUSED"&&this.#s){let t=this.#i[this.#r];this.#v(this.#o??this.#h*t.delayMultiplier);return}this.#s=false,this.#g();}pause(){this.#a();let e=this.#e.state;try{this.#e.transition("pause");}catch(t){this.#p(this.#m(t));return}this.#o=this.#l===null?null:Math.max(0,this.#l-this.#u.now()),this.#n.cancel(),this.#l=null,this.#t.emit("stateChange",{previous:e,current:"PAUSED"});}stop(){this.#a();let e=this.#e.state;try{this.#e.transition("stop");}catch(t){this.#p(this.#m(t));return}this.#n.cancel(),this.#r=0,this.#s=false,this.#l=null,this.#o=null,this.#t.emit("stateChange",{previous:e,current:"STOPPED"});}seek(e){if(this.#a(),e<0||e>=this.#i.length){this.#p(new m(e,this.#i.length));return}let t=this.#e.state;try{this.#e.transition("seek");}catch(r){this.#p(this.#m(r));return}this.#r=e,this.#s=true,this.#o=this.#h*this.#i[e].delayMultiplier,t!=="PAUSED"&&this.#t.emit("stateChange",{previous:t,current:"PAUSED"}),this.#t.emit("itemChange",{item:this.#i[e],index:e,progress:this.progress,reason:"seek"});}next(){if(this.#a(),this.#e.state!=="PAUSED"){this.#t.emit("error",{error:new o("next() is only available in PAUSED state.")});return}this.#r<this.#i.length-1&&(this.#r++,this.#s=true,this.#o=this.#h*this.#i[this.#r].delayMultiplier,this.#t.emit("itemChange",{item:this.#i[this.#r],index:this.#r,progress:this.progress,reason:"next"}));}previous(){if(this.#a(),this.#e.state!=="PAUSED"){this.#t.emit("error",{error:new o("previous() is only available in PAUSED state.")});return}this.#r>0&&(this.#r--,this.#s=true,this.#o=this.#h*this.#i[this.#r].delayMultiplier,this.#t.emit("itemChange",{item:this.#i[this.#r],index:this.#r,progress:this.progress,reason:"previous"}));}reset(){this.#a();let e=this.#e.state;try{this.#e.transition("reset");}catch(t){this.#p(this.#m(t));return}this.#n.cancel(),this.#i=[],this.#r=0,this.#s=false,this.#l=null,this.#o=null,this.#t.emit("stateChange",{previous:e,current:"IDLE"});}destroy(){this.#d||(this.#n.cancel(),this.#l=null,this.#o=null,this.#t.removeAllListeners(),this.#d=true);}setSpeed(e){this.#a(),D(e),this.#h=6e4/e;}setMsPerItem(e){this.#a(),R(e),this.#h=e;}get state(){return this.#e.state}get wpm(){return 6e4/this.#h}get msPerItem(){return this.#h}get currentIndex(){return this.#r}get currentItem(){return this.#i[this.#r]??null}get progress(){return this.#i.length===0?0:this.#s?(this.#r+1)/this.#i.length:0}get totalItems(){return this.#i.length}on(e,t){return this.#a(),this.#t.on(e,t)}snapshot(){return this.#a(),Object.freeze({state:this.state,currentIndex:this.#r,currentItem:this.currentItem,progress:this.progress,totalItems:this.totalItems,wpm:this.wpm,msPerItem:this.#h})}};export{E as DEFAULT_WPM,y as DefaultOVPStrategy,g as DefaultTokenizer,d as DriftCorrectedScheduler,p as EngineDestroyedError,c as EventEmitter,m as IndexOutOfBoundsError,o as InvalidInputError,u as InvalidTransitionError,b as MAX_MS_PER_ITEM,S as MAX_WPM,T as MIN_MS_PER_ITEM,v as MIN_WPM,P as RSVPEngine,f as StateMachine,h as SystemTimeDriver};//# sourceMappingURL=index.js.map
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/engine/config.ts","../src/errors.ts","../src/events/event-emitter.ts","../src/scheduler/system-time-driver.ts","../src/scheduler/drift-corrected-scheduler.ts","../src/state/state-machine.ts","../src/tokenizer/ovp.ts","../src/tokenizer/default-tokenizer.ts","../src/engine/validation.ts","../src/engine/rsvp-engine.ts"],"names":["MIN_WPM","MAX_WPM","MIN_MS_PER_ITEM","MAX_MS_PER_ITEM","DEFAULT_WPM","InvalidTransitionError","from","action","EngineDestroyedError","InvalidInputError","message","IndexOutOfBoundsError","index","totalItems","EventEmitter","#listenerRegistry","event","callback","eventListeners","payload","listenersSnapshot","listener","err","error","SystemTimeDriver","runtimeGlobal","ms","handle","DriftCorrectedScheduler","#timeDriver","#timeoutHandle","#expectedTime","timeDriver","task","delayMs","now","actualDelay","drift","#clearTimer","StateMachine","#state","#transitions","nextState","DefaultOVPStrategy","text","graphemes","segmentGraphemes","content","segment","isPunctuation","target","Segmenter","previous","value","DefaultTokenizer","#sentenceDelay","#clauseDelay","#dashDelay","#nestedTokenize","#ovpStrategy","options","#validateOptions","input","#tokenizeArray","#tokenizeText","#calculateDelay","items","item","trimmed","#segmentWords","word","name","segmenter","tokens","prefix","atBoundary","part","validateTokens","token","validateWpm","wpm","validateMsPerItem","RSVPEngine","#stateMachine","#emitter","#scheduler","#tokenizer","#tokens","#currentIndex","#hasPresentedCurrent","#deadline","#remainingDelay","#msPerItem","#destroyed","data","#assertNotDestroyed","#assertCanLoad","#enterFatalError","#toError","previousState","#setTokens","#reportError","#advance","#completePlayback","#presentCurrent","#scheduleAdvance","delay","#startPlayback"],"mappings":"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","file":"index.js","sourcesContent":["import type { SchedulerStrategy, TimeDriver } from \"../scheduler/types\";\nimport type { TokenizerStrategy } from \"../tokenizer/types\";\n\n/**\n * Configuration options for the RSVP Engine.\n */\nexport interface RSVPEngineOptions<T = string> {\n /** Raw text string, single generic item, or generic item array. */\n readonly data?: T | T[];\n /** Target speed in Words Per Minute. Defaults to 300. */\n readonly wpm?: number;\n /** Direct ms-per-item override. Takes precedence over `wpm`. */\n readonly msPerItem?: number;\n /** Custom tokenizer strategy. Defaults to `DefaultTokenizer`. */\n readonly tokenizer?: TokenizerStrategy<T>;\n /** Custom scheduler strategy. Defaults to `DriftCorrectedScheduler`. */\n readonly scheduler?: SchedulerStrategy;\n /** Clock used to preserve remaining display time across pause/resume. */\n readonly timeDriver?: TimeDriver;\n}\n\n/** Minimum allowed playback speed in Words Per Minute. */\nexport const MIN_WPM = 1;\n/** Maximum allowed playback speed in Words Per Minute. */\nexport const MAX_WPM = 6_000;\n/** Minimum allowed display interval per item in milliseconds. */\nexport const MIN_MS_PER_ITEM = 60_000 / MAX_WPM;\n/** Maximum allowed display interval per item in milliseconds. */\nexport const MAX_MS_PER_ITEM = 60_000 / MIN_WPM;\n/** Default playback speed in Words Per Minute. */\nexport const DEFAULT_WPM = 300;\n","/**\n * Thrown when an invalid state transition is attempted.\n *\n */\nexport class InvalidTransitionError extends Error {\n public readonly from: string;\n public readonly action: string;\n\n constructor(from: string, action: string) {\n super(`Invalid transition: cannot perform '${action}' from '${from}' state.`);\n this.name = \"InvalidTransitionError\";\n this.from = from;\n this.action = action;\n }\n}\n\n/**\n * Thrown when a method is called on a destroyed engine instance.\n */\nexport class EngineDestroyedError extends Error {\n constructor() {\n super(\"Cannot interact with a destroyed engine instance.\");\n this.name = \"EngineDestroyedError\";\n }\n}\n\n/**\n * Thrown when invalid input is provided to the engine (empty text, null data, etc.).\n */\nexport class InvalidInputError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidInputError\";\n }\n}\n\n/**\n * Thrown when a seek index is out of bounds.\n */\nexport class IndexOutOfBoundsError extends Error {\n public readonly index: number;\n public readonly totalItems: number;\n\n constructor(index: number, totalItems: number) {\n super(`Index ${index} is out of bounds (0..${totalItems - 1}).`);\n this.name = \"IndexOutOfBoundsError\";\n this.index = index;\n this.totalItems = totalItems;\n }\n}\n","import type { EventCallback, RSVPEventMap, RSVPEventType, UnsubscribeFn } from \"./types\";\n\ntype ListenerRegistry<T> = {\n [K in RSVPEventType]: Set<EventCallback<RSVPEventMap<T>[K]>>;\n};\n\nexport class EventEmitter<T = string> {\n readonly #listenerRegistry: ListenerRegistry<T> = {\n stateChange: new Set(),\n itemChange: new Set(),\n complete: new Set(),\n error: new Set(),\n };\n\n public on<K extends RSVPEventType>(\n event: K,\n callback: EventCallback<RSVPEventMap<T>[K]>,\n ): UnsubscribeFn {\n const eventListeners = this.#listenerRegistry[event];\n eventListeners.add(callback);\n\n return () => {\n eventListeners.delete(callback);\n };\n }\n\n public emit<K extends RSVPEventType>(event: K, payload: RSVPEventMap<T>[K]): void {\n const eventListeners = this.#listenerRegistry[event];\n\n // Copy to array to handle safe iteration if unsubscribed during emit\n const listenersSnapshot = Array.from(eventListeners);\n\n for (const listener of listenersSnapshot) {\n try {\n listener(payload);\n } catch (err) {\n if (event !== \"error\") {\n const error = err instanceof Error ? err : new Error(String(err));\n this.emit(\"error\", { error });\n }\n }\n }\n }\n\n public removeAllListeners(): void {\n for (const eventListeners of Object.values(this.#listenerRegistry)) {\n eventListeners.clear();\n }\n }\n}\n","import type { TimeDriver } from \"./types\";\nimport type { RuntimeGlobal } from \"../runtimeGlobal\";\n\n/**\n * Default implementation of `TimeDriver` utilizing platform globals\n * (`performance.now` / `Date.now` and `setTimeout` / `clearTimeout`).\n */\nexport class SystemTimeDriver implements TimeDriver {\n public now(): number {\n const runtimeGlobal = globalThis as RuntimeGlobal;\n if (typeof runtimeGlobal.performance?.now === \"function\") {\n return runtimeGlobal.performance.now();\n }\n return Date.now();\n }\n\n public setTimeout(callback: () => void, ms: number): unknown {\n const runtimeGlobal = globalThis as RuntimeGlobal;\n if (typeof runtimeGlobal.setTimeout !== \"function\") {\n throw new TypeError(\"The runtime does not provide setTimeout.\");\n }\n return runtimeGlobal.setTimeout(callback, ms);\n }\n\n public clearTimeout(handle: unknown): void {\n const runtimeGlobal = globalThis as RuntimeGlobal;\n if (typeof runtimeGlobal.clearTimeout !== \"function\") {\n throw new TypeError(\"The runtime does not provide clearTimeout.\");\n }\n runtimeGlobal.clearTimeout(handle);\n }\n}\n","import type { SchedulerStrategy, TimeDriver } from \"./types\";\n\nimport { SystemTimeDriver } from \"./system-time-driver\";\n\n/**\n * High-precision, drift-corrected task scheduler.\n *\n * Implements the {@link SchedulerStrategy} interface by tracking an ideal target timeline\n * (`#expectedTime`) across consecutive task executions. Any latency or jitter introduced\n * by the JavaScript event loop (timer drift) is calculated and subtracted from subsequent\n * intervals, ensuring stable WPM presentation without cumulative time slippage.\n *\n * @example\n * ```ts\n * const scheduler = new DriftCorrectedScheduler();\n *\n * // Schedule a task after 200ms\n * scheduler.schedule(() => {\n * console.log(\"Tick!\");\n * }, 200);\n *\n * // Cancel any pending task\n * scheduler.cancel();\n * ```\n */\nexport class DriftCorrectedScheduler implements SchedulerStrategy {\n readonly #timeDriver: TimeDriver;\n #timeoutHandle: unknown = null;\n #expectedTime: number | null = null;\n\n /**\n * Creates a new instance of `DriftCorrectedScheduler`.\n *\n * @param timeDriver - The underlying time and timer provider. Defaults to {@link SystemTimeDriver}.\n */\n constructor(timeDriver: TimeDriver = new SystemTimeDriver()) {\n this.#timeDriver = timeDriver;\n }\n\n /**\n * Schedules a task to execute after the given delay, compensating for accumulated drift.\n *\n * If a previous task is currently pending, it is automatically cancelled before scheduling\n * the new one. When part of an active session, the actual timeout duration is adjusted\n * by subtracting any detected execution delay (`drift = now - expectedTime`).\n *\n * @param task - The callback function to execute.\n * @param delayMs - The target delay in milliseconds.\n */\n public schedule(task: () => void, delayMs: number): void {\n if (!Number.isFinite(delayMs) || delayMs <= 0) {\n throw new RangeError(\"delayMs must be a positive finite number.\");\n }\n\n if (this.#timeoutHandle !== null) {\n this.cancel();\n }\n\n const now = this.#timeDriver.now();\n let actualDelay = delayMs;\n\n if (this.#expectedTime === null) {\n this.#expectedTime = now + delayMs;\n } else {\n const drift = now - this.#expectedTime;\n if (drift >= delayMs) {\n // Large catch-up bursts make RSVP items unreadable; start a fresh timeline instead.\n this.#expectedTime = now + delayMs;\n } else {\n this.#expectedTime += delayMs;\n actualDelay = Math.max(delayMs / 2, delayMs - drift);\n }\n }\n\n this.#timeoutHandle = this.#timeDriver.setTimeout(() => {\n this.#timeoutHandle = null;\n task();\n }, actualDelay);\n }\n\n /**\n * Cancels any pending scheduled task and resets the session timeline.\n *\n * Calling this method halts the drift compensation timeline so that the next\n * call to {@link schedule} starts a fresh session without previous drift history.\n */\n public cancel(): void {\n this.#clearTimer();\n this.#expectedTime = null;\n }\n\n #clearTimer(): void {\n if (this.#timeoutHandle !== null) {\n this.#timeDriver.clearTimeout(this.#timeoutHandle);\n this.#timeoutHandle = null;\n }\n }\n}\n","import type { RSVPState, StateMachineAction } from \"./types\";\n\nimport { InvalidTransitionError } from \"../errors\";\n\nexport class StateMachine {\n #state: RSVPState = \"IDLE\";\n\n readonly #transitions: Record<RSVPState, Partial<Record<StateMachineAction, RSVPState>>> = {\n IDLE: {\n load: \"IDLE\",\n play: \"PLAYING\",\n error: \"ERROR\",\n },\n PLAYING: {\n pause: \"PAUSED\",\n stop: \"STOPPED\",\n complete: \"COMPLETED\",\n error: \"ERROR\",\n },\n PAUSED: {\n load: \"IDLE\",\n play: \"PLAYING\",\n stop: \"STOPPED\",\n seek: \"PAUSED\",\n error: \"ERROR\",\n },\n STOPPED: {\n load: \"IDLE\",\n play: \"PLAYING\",\n seek: \"PAUSED\",\n error: \"ERROR\",\n },\n COMPLETED: {\n load: \"IDLE\",\n play: \"PLAYING\",\n seek: \"PAUSED\",\n error: \"ERROR\",\n },\n ERROR: {\n reset: \"IDLE\",\n },\n };\n\n public get state(): RSVPState {\n return this.#state;\n }\n\n public transition(action: StateMachineAction): RSVPState {\n const allowedTransitions = this.#transitions[this.#state];\n const nextState = allowedTransitions[action];\n\n if (!nextState) {\n throw new InvalidTransitionError(this.#state, action);\n }\n\n this.#state = nextState;\n return this.#state;\n }\n}\n","import type { OVPStrategy } from \"./types\";\n\n/**\n * Default grapheme-aware Optimal Viewing Position strategy.\n */\nexport class DefaultOVPStrategy implements OVPStrategy {\n /**\n * Calculates the Optimal Viewing Position for text.\n * @returns The zero-based UTF-16 offset of the OVP grapheme.\n */\n public calculate(text: string): number {\n if (!text) {\n return 0;\n }\n\n const graphemes = segmentGraphemes(text);\n const content = graphemes.filter(({ segment }) => !isPunctuation(segment));\n\n if (content.length === 0) {\n return graphemes[0].index;\n }\n\n const target = content.length <= 3 ? 0 : Math.floor(content.length * 0.25);\n return content[target].index;\n }\n}\n\ninterface Grapheme {\n readonly index: number;\n readonly segment: string;\n}\n\nfunction segmentGraphemes(text: string): Grapheme[] {\n const Segmenter = globalThis.Intl?.Segmenter;\n if (typeof Segmenter === \"function\") {\n return Array.from(new Segmenter(undefined, { granularity: \"grapheme\" }).segment(text));\n }\n\n const graphemes: Grapheme[] = [];\n let index = 0;\n for (const segment of text) {\n const previous = graphemes.at(-1);\n if (\n previous &&\n (/^[\\p{M}\\uFE0F]$/u.test(segment) ||\n segment === \"\\u200D\" ||\n previous.segment.endsWith(\"\\u200D\"))\n ) {\n graphemes[graphemes.length - 1] = {\n index: previous.index,\n segment: previous.segment + segment,\n };\n } else {\n graphemes.push({ index, segment });\n }\n index += segment.length;\n }\n return graphemes;\n}\n\nfunction isPunctuation(value: string): boolean {\n return /^[\\p{P}\\p{Z}]+$/u.test(value);\n}\n","import type { OVPStrategy, Token, TokenizerStrategy } from \"./types\";\n\nimport { InvalidInputError } from \"../errors\";\n\nimport { DefaultOVPStrategy } from \"./ovp\";\n\n/**\n * Options for configuring `DefaultTokenizer`.\n */\nexport interface DefaultTokenizerOptions {\n /** Multiplier for sentence-ending punctuation (`.`, `!`, `?`). Defaults to `2.0`. */\n readonly sentenceDelay?: number;\n /** Multiplier for clause-ending punctuation (`,`, `;`, `:`). Defaults to `1.5`. */\n readonly clauseDelay?: number;\n /** Multiplier for dashes (`—`, `–`). Defaults to `1.3`. */\n readonly dashDelay?: number;\n /** Whether array elements and nested inputs should be recursively tokenized. Defaults to `false`. */\n readonly nestedTokenize?: boolean;\n /** Strategy used to calculate viewing positions for string tokens. */\n readonly ovpStrategy?: OVPStrategy;\n}\n\n/**\n * Default configurable tokenizer for text, items, and array inputs.\n * Splits text on whitespace and assigns delay multipliers based on punctuation.\n * Wraps custom generic items or arrays into presentable tokens.\n * @typeParam T - The type of items being presented (defaults to `string`).\n */\nexport class DefaultTokenizer<T> implements TokenizerStrategy<T> {\n readonly #sentenceDelay: number;\n readonly #clauseDelay: number;\n readonly #dashDelay: number;\n readonly #nestedTokenize: boolean;\n readonly #ovpStrategy: OVPStrategy;\n\n constructor(options: DefaultTokenizerOptions = {}) {\n this.#sentenceDelay = options.sentenceDelay ?? 2.0;\n this.#clauseDelay = options.clauseDelay ?? 1.5;\n this.#dashDelay = options.dashDelay ?? 1.3;\n this.#nestedTokenize = options.nestedTokenize ?? false;\n this.#ovpStrategy = options.ovpStrategy ?? new DefaultOVPStrategy();\n this.#validateOptions();\n }\n\n public tokenize(input: T | T[]): Token<T>[] {\n if (input === null || input === undefined) {\n return [];\n }\n\n if (Array.isArray(input)) {\n return this.#tokenizeArray(input);\n }\n\n if (typeof input === \"string\") {\n return this.#tokenizeText(input) as Token<T>[];\n }\n\n return [\n {\n value: input,\n ovpIndex: 0,\n delayMultiplier: this.#calculateDelay(input),\n },\n ];\n }\n\n #tokenizeArray(items: T[]): Token<T>[] {\n if (this.#nestedTokenize) {\n return items.flatMap((item) => this.tokenize(item));\n }\n\n return items.map((item) => ({\n value: item,\n ovpIndex: typeof item === \"string\" ? this.#ovpStrategy.calculate(item) : 0,\n delayMultiplier: this.#calculateDelay(item),\n }));\n }\n\n #tokenizeText(text: string): Token<string>[] {\n const trimmed = text.trim();\n if (!trimmed) {\n return [];\n }\n\n const words = this.#segmentWords(trimmed);\n return words.map((word) => ({\n value: word,\n ovpIndex: this.#ovpStrategy.calculate(word),\n delayMultiplier: this.#calculateDelay(word),\n }));\n }\n\n #calculateDelay(item: unknown): number {\n if (typeof item === \"string\") {\n if (/[.!?。!?…]+[\\p{Pe}\\p{Pf}\"']*$/u.test(item)) {\n return this.#sentenceDelay;\n }\n if (/[,;:,、;:]+[\\p{Pe}\\p{Pf}\"']*$/u.test(item)) {\n return this.#clauseDelay;\n }\n if (/\\p{Pd}+[\\p{Pe}\\p{Pf}\"']*$/u.test(item)) {\n return this.#dashDelay;\n }\n return 1.0;\n }\n\n return 1.0;\n }\n\n #validateOptions(): void {\n const options = [\n [\"sentenceDelay\", this.#sentenceDelay],\n [\"clauseDelay\", this.#clauseDelay],\n [\"dashDelay\", this.#dashDelay],\n ] as const;\n for (const [name, value] of options) {\n if (!Number.isFinite(value) || value <= 0) {\n throw new InvalidInputError(`${name} must be a positive finite number.`);\n }\n }\n }\n\n #segmentWords(text: string): string[] {\n const Segmenter = globalThis.Intl?.Segmenter;\n if (typeof Segmenter !== \"function\") {\n return text.split(/\\s+/);\n }\n\n const segmenter = new Segmenter(undefined, { granularity: \"word\" });\n const tokens: string[] = [];\n let prefix = \"\";\n let atBoundary = true;\n for (const part of segmenter.segment(text)) {\n if (/^\\s+$/u.test(part.segment)) {\n if (prefix) {\n tokens.push(prefix);\n }\n prefix = \"\";\n atBoundary = true;\n } else if (part.isWordLike || !isPunctuation(part.segment)) {\n tokens.push(prefix + part.segment);\n prefix = \"\";\n atBoundary = false;\n } else if (atBoundary || prefix || /^[\\p{Ps}\\p{Pi}]+$/u.test(part.segment)) {\n prefix += part.segment;\n } else {\n tokens[tokens.length - 1] += part.segment;\n }\n }\n if (prefix) {\n tokens.push(prefix);\n }\n return tokens;\n }\n}\n\nfunction isPunctuation(value: string): boolean {\n return /^\\p{P}+$/u.test(value);\n}\n","import type { Token } from \"../tokenizer/types\";\n\nimport { InvalidInputError } from \"../errors\";\n\nimport { MAX_MS_PER_ITEM, MAX_WPM, MIN_MS_PER_ITEM, MIN_WPM } from \"./config\";\n\nexport function validateTokens<T>(tokens: Token<T>[]): void {\n if (!Array.isArray(tokens)) {\n throw new InvalidInputError(\"tokens must be an array.\");\n }\n\n for (const [index, token] of tokens.entries()) {\n if (typeof token !== \"object\" || token === null) {\n throw new InvalidInputError(`Token ${index} must be an object.`);\n }\n if (!Number.isInteger(token.ovpIndex) || token.ovpIndex < 0) {\n throw new InvalidInputError(`Token ${index} has an invalid ovpIndex.`);\n }\n if (typeof token.value === \"string\" && token.ovpIndex > token.value.length) {\n throw new InvalidInputError(`Token ${index} has an out-of-range ovpIndex.`);\n }\n if (!Number.isFinite(token.delayMultiplier) || token.delayMultiplier <= 0) {\n throw new InvalidInputError(`Token ${index} has an invalid delayMultiplier.`);\n }\n }\n}\n\nexport function validateWpm(wpm: number): void {\n if (!Number.isFinite(wpm) || wpm < MIN_WPM || wpm > MAX_WPM) {\n throw new InvalidInputError(`wpm must be a finite number between ${MIN_WPM} and ${MAX_WPM}.`);\n }\n}\n\nexport function validateMsPerItem(ms: number): void {\n if (!Number.isFinite(ms) || ms < MIN_MS_PER_ITEM || ms > MAX_MS_PER_ITEM) {\n throw new InvalidInputError(\n `msPerItem must be a finite number between ${MIN_MS_PER_ITEM} and ${MAX_MS_PER_ITEM}.`,\n );\n }\n}\n","import type { RSVPEngineOptions } from \"./config\";\nimport type { RSVPItem, RSVPSnapshot } from \"./types\";\nimport type { EventCallback, RSVPEventMap, RSVPEventType, UnsubscribeFn } from \"../events/types\";\nimport type { SchedulerStrategy, TimeDriver } from \"../scheduler/types\";\nimport type { RSVPState } from \"../state/types\";\nimport type { Token, TokenizerStrategy } from \"../tokenizer/types\";\n\nimport { EngineDestroyedError, IndexOutOfBoundsError, InvalidInputError } from \"../errors\";\nimport { EventEmitter } from \"../events/event-emitter\";\nimport { DriftCorrectedScheduler } from \"../scheduler/drift-corrected-scheduler\";\nimport { SystemTimeDriver } from \"../scheduler/system-time-driver\";\nimport { StateMachine } from \"../state/state-machine\";\nimport { DefaultTokenizer } from \"../tokenizer/default-tokenizer\";\n\nimport { DEFAULT_WPM } from \"./config\";\nimport { validateMsPerItem, validateTokens, validateWpm } from \"./validation\";\n\n/**\n * Headless RSVP Engine — orchestrates state machine, scheduler, tokenizer, and event emitter.\n * @typeParam T - The type of items being presented (defaults to `string`).\n */\nexport class RSVPEngine<T = string> {\n readonly #stateMachine: StateMachine;\n readonly #emitter: EventEmitter<T>;\n readonly #scheduler: SchedulerStrategy;\n readonly #timeDriver: TimeDriver;\n readonly #tokenizer: TokenizerStrategy<T>;\n #tokens: RSVPItem<T>[] = [];\n #currentIndex = 0;\n #hasPresentedCurrent = false;\n #deadline: number | null = null;\n #remainingDelay: number | null = null;\n #msPerItem: number = 60_000 / DEFAULT_WPM;\n #destroyed = false;\n\n constructor(options: RSVPEngineOptions<T> = {}) {\n this.#stateMachine = new StateMachine();\n this.#emitter = new EventEmitter<T>();\n\n this.#timeDriver = options.timeDriver ?? new SystemTimeDriver();\n this.#scheduler = options.scheduler ?? new DriftCorrectedScheduler(this.#timeDriver);\n this.#tokenizer = options.tokenizer ?? new DefaultTokenizer<T>();\n\n // msPerItem takes precedence over wpm\n if (options.msPerItem !== undefined) {\n this.setMsPerItem(options.msPerItem);\n } else if (options.wpm !== undefined) {\n this.setSpeed(options.wpm);\n }\n\n // Constructor failures must remain observable because listeners cannot be attached yet.\n if (options.data !== undefined) {\n this.load(options.data);\n }\n }\n\n // ────────── Tokenization & Loading ──────────\n\n /**\n * Tokenizes and loads raw data into the engine using the configured tokenizer.\n */\n public load(data: T | T[]): void {\n this.#assertNotDestroyed();\n this.#assertCanLoad();\n try {\n this.loadTokens(this.#tokenizer.tokenize(data));\n } catch (error) {\n if (error instanceof InvalidInputError) {\n throw error;\n }\n this.#enterFatalError(this.#toError(error));\n throw error;\n }\n }\n\n /**\n * Directly loads pre-tokenized items into the engine.\n * Useful when tokenization is performed externally (e.g. AI or asynchronous tokenizers).\n */\n public loadTokens(tokens: Token<T>[]): void {\n this.#assertNotDestroyed();\n this.#assertCanLoad();\n validateTokens(tokens);\n const previousState = this.#stateMachine.state;\n this.#stateMachine.transition(\"load\");\n this.#scheduler.cancel();\n this.#setTokens(tokens);\n this.#currentIndex = 0;\n this.#hasPresentedCurrent = false;\n this.#deadline = null;\n this.#remainingDelay = null;\n if (previousState !== \"IDLE\") {\n this.#emitter.emit(\"stateChange\", { previous: previousState, current: \"IDLE\" });\n }\n }\n\n #assertCanLoad(): void {\n if (this.#stateMachine.state === \"PLAYING\" || this.#stateMachine.state === \"ERROR\") {\n throw new InvalidInputError(`Cannot load data while engine is ${this.#stateMachine.state}.`);\n }\n }\n\n #setTokens(tokens: Token<T>[]): void {\n this.#tokens = tokens.map((token, index) => ({\n value: token.value,\n index,\n ovpIndex: token.ovpIndex,\n delayMultiplier: token.delayMultiplier,\n }));\n }\n\n // ────────── Error handling ──────────\n\n #enterFatalError(error: Error): void {\n const previousState = this.#stateMachine.state;\n try {\n this.#stateMachine.transition(\"error\");\n } catch {\n this.#reportError(error);\n return;\n }\n this.#scheduler.cancel();\n this.#deadline = null;\n this.#remainingDelay = null;\n this.#reportError(error);\n this.#emitter.emit(\"stateChange\", { previous: previousState, current: \"ERROR\" });\n }\n\n #reportError(error: Error): void {\n this.#emitter.emit(\"error\", { error });\n }\n\n #toError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n }\n\n // ────────── Destroyed guard ──────────\n\n #assertNotDestroyed(): void {\n if (this.#destroyed) {\n throw new EngineDestroyedError();\n }\n }\n\n // ────────── Tick loop ──────────\n\n readonly #advance = (): void => {\n this.#deadline = null;\n this.#remainingDelay = null;\n if (this.#stateMachine.state !== \"PLAYING\") {\n return;\n }\n\n if (this.#currentIndex >= this.#tokens.length - 1) {\n this.#completePlayback();\n return;\n }\n\n this.#currentIndex++;\n this.#presentCurrent();\n };\n\n #presentCurrent(): void {\n const item = this.#tokens[this.#currentIndex];\n this.#hasPresentedCurrent = true;\n\n this.#emitter.emit(\"itemChange\", {\n item,\n index: this.#currentIndex,\n progress: this.progress,\n reason: \"playback\",\n });\n if (this.#stateMachine.state !== \"PLAYING\") {\n return;\n }\n\n this.#scheduleAdvance(this.#msPerItem * item.delayMultiplier);\n }\n\n #scheduleAdvance(delay: number): void {\n this.#remainingDelay = null;\n this.#deadline = this.#timeDriver.now() + delay;\n try {\n this.#scheduler.schedule(this.#advance, delay);\n } catch (error) {\n this.#enterFatalError(this.#toError(error));\n }\n }\n\n #completePlayback(): void {\n this.#scheduler.cancel();\n const previousState = this.#stateMachine.state;\n this.#stateMachine.transition(\"complete\");\n const item = this.#tokens[this.#currentIndex];\n this.#emitter.emit(\"stateChange\", { previous: previousState, current: \"COMPLETED\" });\n this.#emitter.emit(\"complete\", { item, totalItems: this.#tokens.length });\n }\n\n // ────────── Playback Control ──────────\n\n /**\n * Starts or resumes playback.\n * From STOPPED/COMPLETED: resets index to 0 and starts from beginning.\n * From PAUSED: resumes from current position.\n */\n public play(): void {\n this.#assertNotDestroyed();\n this.#startPlayback();\n }\n\n #startPlayback(): void {\n // Validate we have tokens\n if (this.#tokens.length === 0) {\n this.#reportError(new InvalidInputError(\"Cannot play: no tokens loaded.\"));\n return;\n }\n\n const previousState = this.#stateMachine.state;\n\n // Reset index if coming from STOPPED or COMPLETED\n if (previousState === \"STOPPED\" || previousState === \"COMPLETED\") {\n this.#currentIndex = 0;\n }\n\n try {\n this.#stateMachine.transition(\"play\");\n } catch (err) {\n this.#reportError(this.#toError(err));\n return;\n }\n\n this.#emitter.emit(\"stateChange\", {\n previous: previousState,\n current: \"PLAYING\",\n });\n\n if (previousState === \"PAUSED\" && this.#hasPresentedCurrent) {\n const item = this.#tokens[this.#currentIndex];\n this.#scheduleAdvance(this.#remainingDelay ?? this.#msPerItem * item.delayMultiplier);\n return;\n }\n\n this.#hasPresentedCurrent = false;\n this.#presentCurrent();\n }\n\n /**\n * Pauses playback, preserving the current index.\n */\n public pause(): void {\n this.#assertNotDestroyed();\n\n const previousState = this.#stateMachine.state;\n\n try {\n this.#stateMachine.transition(\"pause\");\n } catch (err) {\n this.#reportError(this.#toError(err));\n return;\n }\n\n this.#remainingDelay =\n this.#deadline === null ? null : Math.max(0, this.#deadline - this.#timeDriver.now());\n this.#scheduler.cancel();\n this.#deadline = null;\n\n this.#emitter.emit(\"stateChange\", {\n previous: previousState,\n current: \"PAUSED\",\n });\n }\n\n /**\n * Stops playback and resets the index to 0.\n */\n public stop(): void {\n this.#assertNotDestroyed();\n\n const previousState = this.#stateMachine.state;\n\n try {\n this.#stateMachine.transition(\"stop\");\n } catch (err) {\n this.#reportError(this.#toError(err));\n return;\n }\n\n this.#scheduler.cancel();\n this.#currentIndex = 0;\n this.#hasPresentedCurrent = false;\n this.#deadline = null;\n this.#remainingDelay = null;\n\n this.#emitter.emit(\"stateChange\", {\n previous: previousState,\n current: \"STOPPED\",\n });\n }\n\n /**\n * Jumps to a specific token index. Transitions to PAUSED state.\n * Only available from PAUSED, STOPPED, or COMPLETED states.\n */\n public seek(index: number): void {\n this.#assertNotDestroyed();\n\n if (index < 0 || index >= this.#tokens.length) {\n this.#reportError(new IndexOutOfBoundsError(index, this.#tokens.length));\n return;\n }\n\n const previousState = this.#stateMachine.state;\n\n try {\n this.#stateMachine.transition(\"seek\");\n } catch (err) {\n this.#reportError(this.#toError(err));\n return;\n }\n\n this.#currentIndex = index;\n this.#hasPresentedCurrent = true;\n this.#remainingDelay = this.#msPerItem * this.#tokens[index].delayMultiplier;\n\n if (previousState !== \"PAUSED\") {\n this.#emitter.emit(\"stateChange\", {\n previous: previousState,\n current: \"PAUSED\",\n });\n }\n\n this.#emitter.emit(\"itemChange\", {\n item: this.#tokens[index],\n index,\n progress: this.progress,\n reason: \"seek\",\n });\n }\n\n /**\n * Advances to the next token. Only available from PAUSED state.\n */\n public next(): void {\n this.#assertNotDestroyed();\n\n if (this.#stateMachine.state !== \"PAUSED\") {\n this.#emitter.emit(\"error\", {\n error: new InvalidInputError(\"next() is only available in PAUSED state.\"),\n });\n return;\n }\n\n if (this.#currentIndex < this.#tokens.length - 1) {\n this.#currentIndex++;\n this.#hasPresentedCurrent = true;\n this.#remainingDelay = this.#msPerItem * this.#tokens[this.#currentIndex].delayMultiplier;\n this.#emitter.emit(\"itemChange\", {\n item: this.#tokens[this.#currentIndex],\n index: this.#currentIndex,\n progress: this.progress,\n reason: \"next\",\n });\n }\n }\n\n /**\n * Retreats to the previous token. Only available from PAUSED state.\n */\n public previous(): void {\n this.#assertNotDestroyed();\n\n if (this.#stateMachine.state !== \"PAUSED\") {\n this.#emitter.emit(\"error\", {\n error: new InvalidInputError(\"previous() is only available in PAUSED state.\"),\n });\n return;\n }\n\n if (this.#currentIndex > 0) {\n this.#currentIndex--;\n this.#hasPresentedCurrent = true;\n this.#remainingDelay = this.#msPerItem * this.#tokens[this.#currentIndex].delayMultiplier;\n this.#emitter.emit(\"itemChange\", {\n item: this.#tokens[this.#currentIndex],\n index: this.#currentIndex,\n progress: this.progress,\n reason: \"previous\",\n });\n }\n }\n\n /**\n * Resets engine to IDLE state, clearing all data.\n * Only valid from ERROR state.\n */\n public reset(): void {\n this.#assertNotDestroyed();\n\n const previousState = this.#stateMachine.state;\n\n try {\n this.#stateMachine.transition(\"reset\");\n } catch (err) {\n this.#reportError(this.#toError(err));\n return;\n }\n\n this.#scheduler.cancel();\n this.#tokens = [];\n this.#currentIndex = 0;\n this.#hasPresentedCurrent = false;\n this.#deadline = null;\n this.#remainingDelay = null;\n\n this.#emitter.emit(\"stateChange\", {\n previous: previousState,\n current: \"IDLE\",\n });\n }\n\n /**\n * Clears all timers, removes listeners, and disposes the engine.\n * Required for SPA component unmount cleanup.\n */\n public destroy(): void {\n if (this.#destroyed) {\n return; // idempotent\n }\n this.#scheduler.cancel();\n this.#deadline = null;\n this.#remainingDelay = null;\n this.#emitter.removeAllListeners();\n this.#destroyed = true;\n }\n\n // ────────── Speed Control ──────────\n\n /**\n * Updates playback speed via WPM (live, mid-playback).\n */\n public setSpeed(wpm: number): void {\n this.#assertNotDestroyed();\n validateWpm(wpm);\n this.#msPerItem = 60_000 / wpm;\n }\n\n /**\n * Directly sets ms-per-item interval (live, mid-playback).\n */\n public setMsPerItem(ms: number): void {\n this.#assertNotDestroyed();\n validateMsPerItem(ms);\n this.#msPerItem = ms;\n }\n\n // ────────── State & Snapshot Getters ──────────\n\n /** Current engine state. */\n public get state(): RSVPState {\n return this.#stateMachine.state;\n }\n\n /** Current WPM computed from msPerItem. */\n public get wpm(): number {\n return 60_000 / this.#msPerItem;\n }\n\n /** Current ms-per-item interval. */\n public get msPerItem(): number {\n return this.#msPerItem;\n }\n\n /** Current token index (0-based). */\n public get currentIndex(): number {\n return this.#currentIndex;\n }\n\n /** Current token/item being displayed, or null if none. */\n public get currentItem(): RSVPItem<T> | null {\n return this.#tokens[this.#currentIndex] ?? null;\n }\n\n /** Playback progress (0–1). */\n public get progress(): number {\n if (this.#tokens.length === 0) {\n return 0;\n }\n return this.#hasPresentedCurrent ? (this.#currentIndex + 1) / this.#tokens.length : 0;\n }\n\n /** Total number of tokens/items. */\n public get totalItems(): number {\n return this.#tokens.length;\n }\n\n // ────────── Event System ──────────\n\n /**\n * Subscribes to an engine event. Returns an unsubscribe function.\n */\n public on<K extends RSVPEventType>(\n event: K,\n callback: EventCallback<RSVPEventMap<T>[K]>,\n ): UnsubscribeFn {\n this.#assertNotDestroyed();\n return this.#emitter.on(event, callback);\n }\n\n // ────────── Snapshot ──────────\n\n /**\n * Returns a frozen, readonly snapshot of the full engine state.\n */\n public snapshot(): RSVPSnapshot<T> {\n this.#assertNotDestroyed();\n return Object.freeze({\n state: this.state,\n currentIndex: this.#currentIndex,\n currentItem: this.currentItem,\n progress: this.progress,\n totalItems: this.totalItems,\n wpm: this.wpm,\n msPerItem: this.#msPerItem,\n });\n }\n}\n"]}
@@ -0,0 +1,9 @@
1
+ /** Environment-neutral subset of global runtime capabilities used by the default time driver. */
2
+ export interface RuntimeGlobal {
3
+ readonly performance?: {
4
+ readonly now?: () => number;
5
+ };
6
+ readonly setTimeout?: (callback: () => void, ms: number) => unknown;
7
+ readonly clearTimeout?: (handle: unknown) => void;
8
+ }
9
+ //# sourceMappingURL=runtimeGlobal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtimeGlobal.d.ts","sourceRoot":"","sources":["../src/runtimeGlobal.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,WAAW,CAAC,EAAE;QACrB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;KAC7B,CAAC;IACF,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC;IACpE,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;CACnD"}
@@ -0,0 +1,50 @@
1
+ import type { SchedulerStrategy, TimeDriver } from "./types";
2
+ /**
3
+ * High-precision, drift-corrected task scheduler.
4
+ *
5
+ * Implements the {@link SchedulerStrategy} interface by tracking an ideal target timeline
6
+ * (`#expectedTime`) across consecutive task executions. Any latency or jitter introduced
7
+ * by the JavaScript event loop (timer drift) is calculated and subtracted from subsequent
8
+ * intervals, ensuring stable WPM presentation without cumulative time slippage.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const scheduler = new DriftCorrectedScheduler();
13
+ *
14
+ * // Schedule a task after 200ms
15
+ * scheduler.schedule(() => {
16
+ * console.log("Tick!");
17
+ * }, 200);
18
+ *
19
+ * // Cancel any pending task
20
+ * scheduler.cancel();
21
+ * ```
22
+ */
23
+ export declare class DriftCorrectedScheduler implements SchedulerStrategy {
24
+ #private;
25
+ /**
26
+ * Creates a new instance of `DriftCorrectedScheduler`.
27
+ *
28
+ * @param timeDriver - The underlying time and timer provider. Defaults to {@link SystemTimeDriver}.
29
+ */
30
+ constructor(timeDriver?: TimeDriver);
31
+ /**
32
+ * Schedules a task to execute after the given delay, compensating for accumulated drift.
33
+ *
34
+ * If a previous task is currently pending, it is automatically cancelled before scheduling
35
+ * the new one. When part of an active session, the actual timeout duration is adjusted
36
+ * by subtracting any detected execution delay (`drift = now - expectedTime`).
37
+ *
38
+ * @param task - The callback function to execute.
39
+ * @param delayMs - The target delay in milliseconds.
40
+ */
41
+ schedule(task: () => void, delayMs: number): void;
42
+ /**
43
+ * Cancels any pending scheduled task and resets the session timeline.
44
+ *
45
+ * Calling this method halts the drift compensation timeline so that the next
46
+ * call to {@link schedule} starts a fresh session without previous drift history.
47
+ */
48
+ cancel(): void;
49
+ }
50
+ //# sourceMappingURL=drift-corrected-scheduler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drift-corrected-scheduler.d.ts","sourceRoot":"","sources":["../../src/scheduler/drift-corrected-scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAI7D;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,uBAAwB,YAAW,iBAAiB;;IAK/D;;;;OAIG;gBACS,UAAU,GAAE,UAAmC;IAI3D;;;;;;;;;OASG;IACI,QAAQ,CAAC,IAAI,EAAE,MAAM,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IA+BxD;;;;;OAKG;IACI,MAAM,IAAI,IAAI;CAWtB"}
@@ -0,0 +1,11 @@
1
+ import type { TimeDriver } from "./types";
2
+ /**
3
+ * Default implementation of `TimeDriver` utilizing platform globals
4
+ * (`performance.now` / `Date.now` and `setTimeout` / `clearTimeout`).
5
+ */
6
+ export declare class SystemTimeDriver implements TimeDriver {
7
+ now(): number;
8
+ setTimeout(callback: () => void, ms: number): unknown;
9
+ clearTimeout(handle: unknown): void;
10
+ }
11
+ //# sourceMappingURL=system-time-driver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"system-time-driver.d.ts","sourceRoot":"","sources":["../../src/scheduler/system-time-driver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAG1C;;;GAGG;AACH,qBAAa,gBAAiB,YAAW,UAAU;IAC1C,GAAG,IAAI,MAAM;IAQb,UAAU,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO;IAQrD,YAAY,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI;CAO3C"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Low-level time and timer abstraction for schedulers.
3
+ * Allows injecting custom time sources and timers for testing or non-standard environments.
4
+ */
5
+ export interface TimeDriver {
6
+ /** Returns the current timestamp in milliseconds (high-resolution preferred). */
7
+ now(): number;
8
+ /** Schedules a callback after `ms` milliseconds. */
9
+ setTimeout(callback: () => void, ms: number): unknown;
10
+ /** Cancels a scheduled timeout. */
11
+ clearTimeout(handle: unknown): void;
12
+ }
13
+ /**
14
+ * Injectable strategy interface for timing and task scheduling.
15
+ * Implementors control how tasks are scheduled with delay.
16
+ */
17
+ export interface SchedulerStrategy {
18
+ /** Schedules a task to run after `delayMs`, with drift correction for continuous sessions. */
19
+ schedule(task: () => void, delayMs: number): void;
20
+ /** Cancels any currently pending scheduled task and resets the session timeline. */
21
+ cancel(): void;
22
+ }
23
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/scheduler/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,iFAAiF;IACjF,GAAG,IAAI,MAAM,CAAC;IACd,oDAAoD;IACpD,UAAU,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;IACtD,mCAAmC;IACnC,YAAY,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;CACrC;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,8FAA8F;IAC9F,QAAQ,CAAC,IAAI,EAAE,MAAM,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAClD,oFAAoF;IACpF,MAAM,IAAI,IAAI,CAAC;CAChB"}
@@ -0,0 +1,7 @@
1
+ import type { RSVPState, StateMachineAction } from "./types";
2
+ export declare class StateMachine {
3
+ #private;
4
+ get state(): RSVPState;
5
+ transition(action: StateMachineAction): RSVPState;
6
+ }
7
+ //# sourceMappingURL=state-machine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-machine.d.ts","sourceRoot":"","sources":["../../src/state/state-machine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAI7D,qBAAa,YAAY;;IAuCvB,IAAW,KAAK,IAAI,SAAS,CAE5B;IAEM,UAAU,CAAC,MAAM,EAAE,kBAAkB,GAAG,SAAS;CAWzD"}
@@ -0,0 +1,5 @@
1
+ /** All possible states of the RSVP engine lifecycle. */
2
+ export type RSVPState = "IDLE" | "PLAYING" | "PAUSED" | "STOPPED" | "COMPLETED" | "ERROR";
3
+ /** Commands accepted by the state machine transition table. */
4
+ export type StateMachineAction = "play" | "pause" | "stop" | "seek" | "complete" | "error" | "reset" | "load";
5
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/state/types.ts"],"names":[],"mappings":"AAAA,wDAAwD;AACxD,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,CAAC;AAE1F,+DAA+D;AAC/D,MAAM,MAAM,kBAAkB,GAC5B,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC"}
@@ -0,0 +1,28 @@
1
+ import type { OVPStrategy, Token, TokenizerStrategy } from "./types";
2
+ /**
3
+ * Options for configuring `DefaultTokenizer`.
4
+ */
5
+ export interface DefaultTokenizerOptions {
6
+ /** Multiplier for sentence-ending punctuation (`.`, `!`, `?`). Defaults to `2.0`. */
7
+ readonly sentenceDelay?: number;
8
+ /** Multiplier for clause-ending punctuation (`,`, `;`, `:`). Defaults to `1.5`. */
9
+ readonly clauseDelay?: number;
10
+ /** Multiplier for dashes (`—`, `–`). Defaults to `1.3`. */
11
+ readonly dashDelay?: number;
12
+ /** Whether array elements and nested inputs should be recursively tokenized. Defaults to `false`. */
13
+ readonly nestedTokenize?: boolean;
14
+ /** Strategy used to calculate viewing positions for string tokens. */
15
+ readonly ovpStrategy?: OVPStrategy;
16
+ }
17
+ /**
18
+ * Default configurable tokenizer for text, items, and array inputs.
19
+ * Splits text on whitespace and assigns delay multipliers based on punctuation.
20
+ * Wraps custom generic items or arrays into presentable tokens.
21
+ * @typeParam T - The type of items being presented (defaults to `string`).
22
+ */
23
+ export declare class DefaultTokenizer<T> implements TokenizerStrategy<T> {
24
+ #private;
25
+ constructor(options?: DefaultTokenizerOptions);
26
+ tokenize(input: T | T[]): Token<T>[];
27
+ }
28
+ //# sourceMappingURL=default-tokenizer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"default-tokenizer.d.ts","sourceRoot":"","sources":["../../src/tokenizer/default-tokenizer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAMrE;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,qFAAqF;IACrF,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,mFAAmF;IACnF,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,2DAA2D;IAC3D,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,qGAAqG;IACrG,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAClC,sEAAsE;IACtE,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC;CACpC;AAED;;;;;GAKG;AACH,qBAAa,gBAAgB,CAAC,CAAC,CAAE,YAAW,iBAAiB,CAAC,CAAC,CAAC;;gBAOlD,OAAO,GAAE,uBAA4B;IAS1C,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE;CA8G5C"}
@@ -0,0 +1,12 @@
1
+ import type { OVPStrategy } from "./types";
2
+ /**
3
+ * Default grapheme-aware Optimal Viewing Position strategy.
4
+ */
5
+ export declare class DefaultOVPStrategy implements OVPStrategy {
6
+ /**
7
+ * Calculates the Optimal Viewing Position for text.
8
+ * @returns The zero-based UTF-16 offset of the OVP grapheme.
9
+ */
10
+ calculate(text: string): number;
11
+ }
12
+ //# sourceMappingURL=ovp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ovp.d.ts","sourceRoot":"","sources":["../../src/tokenizer/ovp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C;;GAEG;AACH,qBAAa,kBAAmB,YAAW,WAAW;IACpD;;;OAGG;IACI,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAevC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * A processed token ready for presentation.
3
+ */
4
+ export interface Token<T> {
5
+ readonly value: T;
6
+ readonly ovpIndex: number;
7
+ readonly delayMultiplier: number;
8
+ }
9
+ /**
10
+ * Strategy interface for pluggable text and item tokenization.
11
+ */
12
+ export interface TokenizerStrategy<T> {
13
+ /**
14
+ * Tokenizes raw input into an array of presentable tokens.
15
+ */
16
+ tokenize(input: T | T[]): Token<T>[];
17
+ }
18
+ /**
19
+ * Strategy for selecting the Optimal Viewing Position within text.
20
+ */
21
+ export interface OVPStrategy {
22
+ /**
23
+ * Returns the zero-based UTF-16 offset of the preferred viewing position.
24
+ */
25
+ calculate(text: string): number;
26
+ }
27
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/tokenizer/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,KAAK,CAAC,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;IAClB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB,CAAC,CAAC;IAClC;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;CACtC;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;CACjC"}
package/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "@rsvp-engine/core",
3
+ "version": "0.1.0",
4
+ "description": "Headless, zero-dependency RSVP engine with drift-corrected scheduling and pluggable tokenization",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/altunmustafa/rsvp-engine.git",
9
+ "directory": "packages/core"
10
+ },
11
+ "engines": {
12
+ "node": ">=18"
13
+ },
14
+ "publishConfig": {
15
+ "access": "public",
16
+ "provenance": true
17
+ },
18
+ "type": "module",
19
+ "main": "./dist/index.cjs",
20
+ "module": "./dist/index.js",
21
+ "types": "./dist/index.d.ts",
22
+ "exports": {
23
+ ".": {
24
+ "import": {
25
+ "types": "./dist/index.d.ts",
26
+ "default": "./dist/index.js"
27
+ },
28
+ "require": {
29
+ "types": "./dist/index.d.cts",
30
+ "default": "./dist/index.cjs"
31
+ }
32
+ }
33
+ },
34
+ "files": [
35
+ "dist",
36
+ "scripts",
37
+ "README.md",
38
+ "CONTRIBUTING.md",
39
+ "LICENSE",
40
+ "CHANGELOG.md"
41
+ ],
42
+ "dependencies": {},
43
+ "devDependencies": {
44
+ "typescript": "^6.0.3",
45
+ "tsup": "^8.5.1",
46
+ "vitest": "^4.1.10",
47
+ "eslint": "^10.8.1",
48
+ "prettier": "^3.9.6",
49
+ "typescript-eslint": "^8.67.0",
50
+ "@types/node": "^26.2.0",
51
+ "@vitest/coverage-v8": "^4.1.10"
52
+ },
53
+ "sideEffects": false,
54
+ "keywords": [
55
+ "rsvp",
56
+ "speed-reading",
57
+ "rapid-serial-visual-presentation",
58
+ "orp",
59
+ "spritz",
60
+ "reader-engine",
61
+ "text-pacing",
62
+ "headless",
63
+ "typescript"
64
+ ],
65
+ "scripts": {
66
+ "build": "tsup && tsc --emitDeclarationOnly --declaration --outDir dist && cp dist/index.d.ts dist/index.d.cts",
67
+ "test": "vitest run",
68
+ "test:coverage": "vitest run --coverage",
69
+ "lint": "eslint src/",
70
+ "format": "prettier . --write --ignore-path ../../.gitignore",
71
+ "format:check": "prettier . --check --ignore-path ../../.gitignore",
72
+ "typecheck": "tsc --noEmit",
73
+ "check:architecture": "node scripts/check-architecture.js",
74
+ "check:size": "node scripts/check-bundle-size.js",
75
+ "check:package": "node scripts/check-package.js"
76
+ }
77
+ }
@@ -0,0 +1,24 @@
1
+ import { readFileSync, readdirSync } from "node:fs";
2
+
3
+ const sourceRoot = new URL("../src/", import.meta.url);
4
+ const forbidden = /\b(?:window|document|HTMLElement)\b/;
5
+
6
+ function sourceFiles(directory) {
7
+ return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
8
+ const url = new URL(entry.name + (entry.isDirectory() ? "/" : ""), directory);
9
+ return entry.isDirectory() ? sourceFiles(url) : entry.name.endsWith(".ts") ? [url] : [];
10
+ });
11
+ }
12
+
13
+ for (const file of sourceFiles(sourceRoot)) {
14
+ if (forbidden.test(readFileSync(file, "utf8"))) {
15
+ throw new Error(`Forbidden DOM API reference in ${file.pathname}.`);
16
+ }
17
+ }
18
+
19
+ const manifest = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
20
+ if (Object.keys(manifest.dependencies ?? {}).length > 0) {
21
+ throw new Error("Core must have zero production dependencies.");
22
+ }
23
+
24
+ console.log("Architecture constraints passed: zero DOM references and zero runtime dependencies.");
@@ -0,0 +1,12 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { gzipSync } from "node:zlib";
3
+
4
+ const bundle = readFileSync(new URL("../dist/index.js", import.meta.url));
5
+ const bytes = gzipSync(bundle).byteLength;
6
+ const budget = 5 * 1024;
7
+
8
+ if (bytes >= budget) {
9
+ throw new Error(`Gzipped ESM bundle is ${bytes} bytes; budget is strictly below ${budget}.`);
10
+ }
11
+
12
+ console.log(`Gzipped ESM bundle: ${bytes} bytes (< ${budget}).`);
@@ -0,0 +1,24 @@
1
+ import { existsSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+
4
+ const esm = await import("@rsvp-engine/core");
5
+ const require = createRequire(import.meta.url);
6
+ const cjs = require("@rsvp-engine/core");
7
+
8
+ if (typeof esm.RSVPEngine !== "function" || typeof cjs.RSVPEngine !== "function") {
9
+ throw new Error("Published ESM/CJS entry points do not expose RSVPEngine.");
10
+ }
11
+
12
+ for (const file of [
13
+ "README.md",
14
+ "LICENSE",
15
+ "CHANGELOG.md",
16
+ "dist/index.d.ts",
17
+ "dist/index.d.cts",
18
+ ]) {
19
+ if (!existsSync(new URL(`../${file}`, import.meta.url))) {
20
+ throw new Error(`Required package file is missing: ${file}.`);
21
+ }
22
+ }
23
+
24
+ console.log("ESM, CJS, declarations, README, license, and changelog are present.");