@pickleball/expo-sdk 0.1.1 → 0.2.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 (74) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +69 -0
  3. package/dist/advanced-BXDrDl1h.d.ts +316 -0
  4. package/dist/advanced-BdAa2Erv.d.cts +316 -0
  5. package/dist/advanced.cjs +35 -0
  6. package/dist/advanced.d.cts +3 -0
  7. package/dist/advanced.d.ts +3 -0
  8. package/dist/advanced.js +35 -0
  9. package/dist/chunk-2JHULJZ7.js +265 -0
  10. package/dist/chunk-6JOMESNT.js +9 -0
  11. package/dist/chunk-7OX2DSKI.cjs +33 -0
  12. package/dist/chunk-DHDX3ZOO.js +800 -0
  13. package/dist/chunk-FODE2W2H.js +1953 -0
  14. package/dist/chunk-KFQGP6VL.js +33 -0
  15. package/dist/chunk-LCWGNGEN.cjs +265 -0
  16. package/dist/chunk-TBPR2QND.cjs +1953 -0
  17. package/dist/chunk-TRLASNPQ.cjs +9 -0
  18. package/dist/chunk-YGSQ25UA.cjs +800 -0
  19. package/dist/device-agent/index.cjs +498 -0
  20. package/dist/device-agent/index.d.cts +157 -0
  21. package/dist/device-agent/index.d.ts +157 -0
  22. package/dist/device-agent/index.js +498 -0
  23. package/dist/engine-CCSSqPbZ.d.cts +516 -0
  24. package/dist/engine-CCSSqPbZ.d.ts +516 -0
  25. package/dist/index.cjs +339 -5569
  26. package/dist/index.d.cts +22 -1406
  27. package/dist/index.d.ts +22 -1406
  28. package/dist/index.js +269 -5401
  29. package/dist/match/index.cjs +2456 -0
  30. package/dist/match/index.d.cts +253 -0
  31. package/dist/match/index.d.ts +253 -0
  32. package/dist/match/index.js +2456 -0
  33. package/dist/match/remote/index.cjs +21 -0
  34. package/dist/match/remote/index.d.cts +67 -0
  35. package/dist/match/remote/index.d.ts +67 -0
  36. package/dist/match/remote/index.js +21 -0
  37. package/dist/match/voice/index.cjs +23 -0
  38. package/dist/match/voice/index.d.cts +114 -0
  39. package/dist/match/voice/index.d.ts +114 -0
  40. package/dist/match/voice/index.js +23 -0
  41. package/dist/plugin.cjs +53 -70
  42. package/dist/plugin.d.cts +13 -0
  43. package/dist/plugin.d.ts +13 -0
  44. package/dist/plugin.js +20 -15
  45. package/dist/types-CrmcX408.d.cts +176 -0
  46. package/dist/types-CrmcX408.d.ts +176 -0
  47. package/dist/types-DiKWjwvN.d.cts +96 -0
  48. package/dist/types-DiKWjwvN.d.ts +96 -0
  49. package/dist/version.cjs +4 -32
  50. package/dist/version.js +2 -1
  51. package/package.json +68 -4
  52. package/src/advanced.ts +47 -0
  53. package/src/device-agent/agent.ts +443 -0
  54. package/src/device-agent/component.tsx +49 -0
  55. package/src/device-agent/convex-control-channel.ts +82 -0
  56. package/src/device-agent/http-transport.ts +143 -0
  57. package/src/device-agent/index.ts +4 -0
  58. package/src/engine.ts +21 -5
  59. package/src/errors.ts +16 -0
  60. package/src/http-session-provider.ts +19 -13
  61. package/src/index.ts +38 -14
  62. package/src/livekit-room-adapter.ts +39 -2
  63. package/src/livestream-screen.tsx +26 -4
  64. package/src/match/index.ts +29 -0
  65. package/src/match/provider.tsx +4 -2
  66. package/src/match/remote/hid-gesture.ts +10 -2
  67. package/src/match/remote/types.ts +6 -4
  68. package/src/match/voice/scheduler.ts +6 -1
  69. package/src/match/voice/service.ts +16 -4
  70. package/src/native-runtime.ts +54 -4
  71. package/src/plugin.ts +35 -14
  72. package/src/provider.tsx +35 -4
  73. package/src/rtmp-room-adapter.ts +196 -0
  74. package/dist/chunk-VHLUJQG5.js +0 -17
@@ -0,0 +1,253 @@
1
+ import { M as MatchSnapshot, a as MatchEvent, C as CourtSide, l as MatchInputSource, R as RallyAwardedEvent, m as CourtSwitchConfirmedEvent, n as MatchCorrection, o as MatchCorrectedEvent, p as MatchCreateInput, q as MatchCreation, r as MatchEndedEvent, s as MatchEventUndoneEvent, c as VoiceRouteState, V as VoiceAudioMode, e as MatchVoiceService, d as VoiceTtsOptions } from '../types-CrmcX408.cjs';
2
+ export { G as GameSnapshot, t as MatchCreatedEvent, u as MatchFormat, v as MatchPlayer, w as MatchScoringMode, x as MatchTeam, y as MatchTeamId, P as PendingCourtSwitch, S as ServeState, T as TeamScore } from '../types-CrmcX408.cjs';
3
+ import { e as RemoteBinding, o as RemoteState, n as RemoteService, i as RemoteInputMode, d as RemoteActionMapping, a as RemoteHidGestureMapping } from '../types-DiKWjwvN.cjs';
4
+ export { c as RemoteHidVolumeEvent, j as RemoteMappedAction, k as RemoteNativeState, m as RemoteScanAdvertisement } from '../types-DiKWjwvN.cjs';
5
+ import React, { ReactNode } from 'react';
6
+
7
+ declare function createMatch(input: MatchCreateInput, source?: MatchInputSource): MatchCreation;
8
+ declare function applyMatchEvent(snapshot: MatchSnapshot, event: MatchEvent): MatchSnapshot;
9
+ declare function awardRally(snapshot: MatchSnapshot, displaySide: CourtSide, source?: MatchInputSource): RallyAwardedEvent | null;
10
+ declare function confirmCourtSwitch(snapshot: MatchSnapshot, source?: MatchInputSource): CourtSwitchConfirmedEvent;
11
+ declare function correctMatch(snapshot: MatchSnapshot, correction: MatchCorrection, source?: MatchInputSource): MatchCorrectedEvent;
12
+ declare function endMatch(snapshot: MatchSnapshot, reason: string, source?: MatchInputSource): MatchEndedEvent;
13
+
14
+ declare function replayMatchEvents(events: readonly unknown[]): MatchSnapshot;
15
+ declare function undoLastMatchEvent(events: readonly MatchEvent[], source?: MatchInputSource): MatchEventUndoneEvent | null;
16
+
17
+ declare const MATCH_PERSISTENCE_SCHEMA_VERSION: 1;
18
+ interface StoredMatchRow {
19
+ readonly matchId: string;
20
+ readonly schemaVersion: number;
21
+ readonly snapshotJson: string;
22
+ readonly snapshotSequence: number;
23
+ readonly status: MatchSnapshot["status"];
24
+ readonly updatedAt: number;
25
+ }
26
+ interface StoredMatchEventRow {
27
+ readonly matchId: string;
28
+ readonly sequence: number;
29
+ readonly eventId: string;
30
+ readonly schemaVersion: number;
31
+ readonly eventJson: string;
32
+ readonly createdAt: number;
33
+ }
34
+ interface MatchPersistenceTransaction {
35
+ getMatch(matchId: string): Promise<StoredMatchRow | null>;
36
+ getEvents(matchId: string): Promise<readonly StoredMatchEventRow[]>;
37
+ insertMatch(row: StoredMatchRow): Promise<void>;
38
+ insertEvent(row: StoredMatchEventRow): Promise<void>;
39
+ updateSnapshot(matchId: string, expectedSequence: number, row: Pick<StoredMatchRow, "schemaVersion" | "snapshotJson" | "snapshotSequence" | "status" | "updatedAt">): Promise<boolean>;
40
+ setActiveMatch(matchId: string | null): Promise<void>;
41
+ clearActiveMatch(matchId: string): Promise<void>;
42
+ }
43
+ interface MatchPersistenceDriver {
44
+ migrate(): Promise<void>;
45
+ transaction<T>(work: (transaction: MatchPersistenceTransaction) => Promise<T>): Promise<T>;
46
+ readMatch(matchId: string): Promise<{
47
+ readonly match: StoredMatchRow | null;
48
+ readonly events: readonly StoredMatchEventRow[];
49
+ }>;
50
+ getActiveMatchId(): Promise<string | null>;
51
+ readActiveMatch(): Promise<{
52
+ readonly activeMatchId: string | null;
53
+ readonly match: StoredMatchRow | null;
54
+ readonly events: readonly StoredMatchEventRow[];
55
+ }>;
56
+ readMatchList(): Promise<{
57
+ readonly matches: readonly StoredMatchRow[];
58
+ readonly activeMatchId: string | null;
59
+ }>;
60
+ getRemoteBinding?(): Promise<RemoteBinding | null>;
61
+ setRemoteBinding?(binding: RemoteBinding | null): Promise<void>;
62
+ }
63
+ interface PersistedMatch {
64
+ readonly snapshot: MatchSnapshot;
65
+ readonly events: readonly MatchEvent[];
66
+ }
67
+ interface MatchSummary {
68
+ readonly id: string;
69
+ readonly sequence: number;
70
+ readonly status: MatchSnapshot["status"];
71
+ readonly updatedAt: number;
72
+ readonly isActive: boolean;
73
+ }
74
+ type MatchPersistenceErrorCode = "not_found" | "already_exists" | "conflict" | "corrupt_data" | "unsupported_schema" | "sequence_mismatch" | "snapshot_mismatch" | "storage_failure";
75
+ declare class MatchPersistenceError extends Error {
76
+ readonly code: MatchPersistenceErrorCode;
77
+ readonly name = "MatchPersistenceError";
78
+ readonly recoverable = true;
79
+ readonly retryable: boolean;
80
+ constructor(code: MatchPersistenceErrorCode, message: string, options?: {
81
+ cause?: unknown;
82
+ });
83
+ }
84
+
85
+ declare const MATCH_SQL_SCHEMA_VERSION: 2;
86
+ declare const MATCH_SQL_MIGRATIONS: readonly ["CREATE TABLE IF NOT EXISTS match_aggregates (\n match_id TEXT PRIMARY KEY NOT NULL,\n schema_version INTEGER NOT NULL CHECK (schema_version = 1),\n snapshot_json TEXT NOT NULL,\n snapshot_sequence INTEGER NOT NULL CHECK (snapshot_sequence >= 0),\n status TEXT NOT NULL CHECK (status IN ('inProgress', 'completed', 'ended')),\n updated_at INTEGER NOT NULL\n )", "CREATE TABLE IF NOT EXISTS match_events (\n match_id TEXT NOT NULL,\n sequence INTEGER NOT NULL CHECK (sequence >= 0),\n event_id TEXT NOT NULL UNIQUE,\n schema_version INTEGER NOT NULL CHECK (schema_version = 1),\n event_json TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n PRIMARY KEY (match_id, sequence),\n FOREIGN KEY (match_id) REFERENCES match_aggregates(match_id) ON DELETE RESTRICT\n )", "CREATE TABLE IF NOT EXISTS match_active_pointer (\n slot INTEGER PRIMARY KEY NOT NULL CHECK (slot = 1),\n match_id TEXT NOT NULL UNIQUE,\n FOREIGN KEY (match_id) REFERENCES match_aggregates(match_id) ON DELETE RESTRICT\n )", "CREATE INDEX IF NOT EXISTS match_aggregates_recent\n ON match_aggregates(updated_at DESC)", "CREATE TRIGGER IF NOT EXISTS match_events_no_update\n BEFORE UPDATE ON match_events\n BEGIN SELECT RAISE(ABORT, 'match events are immutable'); END", "CREATE TRIGGER IF NOT EXISTS match_events_no_delete\n BEFORE DELETE ON match_events\n BEGIN SELECT RAISE(ABORT, 'match events are immutable'); END", "CREATE TABLE IF NOT EXISTS remote_binding (\n slot INTEGER PRIMARY KEY NOT NULL CHECK (slot = 1),\n device_id TEXT NOT NULL CHECK (length(trim(device_id)) > 0),\n label TEXT\n )"];
87
+
88
+ interface MemoryState {
89
+ migrationVersion: number;
90
+ remoteBinding: RemoteBinding | null;
91
+ activeMatchId: string | null;
92
+ matches: StoredMatchRow[];
93
+ events: StoredMatchEventRow[];
94
+ nextOrdinal: number;
95
+ ordinals: Record<string, number>;
96
+ }
97
+ declare class MemoryMatchPersistenceDriver implements MatchPersistenceDriver {
98
+ private state;
99
+ private eventInsertFailure;
100
+ private snapshotUpdateFailure;
101
+ private transactionTail;
102
+ migrate(): Promise<void>;
103
+ getRemoteBinding(): Promise<{
104
+ deviceId: string;
105
+ label?: string;
106
+ } | null>;
107
+ setRemoteBinding(binding: RemoteBinding | null): Promise<void>;
108
+ getMigrationVersion(): number;
109
+ transaction<T>(work: (transaction: MatchPersistenceTransaction) => Promise<T>): Promise<T>;
110
+ readMatch(matchId: string): Promise<{
111
+ match: StoredMatchRow | null;
112
+ events: StoredMatchEventRow[];
113
+ }>;
114
+ getActiveMatchId(): Promise<string | null>;
115
+ readActiveMatch(): Promise<{
116
+ activeMatchId: null;
117
+ match: null;
118
+ events: never[];
119
+ } | {
120
+ activeMatchId: string;
121
+ match: StoredMatchRow | null;
122
+ events: StoredMatchEventRow[];
123
+ }>;
124
+ readMatchList(): Promise<{
125
+ matches: StoredMatchRow[];
126
+ activeMatchId: string | null;
127
+ }>;
128
+ inspect(): MemoryState;
129
+ failNextSnapshotUpdate(error: Error): void;
130
+ failNextEventInsert(error: Error): void;
131
+ corruptSnapshotJson(matchId: string, snapshotJson: string): void;
132
+ corruptEventJson(matchId: string, sequence: number, eventJson: string): void;
133
+ corruptEventSchema(matchId: string, sequence: number, schemaVersion: number): void;
134
+ corruptEventSequence(matchId: string, sequence: number): void;
135
+ duplicateEventRow(matchId: string, sequence: number): void;
136
+ removeEventRow(matchId: string, sequence: number): void;
137
+ private mutateMatch;
138
+ private mutateEvent;
139
+ }
140
+
141
+ interface SqlRunResult {
142
+ readonly changes: number;
143
+ readonly lastInsertRowId: number;
144
+ }
145
+ interface SqlExecutor {
146
+ runAsync(sql: string, ...params: unknown[]): Promise<SqlRunResult>;
147
+ getFirstAsync<T>(sql: string, ...params: unknown[]): Promise<T | null>;
148
+ getAllAsync<T>(sql: string, ...params: unknown[]): Promise<T[]>;
149
+ }
150
+ interface ExpoSqliteDatabaseLike extends SqlExecutor {
151
+ execAsync(sql: string): Promise<void>;
152
+ }
153
+ /**
154
+ * Serializes adapters sharing this exact connection object. Separate
155
+ * connections to the same file rely on SQLite locking plus busy_timeout.
156
+ */
157
+ declare function createExpoSqliteMatchDriver(database: ExpoSqliteDatabaseLike): MatchPersistenceDriver;
158
+
159
+ interface MatchEventStoreOptions {
160
+ readonly now?: () => number;
161
+ }
162
+ declare class MatchEventStore {
163
+ private readonly driver;
164
+ private initialized;
165
+ private readonly now;
166
+ constructor(driver: MatchPersistenceDriver, options?: MatchEventStoreOptions);
167
+ initialize(): Promise<void>;
168
+ getRemoteBinding(): Promise<RemoteBinding | null>;
169
+ setRemoteBinding(binding: RemoteBinding | null): Promise<void>;
170
+ createMatch(input: MatchCreateInput, source?: MatchInputSource): Promise<PersistedMatch>;
171
+ appendEvent(currentSnapshot: MatchSnapshot, event: MatchEvent): Promise<PersistedMatch>;
172
+ resumeActiveMatch(): Promise<PersistedMatch | null>;
173
+ resumeMatch(matchId: string): Promise<PersistedMatch | null>;
174
+ listMatches(): Promise<readonly MatchSummary[]>;
175
+ archiveMatch(matchId: string): Promise<void>;
176
+ }
177
+
178
+ declare function createDefaultMatchEventStore(_databaseName?: string): Promise<MatchEventStore>;
179
+
180
+ interface PickleballMatchStore {
181
+ initialize(): Promise<void>;
182
+ createMatch(input: MatchCreateInput, source?: MatchInputSource): Promise<PersistedMatch>;
183
+ appendEvent(currentSnapshot: MatchSnapshot, event: MatchEvent): Promise<PersistedMatch>;
184
+ resumeMatch(matchId: string): Promise<PersistedMatch | null>;
185
+ resumeActiveMatch(): Promise<PersistedMatch | null>;
186
+ listMatches(): Promise<readonly MatchSummary[]>;
187
+ archiveMatch(matchId: string): Promise<void>;
188
+ getRemoteBinding?(): Promise<RemoteBinding | null>;
189
+ setRemoteBinding?(binding: RemoteBinding | null): Promise<void>;
190
+ }
191
+ declare class NoActiveMatchError extends Error {
192
+ readonly name = "NoActiveMatchError";
193
+ readonly code = "NO_ACTIVE_MATCH";
194
+ constructor();
195
+ }
196
+ /**
197
+ * State-changing actions reject with NoActiveMatchError when no match is loaded.
198
+ * Commands the engine deems ineligible are ignored and resolve the current snapshot.
199
+ */
200
+ interface PickleballMatchContextValue {
201
+ readonly snapshot: MatchSnapshot | null;
202
+ readonly events: readonly MatchEvent[];
203
+ readonly loading: boolean;
204
+ readonly error: Error | null;
205
+ readonly voiceError: Error | null;
206
+ readonly audioState: VoiceRouteState;
207
+ readonly remoteState: RemoteState;
208
+ createMatch(input: MatchCreateInput, source?: MatchInputSource): Promise<MatchSnapshot>;
209
+ resumeMatch(matchId?: string): Promise<MatchSnapshot | null>;
210
+ awardRallyToSide(side: CourtSide, source?: MatchInputSource): Promise<MatchSnapshot>;
211
+ undo(source?: MatchInputSource): Promise<MatchSnapshot>;
212
+ correctState(correction: MatchCorrection, source?: MatchInputSource): Promise<MatchSnapshot>;
213
+ confirmCourtSwitch(source?: MatchInputSource): Promise<MatchSnapshot>;
214
+ endMatch(reason: string, source?: MatchInputSource): Promise<MatchSnapshot>;
215
+ testSpeaker(): Promise<void>;
216
+ replayAnnouncement(): Promise<void>;
217
+ setVoiceAudioMode(mode: VoiceAudioMode): Promise<void>;
218
+ bindRemote(binding: RemoteBinding): Promise<void>;
219
+ unbindRemote(): Promise<void>;
220
+ startRemoteScan(): Promise<void>;
221
+ stopRemoteScan(): Promise<void>;
222
+ startHidCapture(): Promise<void>;
223
+ stopHidCapture(): Promise<void>;
224
+ }
225
+ interface PickleballMatchProviderProps {
226
+ readonly children?: ReactNode;
227
+ /** Fixed for this provider instance. Remount to use a different local store. */
228
+ readonly store?: PickleballMatchStore;
229
+ /** Fixed for this provider instance. Remount to use a different factory. */
230
+ readonly storeFactory?: () => Promise<PickleballMatchStore>;
231
+ /** Resumes the active local match on mount. Disable for hosts that resume explicitly. */
232
+ readonly autoResume?: boolean;
233
+ readonly onError?: (error: Error) => void | Promise<void>;
234
+ /** Fixed for this provider instance. Primarily useful for custom storage and tests. */
235
+ readonly voiceService?: MatchVoiceService;
236
+ /** Initial audio session mode. Defaults to live so mounting never disables capture. */
237
+ readonly voiceAudioMode?: VoiceAudioMode;
238
+ /** Cloud TTS endpoint for announcements. Without it only bundled WAVs play. Fixed for this provider instance. */
239
+ readonly voiceTts?: VoiceTtsOptions;
240
+ /** Fixed for this provider instance. Primarily useful for tests and custom native bridges. */
241
+ readonly remoteService?: RemoteService;
242
+ /** Foreground silence before a bound remote becomes outOfRange. */
243
+ readonly remoteOutOfRangeTimeoutMs?: number;
244
+ readonly remoteInputMode?: RemoteInputMode;
245
+ readonly remoteActionMapping?: Partial<RemoteActionMapping>;
246
+ /** Optional single/double/hold recognizer for one calibrated HID volume button. */
247
+ readonly hidGestureMapping?: RemoteHidGestureMapping;
248
+ readonly consumeHidVolumeKeys?: boolean;
249
+ }
250
+ declare function PickleballMatchProvider({ children, store, storeFactory, autoResume, onError, voiceService, voiceAudioMode, voiceTts, remoteService, remoteOutOfRangeTimeoutMs, remoteInputMode, remoteActionMapping, hidGestureMapping, consumeHidVolumeKeys, }: PickleballMatchProviderProps): React.JSX.Element;
251
+ declare function usePickleballMatch(): PickleballMatchContextValue;
252
+
253
+ export { CourtSide, CourtSwitchConfirmedEvent, type ExpoSqliteDatabaseLike, MATCH_PERSISTENCE_SCHEMA_VERSION, MATCH_SQL_MIGRATIONS, MATCH_SQL_SCHEMA_VERSION, MatchCorrectedEvent, MatchCorrection, MatchCreateInput, MatchCreation, MatchEndedEvent, MatchEvent, MatchEventStore, MatchEventUndoneEvent, MatchInputSource, type MatchPersistenceDriver, MatchPersistenceError, type MatchPersistenceErrorCode, type MatchPersistenceTransaction, MatchSnapshot, type MatchSummary, MatchVoiceService, MemoryMatchPersistenceDriver, NoActiveMatchError, type PersistedMatch, type PickleballMatchContextValue, PickleballMatchProvider, type PickleballMatchProviderProps, type PickleballMatchStore, RallyAwardedEvent, RemoteActionMapping, RemoteBinding, RemoteHidGestureMapping, RemoteInputMode, RemoteService, RemoteState, type StoredMatchEventRow, type StoredMatchRow, VoiceAudioMode, VoiceRouteState, VoiceTtsOptions, applyMatchEvent, awardRally, confirmCourtSwitch, correctMatch, createDefaultMatchEventStore, createExpoSqliteMatchDriver, createMatch, endMatch, replayMatchEvents, undoLastMatchEvent, usePickleballMatch };
@@ -0,0 +1,253 @@
1
+ import { M as MatchSnapshot, a as MatchEvent, C as CourtSide, l as MatchInputSource, R as RallyAwardedEvent, m as CourtSwitchConfirmedEvent, n as MatchCorrection, o as MatchCorrectedEvent, p as MatchCreateInput, q as MatchCreation, r as MatchEndedEvent, s as MatchEventUndoneEvent, c as VoiceRouteState, V as VoiceAudioMode, e as MatchVoiceService, d as VoiceTtsOptions } from '../types-CrmcX408.js';
2
+ export { G as GameSnapshot, t as MatchCreatedEvent, u as MatchFormat, v as MatchPlayer, w as MatchScoringMode, x as MatchTeam, y as MatchTeamId, P as PendingCourtSwitch, S as ServeState, T as TeamScore } from '../types-CrmcX408.js';
3
+ import { e as RemoteBinding, o as RemoteState, n as RemoteService, i as RemoteInputMode, d as RemoteActionMapping, a as RemoteHidGestureMapping } from '../types-DiKWjwvN.js';
4
+ export { c as RemoteHidVolumeEvent, j as RemoteMappedAction, k as RemoteNativeState, m as RemoteScanAdvertisement } from '../types-DiKWjwvN.js';
5
+ import React, { ReactNode } from 'react';
6
+
7
+ declare function createMatch(input: MatchCreateInput, source?: MatchInputSource): MatchCreation;
8
+ declare function applyMatchEvent(snapshot: MatchSnapshot, event: MatchEvent): MatchSnapshot;
9
+ declare function awardRally(snapshot: MatchSnapshot, displaySide: CourtSide, source?: MatchInputSource): RallyAwardedEvent | null;
10
+ declare function confirmCourtSwitch(snapshot: MatchSnapshot, source?: MatchInputSource): CourtSwitchConfirmedEvent;
11
+ declare function correctMatch(snapshot: MatchSnapshot, correction: MatchCorrection, source?: MatchInputSource): MatchCorrectedEvent;
12
+ declare function endMatch(snapshot: MatchSnapshot, reason: string, source?: MatchInputSource): MatchEndedEvent;
13
+
14
+ declare function replayMatchEvents(events: readonly unknown[]): MatchSnapshot;
15
+ declare function undoLastMatchEvent(events: readonly MatchEvent[], source?: MatchInputSource): MatchEventUndoneEvent | null;
16
+
17
+ declare const MATCH_PERSISTENCE_SCHEMA_VERSION: 1;
18
+ interface StoredMatchRow {
19
+ readonly matchId: string;
20
+ readonly schemaVersion: number;
21
+ readonly snapshotJson: string;
22
+ readonly snapshotSequence: number;
23
+ readonly status: MatchSnapshot["status"];
24
+ readonly updatedAt: number;
25
+ }
26
+ interface StoredMatchEventRow {
27
+ readonly matchId: string;
28
+ readonly sequence: number;
29
+ readonly eventId: string;
30
+ readonly schemaVersion: number;
31
+ readonly eventJson: string;
32
+ readonly createdAt: number;
33
+ }
34
+ interface MatchPersistenceTransaction {
35
+ getMatch(matchId: string): Promise<StoredMatchRow | null>;
36
+ getEvents(matchId: string): Promise<readonly StoredMatchEventRow[]>;
37
+ insertMatch(row: StoredMatchRow): Promise<void>;
38
+ insertEvent(row: StoredMatchEventRow): Promise<void>;
39
+ updateSnapshot(matchId: string, expectedSequence: number, row: Pick<StoredMatchRow, "schemaVersion" | "snapshotJson" | "snapshotSequence" | "status" | "updatedAt">): Promise<boolean>;
40
+ setActiveMatch(matchId: string | null): Promise<void>;
41
+ clearActiveMatch(matchId: string): Promise<void>;
42
+ }
43
+ interface MatchPersistenceDriver {
44
+ migrate(): Promise<void>;
45
+ transaction<T>(work: (transaction: MatchPersistenceTransaction) => Promise<T>): Promise<T>;
46
+ readMatch(matchId: string): Promise<{
47
+ readonly match: StoredMatchRow | null;
48
+ readonly events: readonly StoredMatchEventRow[];
49
+ }>;
50
+ getActiveMatchId(): Promise<string | null>;
51
+ readActiveMatch(): Promise<{
52
+ readonly activeMatchId: string | null;
53
+ readonly match: StoredMatchRow | null;
54
+ readonly events: readonly StoredMatchEventRow[];
55
+ }>;
56
+ readMatchList(): Promise<{
57
+ readonly matches: readonly StoredMatchRow[];
58
+ readonly activeMatchId: string | null;
59
+ }>;
60
+ getRemoteBinding?(): Promise<RemoteBinding | null>;
61
+ setRemoteBinding?(binding: RemoteBinding | null): Promise<void>;
62
+ }
63
+ interface PersistedMatch {
64
+ readonly snapshot: MatchSnapshot;
65
+ readonly events: readonly MatchEvent[];
66
+ }
67
+ interface MatchSummary {
68
+ readonly id: string;
69
+ readonly sequence: number;
70
+ readonly status: MatchSnapshot["status"];
71
+ readonly updatedAt: number;
72
+ readonly isActive: boolean;
73
+ }
74
+ type MatchPersistenceErrorCode = "not_found" | "already_exists" | "conflict" | "corrupt_data" | "unsupported_schema" | "sequence_mismatch" | "snapshot_mismatch" | "storage_failure";
75
+ declare class MatchPersistenceError extends Error {
76
+ readonly code: MatchPersistenceErrorCode;
77
+ readonly name = "MatchPersistenceError";
78
+ readonly recoverable = true;
79
+ readonly retryable: boolean;
80
+ constructor(code: MatchPersistenceErrorCode, message: string, options?: {
81
+ cause?: unknown;
82
+ });
83
+ }
84
+
85
+ declare const MATCH_SQL_SCHEMA_VERSION: 2;
86
+ declare const MATCH_SQL_MIGRATIONS: readonly ["CREATE TABLE IF NOT EXISTS match_aggregates (\n match_id TEXT PRIMARY KEY NOT NULL,\n schema_version INTEGER NOT NULL CHECK (schema_version = 1),\n snapshot_json TEXT NOT NULL,\n snapshot_sequence INTEGER NOT NULL CHECK (snapshot_sequence >= 0),\n status TEXT NOT NULL CHECK (status IN ('inProgress', 'completed', 'ended')),\n updated_at INTEGER NOT NULL\n )", "CREATE TABLE IF NOT EXISTS match_events (\n match_id TEXT NOT NULL,\n sequence INTEGER NOT NULL CHECK (sequence >= 0),\n event_id TEXT NOT NULL UNIQUE,\n schema_version INTEGER NOT NULL CHECK (schema_version = 1),\n event_json TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n PRIMARY KEY (match_id, sequence),\n FOREIGN KEY (match_id) REFERENCES match_aggregates(match_id) ON DELETE RESTRICT\n )", "CREATE TABLE IF NOT EXISTS match_active_pointer (\n slot INTEGER PRIMARY KEY NOT NULL CHECK (slot = 1),\n match_id TEXT NOT NULL UNIQUE,\n FOREIGN KEY (match_id) REFERENCES match_aggregates(match_id) ON DELETE RESTRICT\n )", "CREATE INDEX IF NOT EXISTS match_aggregates_recent\n ON match_aggregates(updated_at DESC)", "CREATE TRIGGER IF NOT EXISTS match_events_no_update\n BEFORE UPDATE ON match_events\n BEGIN SELECT RAISE(ABORT, 'match events are immutable'); END", "CREATE TRIGGER IF NOT EXISTS match_events_no_delete\n BEFORE DELETE ON match_events\n BEGIN SELECT RAISE(ABORT, 'match events are immutable'); END", "CREATE TABLE IF NOT EXISTS remote_binding (\n slot INTEGER PRIMARY KEY NOT NULL CHECK (slot = 1),\n device_id TEXT NOT NULL CHECK (length(trim(device_id)) > 0),\n label TEXT\n )"];
87
+
88
+ interface MemoryState {
89
+ migrationVersion: number;
90
+ remoteBinding: RemoteBinding | null;
91
+ activeMatchId: string | null;
92
+ matches: StoredMatchRow[];
93
+ events: StoredMatchEventRow[];
94
+ nextOrdinal: number;
95
+ ordinals: Record<string, number>;
96
+ }
97
+ declare class MemoryMatchPersistenceDriver implements MatchPersistenceDriver {
98
+ private state;
99
+ private eventInsertFailure;
100
+ private snapshotUpdateFailure;
101
+ private transactionTail;
102
+ migrate(): Promise<void>;
103
+ getRemoteBinding(): Promise<{
104
+ deviceId: string;
105
+ label?: string;
106
+ } | null>;
107
+ setRemoteBinding(binding: RemoteBinding | null): Promise<void>;
108
+ getMigrationVersion(): number;
109
+ transaction<T>(work: (transaction: MatchPersistenceTransaction) => Promise<T>): Promise<T>;
110
+ readMatch(matchId: string): Promise<{
111
+ match: StoredMatchRow | null;
112
+ events: StoredMatchEventRow[];
113
+ }>;
114
+ getActiveMatchId(): Promise<string | null>;
115
+ readActiveMatch(): Promise<{
116
+ activeMatchId: null;
117
+ match: null;
118
+ events: never[];
119
+ } | {
120
+ activeMatchId: string;
121
+ match: StoredMatchRow | null;
122
+ events: StoredMatchEventRow[];
123
+ }>;
124
+ readMatchList(): Promise<{
125
+ matches: StoredMatchRow[];
126
+ activeMatchId: string | null;
127
+ }>;
128
+ inspect(): MemoryState;
129
+ failNextSnapshotUpdate(error: Error): void;
130
+ failNextEventInsert(error: Error): void;
131
+ corruptSnapshotJson(matchId: string, snapshotJson: string): void;
132
+ corruptEventJson(matchId: string, sequence: number, eventJson: string): void;
133
+ corruptEventSchema(matchId: string, sequence: number, schemaVersion: number): void;
134
+ corruptEventSequence(matchId: string, sequence: number): void;
135
+ duplicateEventRow(matchId: string, sequence: number): void;
136
+ removeEventRow(matchId: string, sequence: number): void;
137
+ private mutateMatch;
138
+ private mutateEvent;
139
+ }
140
+
141
+ interface SqlRunResult {
142
+ readonly changes: number;
143
+ readonly lastInsertRowId: number;
144
+ }
145
+ interface SqlExecutor {
146
+ runAsync(sql: string, ...params: unknown[]): Promise<SqlRunResult>;
147
+ getFirstAsync<T>(sql: string, ...params: unknown[]): Promise<T | null>;
148
+ getAllAsync<T>(sql: string, ...params: unknown[]): Promise<T[]>;
149
+ }
150
+ interface ExpoSqliteDatabaseLike extends SqlExecutor {
151
+ execAsync(sql: string): Promise<void>;
152
+ }
153
+ /**
154
+ * Serializes adapters sharing this exact connection object. Separate
155
+ * connections to the same file rely on SQLite locking plus busy_timeout.
156
+ */
157
+ declare function createExpoSqliteMatchDriver(database: ExpoSqliteDatabaseLike): MatchPersistenceDriver;
158
+
159
+ interface MatchEventStoreOptions {
160
+ readonly now?: () => number;
161
+ }
162
+ declare class MatchEventStore {
163
+ private readonly driver;
164
+ private initialized;
165
+ private readonly now;
166
+ constructor(driver: MatchPersistenceDriver, options?: MatchEventStoreOptions);
167
+ initialize(): Promise<void>;
168
+ getRemoteBinding(): Promise<RemoteBinding | null>;
169
+ setRemoteBinding(binding: RemoteBinding | null): Promise<void>;
170
+ createMatch(input: MatchCreateInput, source?: MatchInputSource): Promise<PersistedMatch>;
171
+ appendEvent(currentSnapshot: MatchSnapshot, event: MatchEvent): Promise<PersistedMatch>;
172
+ resumeActiveMatch(): Promise<PersistedMatch | null>;
173
+ resumeMatch(matchId: string): Promise<PersistedMatch | null>;
174
+ listMatches(): Promise<readonly MatchSummary[]>;
175
+ archiveMatch(matchId: string): Promise<void>;
176
+ }
177
+
178
+ declare function createDefaultMatchEventStore(_databaseName?: string): Promise<MatchEventStore>;
179
+
180
+ interface PickleballMatchStore {
181
+ initialize(): Promise<void>;
182
+ createMatch(input: MatchCreateInput, source?: MatchInputSource): Promise<PersistedMatch>;
183
+ appendEvent(currentSnapshot: MatchSnapshot, event: MatchEvent): Promise<PersistedMatch>;
184
+ resumeMatch(matchId: string): Promise<PersistedMatch | null>;
185
+ resumeActiveMatch(): Promise<PersistedMatch | null>;
186
+ listMatches(): Promise<readonly MatchSummary[]>;
187
+ archiveMatch(matchId: string): Promise<void>;
188
+ getRemoteBinding?(): Promise<RemoteBinding | null>;
189
+ setRemoteBinding?(binding: RemoteBinding | null): Promise<void>;
190
+ }
191
+ declare class NoActiveMatchError extends Error {
192
+ readonly name = "NoActiveMatchError";
193
+ readonly code = "NO_ACTIVE_MATCH";
194
+ constructor();
195
+ }
196
+ /**
197
+ * State-changing actions reject with NoActiveMatchError when no match is loaded.
198
+ * Commands the engine deems ineligible are ignored and resolve the current snapshot.
199
+ */
200
+ interface PickleballMatchContextValue {
201
+ readonly snapshot: MatchSnapshot | null;
202
+ readonly events: readonly MatchEvent[];
203
+ readonly loading: boolean;
204
+ readonly error: Error | null;
205
+ readonly voiceError: Error | null;
206
+ readonly audioState: VoiceRouteState;
207
+ readonly remoteState: RemoteState;
208
+ createMatch(input: MatchCreateInput, source?: MatchInputSource): Promise<MatchSnapshot>;
209
+ resumeMatch(matchId?: string): Promise<MatchSnapshot | null>;
210
+ awardRallyToSide(side: CourtSide, source?: MatchInputSource): Promise<MatchSnapshot>;
211
+ undo(source?: MatchInputSource): Promise<MatchSnapshot>;
212
+ correctState(correction: MatchCorrection, source?: MatchInputSource): Promise<MatchSnapshot>;
213
+ confirmCourtSwitch(source?: MatchInputSource): Promise<MatchSnapshot>;
214
+ endMatch(reason: string, source?: MatchInputSource): Promise<MatchSnapshot>;
215
+ testSpeaker(): Promise<void>;
216
+ replayAnnouncement(): Promise<void>;
217
+ setVoiceAudioMode(mode: VoiceAudioMode): Promise<void>;
218
+ bindRemote(binding: RemoteBinding): Promise<void>;
219
+ unbindRemote(): Promise<void>;
220
+ startRemoteScan(): Promise<void>;
221
+ stopRemoteScan(): Promise<void>;
222
+ startHidCapture(): Promise<void>;
223
+ stopHidCapture(): Promise<void>;
224
+ }
225
+ interface PickleballMatchProviderProps {
226
+ readonly children?: ReactNode;
227
+ /** Fixed for this provider instance. Remount to use a different local store. */
228
+ readonly store?: PickleballMatchStore;
229
+ /** Fixed for this provider instance. Remount to use a different factory. */
230
+ readonly storeFactory?: () => Promise<PickleballMatchStore>;
231
+ /** Resumes the active local match on mount. Disable for hosts that resume explicitly. */
232
+ readonly autoResume?: boolean;
233
+ readonly onError?: (error: Error) => void | Promise<void>;
234
+ /** Fixed for this provider instance. Primarily useful for custom storage and tests. */
235
+ readonly voiceService?: MatchVoiceService;
236
+ /** Initial audio session mode. Defaults to live so mounting never disables capture. */
237
+ readonly voiceAudioMode?: VoiceAudioMode;
238
+ /** Cloud TTS endpoint for announcements. Without it only bundled WAVs play. Fixed for this provider instance. */
239
+ readonly voiceTts?: VoiceTtsOptions;
240
+ /** Fixed for this provider instance. Primarily useful for tests and custom native bridges. */
241
+ readonly remoteService?: RemoteService;
242
+ /** Foreground silence before a bound remote becomes outOfRange. */
243
+ readonly remoteOutOfRangeTimeoutMs?: number;
244
+ readonly remoteInputMode?: RemoteInputMode;
245
+ readonly remoteActionMapping?: Partial<RemoteActionMapping>;
246
+ /** Optional single/double/hold recognizer for one calibrated HID volume button. */
247
+ readonly hidGestureMapping?: RemoteHidGestureMapping;
248
+ readonly consumeHidVolumeKeys?: boolean;
249
+ }
250
+ declare function PickleballMatchProvider({ children, store, storeFactory, autoResume, onError, voiceService, voiceAudioMode, voiceTts, remoteService, remoteOutOfRangeTimeoutMs, remoteInputMode, remoteActionMapping, hidGestureMapping, consumeHidVolumeKeys, }: PickleballMatchProviderProps): React.JSX.Element;
251
+ declare function usePickleballMatch(): PickleballMatchContextValue;
252
+
253
+ export { CourtSide, CourtSwitchConfirmedEvent, type ExpoSqliteDatabaseLike, MATCH_PERSISTENCE_SCHEMA_VERSION, MATCH_SQL_MIGRATIONS, MATCH_SQL_SCHEMA_VERSION, MatchCorrectedEvent, MatchCorrection, MatchCreateInput, MatchCreation, MatchEndedEvent, MatchEvent, MatchEventStore, MatchEventUndoneEvent, MatchInputSource, type MatchPersistenceDriver, MatchPersistenceError, type MatchPersistenceErrorCode, type MatchPersistenceTransaction, MatchSnapshot, type MatchSummary, MatchVoiceService, MemoryMatchPersistenceDriver, NoActiveMatchError, type PersistedMatch, type PickleballMatchContextValue, PickleballMatchProvider, type PickleballMatchProviderProps, type PickleballMatchStore, RallyAwardedEvent, RemoteActionMapping, RemoteBinding, RemoteHidGestureMapping, RemoteInputMode, RemoteService, RemoteState, type StoredMatchEventRow, type StoredMatchRow, VoiceAudioMode, VoiceRouteState, VoiceTtsOptions, applyMatchEvent, awardRally, confirmCourtSwitch, correctMatch, createDefaultMatchEventStore, createExpoSqliteMatchDriver, createMatch, endMatch, replayMatchEvents, undoLastMatchEvent, usePickleballMatch };