powersync-bridge 0.0.1-alpha.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.
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Shared vocabulary — defined once (ARCHITECTURE.md §5.1, §6.1, §9).
3
+ * No `any` in public signatures; the only `unknown` is an inbound raw payload.
4
+ */
5
+ type RowOrigin = 'local' | 'api';
6
+ interface PagedRow<T> {
7
+ readonly row: T;
8
+ /** Producing source, post-arbitration. */
9
+ readonly origin: RowOrigin;
10
+ /** A LOCAL row exists in SQLite right now. Write guards key on this, never on origin (R14). */
11
+ readonly presentLocally: boolean;
12
+ /** Rendered from local before any fresh snapshot covered it (R12). */
13
+ readonly stale?: true;
14
+ }
15
+ type ListState = 'pending' | 'api' | 'local' | 'converged';
16
+ type Boundary = 'more' | 'end' | 'unknown';
17
+ type BoundaryReason = 'offline' | 'timeout' | 'error' | 'partition-cap';
18
+ interface ListErrors {
19
+ local?: Error;
20
+ api?: Error;
21
+ stream?: Error;
22
+ }
23
+ type KeyOf<T> = keyof T & string;
24
+ type OrderKey<T> = KeyOf<T> | readonly [KeyOf<T>, KeyOf<T>];
25
+ type SortDirection = 'asc' | 'desc';
26
+ /** Cursor type for an order key: scalar for a single key, tuple for a 2-tuple (F3-010). */
27
+ type CursorOf<T, K> = K extends readonly [infer A extends KeyOf<T>, infer B extends KeyOf<T>] ? [T[A], T[B]] : K extends KeyOf<T> ? T[K] : never;
28
+ /**
29
+ * The cursor type of a definition — INCLUDES `undefined` (the first page). Annotate the page
30
+ * argument of an infinite query with it:
31
+ * query: ({ cursor, limit }: PageArgs<Cursor<Sale, ['soldAt', 'id']>>, scope) => …
32
+ * Every position that carries a cursor (page argument, partitionOf, fetchPage, initialCursor)
33
+ * uses the same type, so TypeScript sees one consistent candidate for TCursor.
34
+ */
35
+ type Cursor<T, K extends OrderKey<T>> = CursorOf<T, K> | undefined;
36
+ /** Base64-encoded tuple the API never unpacks (§12.1). */
37
+ type OpaqueCursor = string & {
38
+ readonly __opaqueCursor: true;
39
+ };
40
+ type Params = Record<string, string | number | boolean | null>;
41
+ type PendingOp = 'INSERT' | 'UPDATE' | 'DELETE';
42
+ interface PendingOpRef {
43
+ readonly table: string;
44
+ readonly id: string;
45
+ readonly op: PendingOp;
46
+ }
47
+ interface CrudDelta {
48
+ readonly added: readonly PendingOpRef[];
49
+ readonly completed: readonly PendingOpRef[];
50
+ }
51
+ /** What the PowerSync SDK's `db.query()` accepts (kept structural: no SDK import in core). */
52
+ interface CompilableQuery<T> {
53
+ compile(): {
54
+ sql: string;
55
+ parameters?: unknown[];
56
+ };
57
+ execute(): Promise<T[]>;
58
+ }
59
+ /** A query the adapter has bound to the ORM. Rows come back DECODED (F-010). */
60
+ interface PreparedQuery<T> {
61
+ /** Tables the query reads; derived by the adapter from the builder (F-022). */
62
+ readonly tables: readonly string[];
63
+ run(signal?: AbortSignal): Promise<T[]>;
64
+ /** When present, the driver hands this to the SDK's differentialWatch (R6). */
65
+ readonly compilable?: CompilableQuery<T>;
66
+ }
67
+ /** One emission of a differential watch. `all` is the full current result, reference-stable per id. */
68
+ interface RowDiff<T> {
69
+ readonly all: readonly T[];
70
+ readonly added: readonly T[];
71
+ readonly updated: readonly T[];
72
+ /** Ids no longer in the result set. */
73
+ readonly removed: readonly string[];
74
+ }
75
+ interface DifferentialStream<T> {
76
+ subscribe(onDiff: (diff: RowDiff<T>) => void, onError?: (err: Error) => void): () => void;
77
+ }
78
+ interface StreamDescription {
79
+ readonly name: string;
80
+ readonly params: Params | null;
81
+ }
82
+ interface StreamStatus extends StreamDescription {
83
+ readonly active: boolean;
84
+ readonly expiresAt: Date | null;
85
+ readonly hasSynced: boolean;
86
+ readonly lastSyncedAt: Date | null;
87
+ }
88
+ interface StreamHandle extends StreamDescription {
89
+ /** Resolves when the subscription's first sync has been applied. Rejects on abort. */
90
+ waitForFirstSync(signal?: AbortSignal): Promise<void>;
91
+ unsubscribe(): void;
92
+ }
93
+ interface PriorityStatus {
94
+ readonly hasSynced: boolean;
95
+ readonly lastSyncedAt: Date | null;
96
+ }
97
+ interface SyncStatusView {
98
+ readonly connected: boolean;
99
+ readonly downloading: boolean;
100
+ /** Persisted by the SDK: true at open on any previously synced DB. Never "answered". */
101
+ readonly hasSynced: boolean | undefined;
102
+ readonly lastSyncedAt: Date | null;
103
+ readonly syncStreams: readonly StreamStatus[];
104
+ forStream(desc: StreamDescription): StreamStatus | undefined;
105
+ statusForPriority(priority: number): PriorityStatus;
106
+ }
107
+ interface BridgeDriver {
108
+ query<T>(q: PreparedQuery<T>, signal?: AbortSignal): Promise<T[]>;
109
+ diffs<T>(q: PreparedQuery<T>, opts?: {
110
+ throttleMs?: number;
111
+ }): DifferentialStream<T>;
112
+ /** Full read of the upload queue (iterate `getCrudBatch` until `haveMore` is false), keyed (table, id). */
113
+ pendingUploadIds(): Promise<PendingOpRef[]>;
114
+ /** The driver diffs consecutive queue snapshots and EMITS the delta; core never re-diffs. */
115
+ onCrudChange(cb: (delta: CrudDelta) => void): () => void;
116
+ /** Batched presence probe: which of `ids` exist in `table` right now (R14). */
117
+ presentIds(table: string, ids: readonly string[]): Promise<Set<string>>;
118
+ /** A hint for the ApiPolicy gate, never a skip. `undefined` when unknown. */
119
+ onlineHint(): boolean | undefined;
120
+ syncStream(name: string, params: Params | null, opts?: {
121
+ ttl?: number;
122
+ priority?: 0 | 1 | 2 | 3;
123
+ }): Promise<StreamHandle>;
124
+ syncStatus(): SyncStatusView;
125
+ onStatusChanged(cb: () => void): () => void;
126
+ }
127
+ /**
128
+ * Anything whose awaited value is `T[]`. Drizzle/Kysely select builders are thenables
129
+ * with exactly this shape, which is what lets `TItem` be inferred from the query (§8.1).
130
+ */
131
+ type RowsBuilder<T> = PromiseLike<T[]>;
132
+ interface QueryAdapter {
133
+ prepare<T>(builder: RowsBuilder<T>): PreparedQuery<T>;
134
+ }
135
+ interface Snapshot<TApi> {
136
+ readonly rows: readonly TApi[];
137
+ /** Optional PowerSync write checkpoint the backend served at (§10.2). */
138
+ readonly asOf?: string;
139
+ }
140
+ /**
141
+ * Definitions close over the app's ORM instance (`drizzle.select()…`) instead of receiving it:
142
+ * a query callback whose every parameter is annotated is not context-sensitive, which is what
143
+ * lets TypeScript infer TItem from its return (§8.1) alongside the other properties.
144
+ */
145
+ interface RacedListDefinition<TItem, TScope, TApi, K extends OrderKey<TItem>> {
146
+ readonly kind: 'raced';
147
+ readonly id: string;
148
+ /** Builder; the adapter executes it decoded. ORDER BY … LIMIT. */
149
+ readonly query: (scope: TScope) => RowsBuilder<TItem>;
150
+ /** The API leg. */
151
+ readonly fetchSnapshot: (scope: TScope, signal: AbortSignal) => Promise<Snapshot<TApi>>;
152
+ /** Return type is the compile-time check against TItem (§8.2). */
153
+ readonly mapApi: (raw: TApi) => TItem;
154
+ readonly orderKey: K;
155
+ /** Direction of the ORDER BY. Applies to every key of a tuple. Default 'asc'. */
156
+ readonly direction?: SortDirection;
157
+ readonly limit: number;
158
+ /** ms; default 10 min. One snapshot per epoch × queryKey × window (R9). */
159
+ readonly staleTime?: number;
160
+ /** Owning stream; gates freshness per-stream (R5). */
161
+ readonly stream?: StreamDescription;
162
+ /** The table rows live in — needed for presence probes and write guards. Inferred by adapters when possible. */
163
+ readonly table?: string;
164
+ /** Keep api-only rows never seen locally after ownership (out-of-scope assumption). Default false. */
165
+ readonly apiScopeSupersetOfLocal?: boolean;
166
+ /** Pre-snapshot: render local rows flagged stale (default) or hold 'pending'. */
167
+ readonly holdPending?: 'stale' | 'pending';
168
+ /** Classifies rows for predicate windows (§6.4). */
169
+ readonly isInWindow?: (row: TItem) => boolean;
170
+ }
171
+ interface PageArgs<TCursor> {
172
+ /** `undefined` for the first page (TCursor includes undefined — see `Cursor`). */
173
+ readonly cursor: TCursor;
174
+ /** The engine passes pageSize + 1 (lookahead). */
175
+ readonly limit: number;
176
+ }
177
+ interface PartitionPolicy<TCursor, TPartition> {
178
+ readonly kind: 'partition';
179
+ readonly stream: string;
180
+ partitionOf(cursor: TCursor): TPartition | null;
181
+ nextPartition(p: TPartition): TPartition | null;
182
+ partitionParams(p: TPartition): Params;
183
+ /** Static floor: always required. */
184
+ readonly floor: TPartition;
185
+ /** Optional exact floor from the server (a lower bound, re-validated on 'end'). */
186
+ serverFloor?(): Promise<TPartition | null>;
187
+ /** Compares partitions; default: string/number `<`. */
188
+ isBefore?(a: TPartition, b: TPartition): boolean;
189
+ readonly ttl?: number;
190
+ readonly partitionCap?: number;
191
+ readonly timeoutMs?: number;
192
+ }
193
+ interface ApiPolicy<TCursor, TItem> {
194
+ readonly kind: 'api';
195
+ fetchPage(cursor: TCursor, signal: AbortSignal): Promise<readonly TItem[]>;
196
+ }
197
+ /** Degraded mode for offset-only APIs: id-set dedupe removes duplicates; skips are reported. */
198
+ interface ApiOffsetPolicy<TItem> {
199
+ readonly kind: 'api-offset';
200
+ fetchPage(offset: number, signal: AbortSignal): Promise<readonly TItem[]>;
201
+ }
202
+ type EscalationPolicy<TCursor, TItem, TPartition = string> = PartitionPolicy<TCursor, TPartition> | ApiPolicy<TCursor, TItem> | ApiOffsetPolicy<TItem>;
203
+ /**
204
+ * `TCursor` is a separate parameter, inferred from the ANNOTATED page argument
205
+ * (`{ cursor, limit }: PageArgs<Cursor<Sale, ['soldAt','id']>>`) and the policy's cursor
206
+ * positions, which all carry the same type.
207
+ */
208
+ interface InfiniteListDefinition<TItem, TScope, K extends OrderKey<TItem>, TCursor, TPartition = string> {
209
+ readonly kind: 'infinite';
210
+ readonly id: string;
211
+ readonly pageSize: number;
212
+ /** Page first so scope-less lists can omit the second parameter entirely. */
213
+ readonly query: (page: PageArgs<TCursor>, scope: TScope) => RowsBuilder<TItem>;
214
+ readonly orderKey: K;
215
+ readonly direction?: SortDirection;
216
+ readonly initialCursor?: TCursor;
217
+ readonly escalate: EscalationPolicy<TCursor, TItem, TPartition>;
218
+ readonly table?: string;
219
+ readonly isInWindow?: (row: TItem) => boolean;
220
+ /** Bridge-active partition subscriptions kept alive (LRU). Bounds memory, never buckets. */
221
+ readonly keepPagesAlive?: number;
222
+ }
223
+ interface RacedListResult<TItem> {
224
+ readonly items: readonly PagedRow<TItem>[];
225
+ readonly state: ListState;
226
+ readonly awaiting: {
227
+ readonly local: boolean;
228
+ readonly api: boolean;
229
+ };
230
+ readonly errors: ListErrors;
231
+ }
232
+ interface InfiniteListResult<TItem> {
233
+ readonly items: readonly PagedRow<TItem>[];
234
+ readonly boundary: Boundary;
235
+ readonly boundaryReason?: BoundaryReason;
236
+ readonly awaitingSync: boolean;
237
+ readonly loading: boolean;
238
+ readonly errors: ListErrors;
239
+ /** Set in api-offset mode when a fetched page shows the list shifted under us. */
240
+ readonly warnings: readonly string[];
241
+ }
242
+ type OnDemandStatus = 'subscribing' | 'syncing' | 'synced' | 'timeout' | 'offline' | 'error';
243
+ interface OnDemandResult {
244
+ readonly status: OnDemandStatus;
245
+ readonly error?: Error;
246
+ }
247
+ interface Store<S> {
248
+ getSnapshot(): S;
249
+ subscribe(listener: () => void): () => void;
250
+ }
251
+ interface RaceMeasurement {
252
+ readonly list: string;
253
+ readonly winner: 'local' | 'api' | 'none';
254
+ readonly localMs: number | null;
255
+ readonly apiMs: number | null;
256
+ /** From connect() to the first in-session local freshness — never a persisted hasSynced. */
257
+ readonly sessionFreshnessMs: number | null;
258
+ readonly rowsRendered: number;
259
+ readonly source: ListState;
260
+ }
261
+ type MeasureSink = (m: RaceMeasurement) => void;
262
+
263
+ /** Injectable time so the engine is testable with virtual time. */
264
+ interface BridgeClock {
265
+ now(): number;
266
+ /** Returns a cancel function. */
267
+ setTimeout(fn: () => void, ms: number): () => void;
268
+ }
269
+ declare const realClock: BridgeClock;
270
+
271
+ export { type ApiOffsetPolicy as A, type BridgeDriver as B, type CursorOf as C, type DifferentialStream as D, type EscalationPolicy as E, type Snapshot as F, type StreamHandle as G, type StreamStatus as H, type InfiniteListResult as I, type SyncStatusView as J, type KeyOf as K, type ListErrors as L, type MeasureSink as M, realClock as N, type OrderKey as O, type Params as P, type QueryAdapter as Q, type RacedListResult as R, type Store as S, type BridgeClock as a, type InfiniteListDefinition as b, type OnDemandResult as c, type StreamDescription as d, type OpaqueCursor as e, type SortDirection as f, type CrudDelta as g, type PendingOpRef as h, type RacedListDefinition as i, type ApiPolicy as j, type Boundary as k, type BoundaryReason as l, type CompilableQuery as m, type Cursor as n, type ListState as o, type OnDemandStatus as p, type PageArgs as q, type PagedRow as r, type PartitionPolicy as s, type PendingOp as t, type PreparedQuery as u, type PriorityStatus as v, type RaceMeasurement as w, type RowDiff as x, type RowOrigin as y, type RowsBuilder as z };
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Shared vocabulary — defined once (ARCHITECTURE.md §5.1, §6.1, §9).
3
+ * No `any` in public signatures; the only `unknown` is an inbound raw payload.
4
+ */
5
+ type RowOrigin = 'local' | 'api';
6
+ interface PagedRow<T> {
7
+ readonly row: T;
8
+ /** Producing source, post-arbitration. */
9
+ readonly origin: RowOrigin;
10
+ /** A LOCAL row exists in SQLite right now. Write guards key on this, never on origin (R14). */
11
+ readonly presentLocally: boolean;
12
+ /** Rendered from local before any fresh snapshot covered it (R12). */
13
+ readonly stale?: true;
14
+ }
15
+ type ListState = 'pending' | 'api' | 'local' | 'converged';
16
+ type Boundary = 'more' | 'end' | 'unknown';
17
+ type BoundaryReason = 'offline' | 'timeout' | 'error' | 'partition-cap';
18
+ interface ListErrors {
19
+ local?: Error;
20
+ api?: Error;
21
+ stream?: Error;
22
+ }
23
+ type KeyOf<T> = keyof T & string;
24
+ type OrderKey<T> = KeyOf<T> | readonly [KeyOf<T>, KeyOf<T>];
25
+ type SortDirection = 'asc' | 'desc';
26
+ /** Cursor type for an order key: scalar for a single key, tuple for a 2-tuple (F3-010). */
27
+ type CursorOf<T, K> = K extends readonly [infer A extends KeyOf<T>, infer B extends KeyOf<T>] ? [T[A], T[B]] : K extends KeyOf<T> ? T[K] : never;
28
+ /**
29
+ * The cursor type of a definition — INCLUDES `undefined` (the first page). Annotate the page
30
+ * argument of an infinite query with it:
31
+ * query: ({ cursor, limit }: PageArgs<Cursor<Sale, ['soldAt', 'id']>>, scope) => …
32
+ * Every position that carries a cursor (page argument, partitionOf, fetchPage, initialCursor)
33
+ * uses the same type, so TypeScript sees one consistent candidate for TCursor.
34
+ */
35
+ type Cursor<T, K extends OrderKey<T>> = CursorOf<T, K> | undefined;
36
+ /** Base64-encoded tuple the API never unpacks (§12.1). */
37
+ type OpaqueCursor = string & {
38
+ readonly __opaqueCursor: true;
39
+ };
40
+ type Params = Record<string, string | number | boolean | null>;
41
+ type PendingOp = 'INSERT' | 'UPDATE' | 'DELETE';
42
+ interface PendingOpRef {
43
+ readonly table: string;
44
+ readonly id: string;
45
+ readonly op: PendingOp;
46
+ }
47
+ interface CrudDelta {
48
+ readonly added: readonly PendingOpRef[];
49
+ readonly completed: readonly PendingOpRef[];
50
+ }
51
+ /** What the PowerSync SDK's `db.query()` accepts (kept structural: no SDK import in core). */
52
+ interface CompilableQuery<T> {
53
+ compile(): {
54
+ sql: string;
55
+ parameters?: unknown[];
56
+ };
57
+ execute(): Promise<T[]>;
58
+ }
59
+ /** A query the adapter has bound to the ORM. Rows come back DECODED (F-010). */
60
+ interface PreparedQuery<T> {
61
+ /** Tables the query reads; derived by the adapter from the builder (F-022). */
62
+ readonly tables: readonly string[];
63
+ run(signal?: AbortSignal): Promise<T[]>;
64
+ /** When present, the driver hands this to the SDK's differentialWatch (R6). */
65
+ readonly compilable?: CompilableQuery<T>;
66
+ }
67
+ /** One emission of a differential watch. `all` is the full current result, reference-stable per id. */
68
+ interface RowDiff<T> {
69
+ readonly all: readonly T[];
70
+ readonly added: readonly T[];
71
+ readonly updated: readonly T[];
72
+ /** Ids no longer in the result set. */
73
+ readonly removed: readonly string[];
74
+ }
75
+ interface DifferentialStream<T> {
76
+ subscribe(onDiff: (diff: RowDiff<T>) => void, onError?: (err: Error) => void): () => void;
77
+ }
78
+ interface StreamDescription {
79
+ readonly name: string;
80
+ readonly params: Params | null;
81
+ }
82
+ interface StreamStatus extends StreamDescription {
83
+ readonly active: boolean;
84
+ readonly expiresAt: Date | null;
85
+ readonly hasSynced: boolean;
86
+ readonly lastSyncedAt: Date | null;
87
+ }
88
+ interface StreamHandle extends StreamDescription {
89
+ /** Resolves when the subscription's first sync has been applied. Rejects on abort. */
90
+ waitForFirstSync(signal?: AbortSignal): Promise<void>;
91
+ unsubscribe(): void;
92
+ }
93
+ interface PriorityStatus {
94
+ readonly hasSynced: boolean;
95
+ readonly lastSyncedAt: Date | null;
96
+ }
97
+ interface SyncStatusView {
98
+ readonly connected: boolean;
99
+ readonly downloading: boolean;
100
+ /** Persisted by the SDK: true at open on any previously synced DB. Never "answered". */
101
+ readonly hasSynced: boolean | undefined;
102
+ readonly lastSyncedAt: Date | null;
103
+ readonly syncStreams: readonly StreamStatus[];
104
+ forStream(desc: StreamDescription): StreamStatus | undefined;
105
+ statusForPriority(priority: number): PriorityStatus;
106
+ }
107
+ interface BridgeDriver {
108
+ query<T>(q: PreparedQuery<T>, signal?: AbortSignal): Promise<T[]>;
109
+ diffs<T>(q: PreparedQuery<T>, opts?: {
110
+ throttleMs?: number;
111
+ }): DifferentialStream<T>;
112
+ /** Full read of the upload queue (iterate `getCrudBatch` until `haveMore` is false), keyed (table, id). */
113
+ pendingUploadIds(): Promise<PendingOpRef[]>;
114
+ /** The driver diffs consecutive queue snapshots and EMITS the delta; core never re-diffs. */
115
+ onCrudChange(cb: (delta: CrudDelta) => void): () => void;
116
+ /** Batched presence probe: which of `ids` exist in `table` right now (R14). */
117
+ presentIds(table: string, ids: readonly string[]): Promise<Set<string>>;
118
+ /** A hint for the ApiPolicy gate, never a skip. `undefined` when unknown. */
119
+ onlineHint(): boolean | undefined;
120
+ syncStream(name: string, params: Params | null, opts?: {
121
+ ttl?: number;
122
+ priority?: 0 | 1 | 2 | 3;
123
+ }): Promise<StreamHandle>;
124
+ syncStatus(): SyncStatusView;
125
+ onStatusChanged(cb: () => void): () => void;
126
+ }
127
+ /**
128
+ * Anything whose awaited value is `T[]`. Drizzle/Kysely select builders are thenables
129
+ * with exactly this shape, which is what lets `TItem` be inferred from the query (§8.1).
130
+ */
131
+ type RowsBuilder<T> = PromiseLike<T[]>;
132
+ interface QueryAdapter {
133
+ prepare<T>(builder: RowsBuilder<T>): PreparedQuery<T>;
134
+ }
135
+ interface Snapshot<TApi> {
136
+ readonly rows: readonly TApi[];
137
+ /** Optional PowerSync write checkpoint the backend served at (§10.2). */
138
+ readonly asOf?: string;
139
+ }
140
+ /**
141
+ * Definitions close over the app's ORM instance (`drizzle.select()…`) instead of receiving it:
142
+ * a query callback whose every parameter is annotated is not context-sensitive, which is what
143
+ * lets TypeScript infer TItem from its return (§8.1) alongside the other properties.
144
+ */
145
+ interface RacedListDefinition<TItem, TScope, TApi, K extends OrderKey<TItem>> {
146
+ readonly kind: 'raced';
147
+ readonly id: string;
148
+ /** Builder; the adapter executes it decoded. ORDER BY … LIMIT. */
149
+ readonly query: (scope: TScope) => RowsBuilder<TItem>;
150
+ /** The API leg. */
151
+ readonly fetchSnapshot: (scope: TScope, signal: AbortSignal) => Promise<Snapshot<TApi>>;
152
+ /** Return type is the compile-time check against TItem (§8.2). */
153
+ readonly mapApi: (raw: TApi) => TItem;
154
+ readonly orderKey: K;
155
+ /** Direction of the ORDER BY. Applies to every key of a tuple. Default 'asc'. */
156
+ readonly direction?: SortDirection;
157
+ readonly limit: number;
158
+ /** ms; default 10 min. One snapshot per epoch × queryKey × window (R9). */
159
+ readonly staleTime?: number;
160
+ /** Owning stream; gates freshness per-stream (R5). */
161
+ readonly stream?: StreamDescription;
162
+ /** The table rows live in — needed for presence probes and write guards. Inferred by adapters when possible. */
163
+ readonly table?: string;
164
+ /** Keep api-only rows never seen locally after ownership (out-of-scope assumption). Default false. */
165
+ readonly apiScopeSupersetOfLocal?: boolean;
166
+ /** Pre-snapshot: render local rows flagged stale (default) or hold 'pending'. */
167
+ readonly holdPending?: 'stale' | 'pending';
168
+ /** Classifies rows for predicate windows (§6.4). */
169
+ readonly isInWindow?: (row: TItem) => boolean;
170
+ }
171
+ interface PageArgs<TCursor> {
172
+ /** `undefined` for the first page (TCursor includes undefined — see `Cursor`). */
173
+ readonly cursor: TCursor;
174
+ /** The engine passes pageSize + 1 (lookahead). */
175
+ readonly limit: number;
176
+ }
177
+ interface PartitionPolicy<TCursor, TPartition> {
178
+ readonly kind: 'partition';
179
+ readonly stream: string;
180
+ partitionOf(cursor: TCursor): TPartition | null;
181
+ nextPartition(p: TPartition): TPartition | null;
182
+ partitionParams(p: TPartition): Params;
183
+ /** Static floor: always required. */
184
+ readonly floor: TPartition;
185
+ /** Optional exact floor from the server (a lower bound, re-validated on 'end'). */
186
+ serverFloor?(): Promise<TPartition | null>;
187
+ /** Compares partitions; default: string/number `<`. */
188
+ isBefore?(a: TPartition, b: TPartition): boolean;
189
+ readonly ttl?: number;
190
+ readonly partitionCap?: number;
191
+ readonly timeoutMs?: number;
192
+ }
193
+ interface ApiPolicy<TCursor, TItem> {
194
+ readonly kind: 'api';
195
+ fetchPage(cursor: TCursor, signal: AbortSignal): Promise<readonly TItem[]>;
196
+ }
197
+ /** Degraded mode for offset-only APIs: id-set dedupe removes duplicates; skips are reported. */
198
+ interface ApiOffsetPolicy<TItem> {
199
+ readonly kind: 'api-offset';
200
+ fetchPage(offset: number, signal: AbortSignal): Promise<readonly TItem[]>;
201
+ }
202
+ type EscalationPolicy<TCursor, TItem, TPartition = string> = PartitionPolicy<TCursor, TPartition> | ApiPolicy<TCursor, TItem> | ApiOffsetPolicy<TItem>;
203
+ /**
204
+ * `TCursor` is a separate parameter, inferred from the ANNOTATED page argument
205
+ * (`{ cursor, limit }: PageArgs<Cursor<Sale, ['soldAt','id']>>`) and the policy's cursor
206
+ * positions, which all carry the same type.
207
+ */
208
+ interface InfiniteListDefinition<TItem, TScope, K extends OrderKey<TItem>, TCursor, TPartition = string> {
209
+ readonly kind: 'infinite';
210
+ readonly id: string;
211
+ readonly pageSize: number;
212
+ /** Page first so scope-less lists can omit the second parameter entirely. */
213
+ readonly query: (page: PageArgs<TCursor>, scope: TScope) => RowsBuilder<TItem>;
214
+ readonly orderKey: K;
215
+ readonly direction?: SortDirection;
216
+ readonly initialCursor?: TCursor;
217
+ readonly escalate: EscalationPolicy<TCursor, TItem, TPartition>;
218
+ readonly table?: string;
219
+ readonly isInWindow?: (row: TItem) => boolean;
220
+ /** Bridge-active partition subscriptions kept alive (LRU). Bounds memory, never buckets. */
221
+ readonly keepPagesAlive?: number;
222
+ }
223
+ interface RacedListResult<TItem> {
224
+ readonly items: readonly PagedRow<TItem>[];
225
+ readonly state: ListState;
226
+ readonly awaiting: {
227
+ readonly local: boolean;
228
+ readonly api: boolean;
229
+ };
230
+ readonly errors: ListErrors;
231
+ }
232
+ interface InfiniteListResult<TItem> {
233
+ readonly items: readonly PagedRow<TItem>[];
234
+ readonly boundary: Boundary;
235
+ readonly boundaryReason?: BoundaryReason;
236
+ readonly awaitingSync: boolean;
237
+ readonly loading: boolean;
238
+ readonly errors: ListErrors;
239
+ /** Set in api-offset mode when a fetched page shows the list shifted under us. */
240
+ readonly warnings: readonly string[];
241
+ }
242
+ type OnDemandStatus = 'subscribing' | 'syncing' | 'synced' | 'timeout' | 'offline' | 'error';
243
+ interface OnDemandResult {
244
+ readonly status: OnDemandStatus;
245
+ readonly error?: Error;
246
+ }
247
+ interface Store<S> {
248
+ getSnapshot(): S;
249
+ subscribe(listener: () => void): () => void;
250
+ }
251
+ interface RaceMeasurement {
252
+ readonly list: string;
253
+ readonly winner: 'local' | 'api' | 'none';
254
+ readonly localMs: number | null;
255
+ readonly apiMs: number | null;
256
+ /** From connect() to the first in-session local freshness — never a persisted hasSynced. */
257
+ readonly sessionFreshnessMs: number | null;
258
+ readonly rowsRendered: number;
259
+ readonly source: ListState;
260
+ }
261
+ type MeasureSink = (m: RaceMeasurement) => void;
262
+
263
+ /** Injectable time so the engine is testable with virtual time. */
264
+ interface BridgeClock {
265
+ now(): number;
266
+ /** Returns a cancel function. */
267
+ setTimeout(fn: () => void, ms: number): () => void;
268
+ }
269
+ declare const realClock: BridgeClock;
270
+
271
+ export { type ApiOffsetPolicy as A, type BridgeDriver as B, type CursorOf as C, type DifferentialStream as D, type EscalationPolicy as E, type Snapshot as F, type StreamHandle as G, type StreamStatus as H, type InfiniteListResult as I, type SyncStatusView as J, type KeyOf as K, type ListErrors as L, type MeasureSink as M, realClock as N, type OrderKey as O, type Params as P, type QueryAdapter as Q, type RacedListResult as R, type Store as S, type BridgeClock as a, type InfiniteListDefinition as b, type OnDemandResult as c, type StreamDescription as d, type OpaqueCursor as e, type SortDirection as f, type CrudDelta as g, type PendingOpRef as h, type RacedListDefinition as i, type ApiPolicy as j, type Boundary as k, type BoundaryReason as l, type CompilableQuery as m, type Cursor as n, type ListState as o, type OnDemandStatus as p, type PageArgs as q, type PagedRow as r, type PartitionPolicy as s, type PendingOp as t, type PreparedQuery as u, type PriorityStatus as v, type RaceMeasurement as w, type RowDiff as x, type RowOrigin as y, type RowsBuilder as z };