snapback2 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/README.md +127 -0
  2. package/bin/snapback2.mjs +36 -0
  3. package/dist/api.d.ts +20 -0
  4. package/dist/assets.d.ts +34 -0
  5. package/dist/client-assets.d.ts +26 -0
  6. package/dist/client-offline.d.ts +94 -0
  7. package/dist/client-outbox.d.ts +58 -0
  8. package/dist/client-upload.d.ts +41 -0
  9. package/dist/client-wire.d.ts +41 -0
  10. package/dist/client.d.ts +12 -0
  11. package/dist/client.mjs +5 -0
  12. package/dist/client.mjs.map +7 -0
  13. package/dist/compiler-core.mjs +60 -0
  14. package/dist/compiler-core.mjs.map +7 -0
  15. package/dist/compiler-lib.d.ts +71 -0
  16. package/dist/compiler.mjs +5 -0
  17. package/dist/compiler.mjs.map +7 -0
  18. package/dist/drain.d.ts +33 -0
  19. package/dist/expo/files.d.ts +3 -0
  20. package/dist/expo/index.d.ts +16 -0
  21. package/dist/expo/token-store.d.ts +7 -0
  22. package/dist/expo/witness.d.ts +28 -0
  23. package/dist/expo/witness.mjs +2 -0
  24. package/dist/expo/witness.mjs.map +7 -0
  25. package/dist/expo.d.ts +4 -0
  26. package/dist/expo.mjs +9 -0
  27. package/dist/expo.mjs.map +7 -0
  28. package/dist/guide/auth.md +89 -0
  29. package/dist/guide/effects.md +52 -0
  30. package/dist/guide/families.json +446 -0
  31. package/dist/guide/grammar.md +52 -0
  32. package/dist/guide/live-query.md +27 -0
  33. package/dist/guide/offline.md +75 -0
  34. package/dist/guide/personas.md +15 -0
  35. package/dist/guide/quarry.md +297 -0
  36. package/dist/guide/testing.md +57 -0
  37. package/dist/index.d.ts +362 -0
  38. package/dist/index.mjs +2 -0
  39. package/dist/index.mjs.map +7 -0
  40. package/dist/offline-protocol.d.ts +180 -0
  41. package/dist/offline-schema.d.ts +1 -0
  42. package/dist/offline.d.ts +177 -0
  43. package/dist/react-core.d.ts +91 -0
  44. package/dist/react-core.mjs +2 -0
  45. package/dist/react-core.mjs.map +7 -0
  46. package/dist/react-native/components.d.ts +23 -0
  47. package/dist/react-native/files.d.ts +43 -0
  48. package/dist/react-native/source.d.ts +16 -0
  49. package/dist/react-native.d.ts +4 -0
  50. package/dist/react-native.mjs +2 -0
  51. package/dist/react-native.mjs.map +7 -0
  52. package/dist/react.d.ts +19 -0
  53. package/dist/react.mjs +2 -0
  54. package/dist/react.mjs.map +7 -0
  55. package/dist/sqlite-test.mjs +1720 -0
  56. package/dist/sqlite-test.mjs.map +7 -0
  57. package/dist/sse.d.ts +44 -0
  58. package/dist/store/byte-cache.d.ts +162 -0
  59. package/dist/store/canonical.d.ts +2 -0
  60. package/dist/store/indexeddb.d.ts +23 -0
  61. package/dist/store/locks.d.ts +11 -0
  62. package/dist/store/outbox.d.ts +113 -0
  63. package/dist/store/overlay-retirement.d.ts +17 -0
  64. package/dist/store/projection.d.ts +11 -0
  65. package/dist/store/range-store.d.ts +388 -0
  66. package/dist/store/sqlite-driver.conformance.d.ts +7 -0
  67. package/dist/store/sqlite-driver.d.ts +16 -0
  68. package/dist/store/sqlite-expo.d.ts +3 -0
  69. package/dist/store/sqlite.d.ts +31 -0
  70. package/dist/templates/chat/expo/App.tsx +29 -0
  71. package/dist/templates/chat/expo/app.json +11 -0
  72. package/dist/templates/chat/expo/index.js +3 -0
  73. package/dist/templates/chat/expo/shared/log.js +92 -0
  74. package/dist/templates/chat/expo/shared/log.ts +130 -0
  75. package/dist/templates/chat/expo/shared/offline.js +8 -0
  76. package/dist/templates/chat/expo/shared/offline.ts +9 -0
  77. package/dist/templates/chat/expo/src/Chat.tsx +23 -0
  78. package/dist/templates/chat/expo/src/Composer.tsx +23 -0
  79. package/dist/templates/chat/expo/src/MediaView.tsx +29 -0
  80. package/dist/templates/chat/expo/src/Witness.tsx +131 -0
  81. package/dist/templates/chat/expo/src/screens/Inbox.tsx +9 -0
  82. package/dist/templates/chat/expo/src/screens/Search.tsx +10 -0
  83. package/dist/templates/chat/expo/src/screens/Thread.tsx +22 -0
  84. package/dist/templates/chat/expo/src/witness-state.ts +68 -0
  85. package/dist/templates/chat/expo/tsconfig.json +5 -0
  86. package/dist/templates/chat/react/index.html +5 -0
  87. package/dist/templates/chat/react/shared/log.js +92 -0
  88. package/dist/templates/chat/react/shared/log.ts +130 -0
  89. package/dist/templates/chat/react/shared/offline.js +8 -0
  90. package/dist/templates/chat/react/shared/offline.ts +9 -0
  91. package/dist/templates/chat/react/src/App.tsx +36 -0
  92. package/dist/templates/chat/react/src/Composer.tsx +45 -0
  93. package/dist/templates/chat/react/src/Inbox.tsx +24 -0
  94. package/dist/templates/chat/react/src/Search.tsx +26 -0
  95. package/dist/templates/chat/react/src/Thread.tsx +126 -0
  96. package/dist/templates/chat/react/src/env.d.ts +1 -0
  97. package/dist/templates/chat/react/src/main.tsx +20 -0
  98. package/dist/templates/chat/shared/log.js +92 -0
  99. package/dist/templates/chat/shared/log.ts +130 -0
  100. package/dist/templates/chat/shared/offline.js +8 -0
  101. package/dist/templates/chat/shared/offline.ts +9 -0
  102. package/dist/templates/chat/snapback/deliveries.q +33 -0
  103. package/dist/templates/chat/snapback/delivery.ts +30 -0
  104. package/dist/templates/chat/snapback/feeds.q +14 -0
  105. package/dist/templates/chat/snapback/follows.q +11 -0
  106. package/dist/templates/chat/snapback/groups.q +40 -0
  107. package/dist/templates/chat/snapback/messages.q +32 -0
  108. package/dist/templates/chat/snapback/notifications.q +7 -0
  109. package/dist/templates/chat/snapback/posts.q +4 -0
  110. package/dist/templates/chat/snapback/profiles.q +17 -0
  111. package/dist/templates/chat/snapback/schema.q +136 -0
  112. package/dist/templates/chat/snapback/seed.ts +45 -0
  113. package/dist/templates/chat/snapback/tests/chat.test.ts +759 -0
  114. package/dist/templates/react/index.html +5 -0
  115. package/dist/templates/react/src/App.tsx +5 -0
  116. package/dist/templates/react/src/main.tsx +18 -0
  117. package/dist/templates/todos/snapback/schema.q +11 -0
  118. package/dist/templates/todos/snapback/seed.ts +10 -0
  119. package/dist/templates/todos/snapback/tests/todos.test.ts +13 -0
  120. package/dist/templates/todos/snapback/todos.q +6 -0
  121. package/dist/test-runner.mjs +8541 -0
  122. package/dist/test-runner.mjs.map +7 -0
  123. package/dist/test.d.ts +28 -0
  124. package/dist/test.mjs +8541 -0
  125. package/dist/test.mjs.map +7 -0
  126. package/dist/token-store.d.ts +3 -0
  127. package/dist/twin-hydrate.d.ts +43 -0
  128. package/dist/twin.d.ts +70 -0
  129. package/dist/types.d.ts +410 -0
  130. package/dist/witness-test.mjs +2 -0
  131. package/dist/witness-test.mjs.map +7 -0
  132. package/package.json +117 -0
@@ -0,0 +1,3 @@
1
+ import type { AuthTokenStore } from "./types.js";
2
+ /** Adapts explicitly supplied browser storage; the client never selects storage itself. */
3
+ export declare function browserTokenStore(storage: Pick<Storage, "getItem" | "setItem" | "removeItem">, key: string): AuthTokenStore;
@@ -0,0 +1,43 @@
1
+ import { type EvaluatorClosureReceipt, type TaggedValue } from "./offline-protocol.js";
2
+ import type { Coverage } from "./store/range-store.js";
3
+ /**
4
+ * The evaluator's `ClosureReceipt` for a partition's coverage. Rows keep the
5
+ * pool's order; a range's halo is the pooled row its `haloId` names; `n2Key`
6
+ * never leaves the server, so it is `null` here as it is on the wire. The
7
+ * schema hash is recomputed from the schema, never taken from the caller, so
8
+ * every rebuild proves the hasher against the server's; an expected hash that
9
+ * disagrees refuses the rebuild — coverage delivered under another schema, or
10
+ * a hasher drifted from Rust, is `hydrating`, never evaluated (LLP 1012 §8).
11
+ */
12
+ export declare function closureFromCoverage(coverage: Coverage, schema: any, expectedSchemaHash?: string): EvaluatorClosureReceipt;
13
+ /** Tag one operation argument by the IR's descriptor (`Cursor`, `Optional`, or a column type). */
14
+ export declare function tagArgument(descriptor: unknown, value: unknown, path: string): TaggedValue;
15
+ export declare function tagArguments(descriptors: unknown, args: unknown): Record<string, TaggedValue>;
16
+ export type PartialReason = "never-held" | "evicted" | "computed-key";
17
+ export interface PartialSite {
18
+ site: string;
19
+ reason: PartialReason;
20
+ }
21
+ interface TraceLike {
22
+ ranges: Array<{
23
+ site: string;
24
+ table: string;
25
+ provenance?: string;
26
+ }>;
27
+ absences: Array<{
28
+ site: string;
29
+ table: string;
30
+ index: string;
31
+ key: unknown[];
32
+ provenance?: string;
33
+ }>;
34
+ }
35
+ /**
36
+ * Why a site fell outside coverage: a row a swap evicted (`evicted`: a
37
+ * primary-key read of an evicted row, or a range over a table one of whose
38
+ * held pages lost a member), a key the program computed from another row so
39
+ * no page could have named it (`computed-key`), or a read the store simply
40
+ * never held (`never-held`).
41
+ */
42
+ export declare function partialReasons(sites: string[], trace: TraceLike, coverage: Coverage): PartialSite[];
43
+ export {};
package/dist/twin.d.ts ADDED
@@ -0,0 +1,70 @@
1
+ import { type PartialSite } from "./twin-hydrate.js";
2
+ import type { Coverage, HeldProgram, OutboxEntry, OutboxTrace, OverlayRow } from "./store/range-store.js";
3
+ export interface TwinSources {
4
+ /** The certified program held for this partition, if any. */
5
+ program(): HeldProgram | undefined;
6
+ coverage(): Promise<Coverage | undefined>;
7
+ overlay(): Promise<Record<string, OverlayRow[]>>;
8
+ /** Every durable intent, FIFO; overlays are layered in this order. */
9
+ entries(): Promise<OutboxEntry[]>;
10
+ principal(): string | undefined;
11
+ now(): number;
12
+ seq(): number;
13
+ }
14
+ /** A query the twin answered locally, in the wire's shape plus its offline facts. */
15
+ export interface LocalAnswer {
16
+ state: "complete" | "capped" | "partial" | "denied";
17
+ data?: unknown;
18
+ next?: string | null;
19
+ seq: number;
20
+ reason?: string;
21
+ rule?: string;
22
+ /** The first uncovered site (`partial`). */
23
+ site?: string;
24
+ /** Every uncovered site with its reason (`partial`). */
25
+ sites?: PartialSite[];
26
+ /** Queued intents whose predicted rows are visible in `data`, in order. */
27
+ predicted?: string[];
28
+ /** The newest delivery the answer was computed from. */
29
+ since?: number;
30
+ }
31
+ export interface Prediction {
32
+ predicted: boolean;
33
+ overlay: OverlayRow[];
34
+ trace: OutboxTrace;
35
+ /** Range reads evaluated over held rows only; ranges re-execute fresh at commit and are never validated. */
36
+ partialRanges: string[];
37
+ /** The point reads or rule inputs outside coverage that stopped the prediction. */
38
+ uncovered: PartialSite[];
39
+ /**
40
+ * Engine-owned aggregate targets (`maintain … count`) this write updates on the
41
+ * server but the overlay omits, because the store does not hold them: named so
42
+ * a reader can tell an intentionally omitted aggregate from a lost write.
43
+ */
44
+ omitted: string[];
45
+ }
46
+ /** Debug seam for the witness: why the twin last declined to evaluate (never part of a result). */
47
+ export declare function twinLastDecline(): string | undefined;
48
+ export declare class Twin {
49
+ private readonly sources;
50
+ constructor(sources: TwinSources);
51
+ /**
52
+ * The certified program run over coverage. `undefined` when no program is
53
+ * held, when the coverage was delivered under another generation or schema
54
+ * (the successor fence: a predecessor's program never evaluates into current
55
+ * state), or when the operation is not a query the program knows.
56
+ */
57
+ evaluate(op: string, args: unknown): Promise<LocalAnswer | undefined>;
58
+ /**
59
+ * The mutation run over held rows with the intent's pinned ids as its `new`
60
+ * envelope, so the prediction and the commit mint the same ids. Predicted
61
+ * when every point read and rule input was covered; a range the store does
62
+ * not hold whole is evaluated over the rows it has and named in
63
+ * `partialRanges` — ranges re-execute at commit and are never validated. An
64
+ * uncovered point or rule input, or a local refusal, leaves the intent
65
+ * queued without a prediction and with an empty trace: the server judges.
66
+ */
67
+ predict(op: string, args: unknown, ids: string[]): Promise<Prediction>;
68
+ private context;
69
+ private hydrate;
70
+ }
@@ -0,0 +1,410 @@
1
+ import type { AssetFetchOptions } from "./store/byte-cache.js";
2
+ import type { Channel as ChannelRef, Op } from "./api.js";
3
+ import type { AssetCache, ByteCacheEntry, ByteCacheStatus, ViewOncePredicate } from "./store/byte-cache.js";
4
+ import type { OutboxEntry, RangeStoreFactory, RowRef, StoredRow } from "./store/range-store.js";
5
+ export type { OutboxEntry, OutboxGrant, OutboxState, OutboxTrace } from "./store/range-store.js";
6
+ export type { AssetCache, ByteCacheEntry, ByteCacheStatus, ViewOncePredicate };
7
+ export type ImageAsset = {
8
+ readonly __asset: "image";
9
+ readonly id: string;
10
+ readonly width: number;
11
+ readonly height: number;
12
+ };
13
+ export type VideoAsset = {
14
+ readonly __asset: "video";
15
+ readonly id: string;
16
+ readonly width: number;
17
+ readonly height: number;
18
+ readonly duration: number;
19
+ };
20
+ /** Server-minted authority subject; structurally compatible with `snapback2.Principal`. */
21
+ export type Principal = string & {
22
+ readonly __snapbackPrincipal: "Principal";
23
+ };
24
+ export type Cursor = string & {
25
+ readonly __snapbackCursor: "Cursor";
26
+ };
27
+ export interface RefusalEnvelope {
28
+ code: string;
29
+ family: string;
30
+ message: string;
31
+ site?: string;
32
+ rule?: string;
33
+ rewrite?: string;
34
+ guide: string;
35
+ /** Offline resume detail identifying the request member that exceeded a cap. */
36
+ which?: string;
37
+ /** Offline resume detail limiting the next bounded request. */
38
+ cap?: number;
39
+ }
40
+ /** Opt-in body fields for a query request that asks for local-first custody. */
41
+ export interface QueryReceiptRequest {
42
+ readonly receipt: true;
43
+ readonly validator?: string;
44
+ }
45
+ /** Opt-in query fields for a subscription request that asks for local-first custody. */
46
+ export interface SubscribeReceiptRequest {
47
+ readonly receipt: true;
48
+ readonly validator?: string;
49
+ }
50
+ export type QueryComplete<Result> = {
51
+ state: "complete";
52
+ data: Result;
53
+ seq: number;
54
+ next: Cursor | null;
55
+ };
56
+ export type QueryCapped<Result> = {
57
+ state: "capped";
58
+ data: Result;
59
+ seq: number;
60
+ next: Cursor | null;
61
+ };
62
+ export type QueryPartial<Result> = {
63
+ state: "partial";
64
+ data?: Result;
65
+ seq: number;
66
+ reason: string;
67
+ };
68
+ export type QueryDenied = {
69
+ state: "denied";
70
+ seq: number;
71
+ rule: string;
72
+ };
73
+ export type ServerQueryResult<Result> = QueryComplete<Result> | QueryCapped<Result> | QueryPartial<Result> | QueryDenied;
74
+ export type OfflineFacts = {
75
+ live: false;
76
+ connection: "offline";
77
+ stale: {
78
+ since: number;
79
+ };
80
+ retained?: boolean;
81
+ caughtUp?: boolean;
82
+ watermark?: Cursor | null;
83
+ /** Queued intents whose predicted rows are inside `data`, in order (the twin, LLP 1012 §7). */
84
+ predicted?: string[];
85
+ /** A local `partial`: the uncovered site and every uncovered site with its reason. */
86
+ site?: string;
87
+ sites?: Array<{
88
+ site: string;
89
+ reason: "never-held" | "evicted" | "computed-key";
90
+ }>;
91
+ };
92
+ export type OfflineQueryResult<Result> = (QueryComplete<Result> & OfflineFacts) | (QueryCapped<Result> & OfflineFacts) | (QueryPartial<Result> & OfflineFacts) | (QueryDenied & OfflineFacts) | {
93
+ state: "hydrating";
94
+ live: false;
95
+ connection: "offline";
96
+ };
97
+ export type QueryResult<Result> = ServerQueryResult<Result> | OfflineQueryResult<Result>;
98
+ export type MutationCommitted<Result> = {
99
+ state: "committed";
100
+ seq: number;
101
+ data: Result;
102
+ intent?: string;
103
+ };
104
+ export type MutationRejected = {
105
+ state: "rejected";
106
+ code: string;
107
+ family: string;
108
+ message: string;
109
+ retryable?: boolean;
110
+ rewrite?: string;
111
+ site?: string;
112
+ rule?: string;
113
+ guide: string;
114
+ /** `conflict` when trace validation rolled a replayed write back (LLP 1012 §7). */
115
+ reason?: string;
116
+ intent?: string;
117
+ };
118
+ /** The write is durable in this device's outbox and will replay after the next resume. */
119
+ export type MutationQueued = {
120
+ state: "queued";
121
+ intent: string;
122
+ ids: string[];
123
+ };
124
+ export type MutationResult<Result> = MutationCommitted<Result> | MutationRejected | MutationQueued;
125
+ export type LiveQueryDelivery<Result> = ServerQueryResult<Result> & {
126
+ live: true;
127
+ caughtUp?: boolean;
128
+ watermark?: Cursor | null;
129
+ };
130
+ export type StaleDelivery<Result> = {
131
+ state: "stale";
132
+ live: false;
133
+ since: number;
134
+ data?: Result;
135
+ seq?: number;
136
+ };
137
+ export type RefusedDelivery = {
138
+ state: "refused";
139
+ code: string;
140
+ family: string;
141
+ message: string;
142
+ site?: string;
143
+ rule?: string;
144
+ rewrite?: string;
145
+ guide: string;
146
+ seq?: number;
147
+ };
148
+ export type QueryRefusedDelivery = RefusedDelivery & {
149
+ live: false;
150
+ };
151
+ export type SubscriptionDelivery<Result> = LiveQueryDelivery<Result> | (ServerQueryResult<Result> & {
152
+ live: false;
153
+ caughtUp?: boolean;
154
+ watermark?: Cursor | null;
155
+ }) | OfflineQueryResult<Result> | StaleDelivery<Result> | QueryRefusedDelivery;
156
+ export interface Subscription<Event> {
157
+ close(): void;
158
+ until(predicate: (event: Event) => boolean, timeoutMs?: number): Promise<Event>;
159
+ }
160
+ export interface AuthSession {
161
+ token: string;
162
+ viewer: Principal;
163
+ }
164
+ export interface AuthMe {
165
+ viewer: Principal;
166
+ kind: string;
167
+ }
168
+ export interface AuthApi {
169
+ signup(input: {
170
+ email: string;
171
+ password: string;
172
+ }): Promise<AuthSession>;
173
+ login(input: {
174
+ email: string;
175
+ password: string;
176
+ }): Promise<AuthSession>;
177
+ guest(): Promise<AuthSession>;
178
+ logout(): Promise<{
179
+ ok: boolean;
180
+ }>;
181
+ me(): Promise<AuthMe>;
182
+ }
183
+ export interface ChannelEvent<Payload = unknown> {
184
+ from: Principal;
185
+ payload: Payload;
186
+ at: number;
187
+ }
188
+ export type ChannelDelivery<Payload = unknown> = ChannelEvent<Payload> | RefusedDelivery;
189
+ export type ChannelPublishResult = {
190
+ ok: true;
191
+ } | ({
192
+ state: "refused";
193
+ } & RefusalEnvelope);
194
+ export interface ChannelHandle<Payload = unknown> {
195
+ publish(payload: Payload): Promise<ChannelPublishResult>;
196
+ subscribe(callback: (event: ChannelDelivery<Payload>) => void): Subscription<ChannelDelivery<Payload>>;
197
+ }
198
+ export interface ManifestOperation {
199
+ kind: string;
200
+ args: unknown;
201
+ planHash: string;
202
+ }
203
+ export interface ManifestTable {
204
+ retain?: "until-revoked" | "delivered-history";
205
+ [key: string]: unknown;
206
+ }
207
+ export interface Manifest {
208
+ generation: string;
209
+ programGeneration: string;
210
+ ops: Record<string, ManifestOperation>;
211
+ channels: Record<string, unknown>;
212
+ auth: Record<string, unknown>;
213
+ schemaHash?: string;
214
+ tables?: Record<string, ManifestTable>;
215
+ schema?: {
216
+ tables?: Record<string, ManifestTable>;
217
+ [key: string]: unknown;
218
+ };
219
+ }
220
+ export type AssetRecord = ImageAsset | VideoAsset;
221
+ export interface NativeAssetSource {
222
+ readonly uri: string;
223
+ readonly size?: number;
224
+ readonly fileSize?: number;
225
+ readonly type?: string;
226
+ readonly mimeType?: string;
227
+ }
228
+ export type AssetUploadSource = File | NativeAssetSource;
229
+ export interface UploadProgress {
230
+ readonly loaded: number;
231
+ readonly total: number;
232
+ }
233
+ export type AssetKind = "image" | "video";
234
+ export interface AssetUploadOptions {
235
+ /**
236
+ * The nominal record expected back. The server sniffs the bytes and decides the
237
+ * kind; when `kind` is given the client verifies the returned record and refuses
238
+ * `E_ASSET_TYPE` on a mismatch, so the result can flow straight into a generated
239
+ * `image`/`video` argument (LLP 1006 §10). Without it the result is the union.
240
+ */
241
+ readonly kind?: AssetKind;
242
+ /**
243
+ * Reports streamed byte progress where the runtime supports request streams.
244
+ * Expo native File uploads use expo/fetch's direct File body and report only
245
+ * whole-body start and successful completion (0, then total).
246
+ */
247
+ readonly onProgress?: (progress: UploadProgress) => void;
248
+ }
249
+ export interface AssetUrlOptions {
250
+ readonly width?: number;
251
+ readonly carrier?: boolean;
252
+ readonly download?: boolean;
253
+ }
254
+ /** Replaces generated asset leaves with the browser/native upload convenience accepted by the SDK. */
255
+ export type AssetInput<Value> = Value extends VideoAsset ? Value | File | NativeAssetSource : Value extends ImageAsset ? Value | File | NativeAssetSource : Value extends File ? Value : Value extends readonly (infer Item)[] ? AssetInput<Item>[] : Value extends object ? {
256
+ [Key in keyof Value]: AssetInput<Value[Key]>;
257
+ } : Value;
258
+ export type ConnectionState = "connected" | "reconnecting" | "offline";
259
+ export interface ClientSnapshot {
260
+ /** Client-local cache/transport partition; never a server bearer or epoch. */
261
+ authGeneration: number;
262
+ token?: string;
263
+ persona?: string;
264
+ connection: ConnectionState;
265
+ generation?: string;
266
+ lastRefusal?: RefusalEnvelope;
267
+ }
268
+ export interface AuthTokenStore {
269
+ /** Restores the bearer before `client.ready` settles. */
270
+ load(): string | null | undefined | Promise<string | null | undefined>;
271
+ /** Persists a bearer, or clears custody with `null`. */
272
+ save(token: string | null): void | Promise<void>;
273
+ /** Optional companion custody needed to reopen a per-principal offline partition before network I/O. */
274
+ loadPrincipal?(): string | null | undefined | Promise<string | null | undefined>;
275
+ savePrincipal?(principal: string | null): void | Promise<void>;
276
+ }
277
+ interface ClientLocationOptions {
278
+ url: string;
279
+ fetch?: typeof globalThis.fetch;
280
+ offline?: OfflineClientOptions;
281
+ }
282
+ export interface OfflineClientOptions {
283
+ store: RangeStoreFactory;
284
+ fenceDays?: number;
285
+ /** Ceiling on cached asset bytes per partition, LRU by each rendition's own last read; default 256 MiB. */
286
+ maxAssetBytes?: number;
287
+ onTombstone?: (row: StoredRow) => RowRef[];
288
+ /**
289
+ * Names the rows whose media renders once: a viewed row is excluded from every
290
+ * later offline answer and its bytes are never cached (LLP 1012 §9). This stands
291
+ * in for LLP 1006's one-use delivery until that lands on the server.
292
+ */
293
+ viewOnce?: ViewOncePredicate;
294
+ }
295
+ /** A client has exactly one authority source: a dev persona or bearer custody. */
296
+ export type CreateClientOptions = ClientLocationOptions & ({
297
+ as: string;
298
+ token?: never;
299
+ tokenStore?: never;
300
+ } | {
301
+ as?: never;
302
+ token?: string;
303
+ tokenStore?: AuthTokenStore;
304
+ });
305
+ export type OutboxSettlement = void | {
306
+ stopped: "fence";
307
+ };
308
+ export interface OutboxApi {
309
+ /** Every durable intent of the open partition, FIFO; empty without a store. */
310
+ entries(): Promise<OutboxEntry[]>;
311
+ /** Drops one settled entry; queued intents cannot be dismissed. */
312
+ dismiss(intent: string): Promise<void>;
313
+ /** Observes the outbox; called with the current entries at once and after every change. */
314
+ subscribe(listener: (entries: OutboxEntry[]) => void): () => void;
315
+ /**
316
+ * Without an op, true when a write will not refuse `E_OFFLINE_ID_BLOCK` for
317
+ * want of a replay grant: no partition is open (today's bytes), or the open
318
+ * partition holds a current, unexpired grant — minted by a login, a guest
319
+ * call, or the first drain reaching its watermark. The fence gates replay and
320
+ * prediction, not this (see `fence`). With an op, also means this write would
321
+ * not refuse `E_OFFLINE_ID_BLOCK` for want of a known bound or capacity for
322
+ * `1 + maxNewIds`. Grant, bound, fence, and expiry changes are observable
323
+ * through `subscribe` even when the entries are unchanged (LLP 1012 §7).
324
+ */
325
+ ready<Args = unknown, Result = unknown>(op?: Op<Args, Result>): Promise<boolean>;
326
+ /** Past means replay waits for this partition's successful resume; enqueue remains ready. */
327
+ fence(): Promise<"inside" | "past">;
328
+ /** Replays FIFO; a fence stop returns { stopped: "fence" } with the intents still queued. */
329
+ drain(): Promise<OutboxSettlement>;
330
+ /**
331
+ * Resolves once the in-flight replay, if any, emptied the queue or a retryable
332
+ * transport failure stopped it; returns { stopped: "fence" } if resume is
333
+ * required, and rejects with a non-retryable refusal that stopped it.
334
+ */
335
+ settled(): Promise<OutboxSettlement>;
336
+ }
337
+ export interface SnapbackClient {
338
+ readonly auth: AuthApi;
339
+ readonly outbox: OutboxApi;
340
+ /** Settles only after explicit token restoration and any initial persistence. */
341
+ readonly ready: Promise<void>;
342
+ readonly snapshot: ClientSnapshot;
343
+ query<Args, Result>(ref: Op<Args, Result>, args: NoInfer<Args>): Promise<QueryResult<Result>>;
344
+ mutation<Args, Result>(ref: Op<Args, Result>, args: NoInfer<AssetInput<Args>>): Promise<MutationResult<Result>>;
345
+ /** Uploads one browser File or Expo URI-backed File and returns its closed asset record. */
346
+ upload(source: AssetUploadSource, options: AssetUploadOptions & {
347
+ readonly kind: "image";
348
+ }): Promise<ImageAsset>;
349
+ upload(source: AssetUploadSource, options: AssetUploadOptions & {
350
+ readonly kind: "video";
351
+ }): Promise<VideoAsset>;
352
+ upload(source: AssetUploadSource, options?: AssetUploadOptions): Promise<AssetRecord>;
353
+ assetUrl(asset: AssetRecord | null | undefined, options?: AssetUrlOptions): Promise<string | undefined>;
354
+ assetUrl(assets: readonly AssetRecord[], options?: AssetUrlOptions): Promise<string[]>;
355
+ /** Authentication headers for a native image source; carriers stay reserved for players/downloads. */
356
+ assetHeaders(): Promise<Record<string, string>>;
357
+ /** Fetches a client-issued asset URL: the byte cache under the row's lease first, then the network with the header credential. */
358
+ assetBlob(url: string, options?: AssetFetchOptions): Promise<Blob>;
359
+ /** Native byte path: cache first, then an authenticated ArrayBuffer response; no Blob. */
360
+ assetBytes(url: string, options?: AssetFetchOptions): Promise<import("./store/byte-cache.js").AssetBytes>;
361
+ /** The partition's byte cache when the client has an offline store; components read it before the network. */
362
+ readonly assetCache: AssetCache | undefined;
363
+ /** Delivers validated query receipt states with `live: true`; transport loss is client-made `stale`. */
364
+ subscribe<Args, Result>(ref: Op<Args, Result>, args: NoInfer<Args>, callback: (delivery: SubscriptionDelivery<Result>) => void, options?: {
365
+ drain?: boolean;
366
+ }): Subscription<SubscriptionDelivery<Result>>;
367
+ channel<Args, Payload = unknown>(ref: ChannelRef<Args, Payload>, args: NoInfer<Args>): ChannelHandle<Payload>;
368
+ manifest(): Promise<Manifest>;
369
+ subscribeStatus(callback: (snapshot: ClientSnapshot) => void): () => void;
370
+ /** Cut only this client's transport; the owner remains running. */
371
+ offline(): Promise<void>;
372
+ /** Restore transport and perform resume before new subscription opens. */
373
+ online(): Promise<void>;
374
+ /** Close and reopen this logical device on the same durable partition. */
375
+ restart(): Promise<SnapbackClient>;
376
+ /** Resolves when the offline carrier is quiet; safe to remove the store after. */
377
+ close(): Promise<void>;
378
+ }
379
+ type ViewFacts = {
380
+ live: boolean;
381
+ stale?: {
382
+ since: number;
383
+ };
384
+ retained?: boolean;
385
+ caughtUp?: boolean;
386
+ watermark?: Cursor | null;
387
+ predicted?: string[];
388
+ };
389
+ export type QueryView<Result> = (QueryComplete<Result> & ViewFacts) | (QueryCapped<Result> & ViewFacts) | (QueryPartial<Result> & ViewFacts) | (QueryDenied & ViewFacts) | {
390
+ state: "refused";
391
+ live: false;
392
+ refusal: RefusalEnvelope;
393
+ } | {
394
+ state: "stale";
395
+ live: false;
396
+ data?: Result;
397
+ seq?: number;
398
+ since: number;
399
+ } | {
400
+ state: "hydrating";
401
+ live: false;
402
+ };
403
+ export type QueryViewState = QueryView<unknown>["state"];
404
+ export type QueryHookResult<Result> = QueryView<Result> & {
405
+ connection: ConnectionState;
406
+ };
407
+ export type QueryMachineEvent<Result> = QueryResult<Result> | SubscriptionDelivery<Result>;
408
+ export declare function initialQueryState<Result>(): QueryView<Result>;
409
+ export declare function stableSerialize(value: unknown): string;
410
+ export declare function reduceQueryState<Result>(current: QueryView<Result>, event: QueryMachineEvent<Result>): QueryView<Result>;
@@ -0,0 +1,2 @@
1
+ var Le=Object.defineProperty;var y=(t,e)=>()=>(t&&(e=t(t=0)),e);var Be=(t,e)=>{for(var n in e)Le(t,n,{get:e[n],enumerable:!0})};import ne,{useCallback as R}from"react";import{AssetImage as Ve,AssetVideo as Me}from"snapback2/react-native";function W({media:t}){let e=R((o,i)=>{t.uri=o,t.hidden&&i&&t.resolve({loaded:t.loaded,uri:o})},[t]),n=R(o=>{let i=t.uri;if(!i){t.resolve({loaded:!1});return}o&&o!==i||(t.loaded=!0,(t.once?i.startsWith("data:"):i.startsWith("file://"))&&t.resolve({loaded:!0,uri:i,..."duration"in t.asset?{playing:!0}:{}}))},[t]),r=R(o=>t.resolve({loaded:!1,refusal:o.envelope}),[t]);return"duration"in t.asset?ne.createElement(Me,{asset:t.asset,width:64,autoPlay:!t.hidden,testID:"witness-media",onSourceChange:e,onPlaybackStart:n,onRefusal:r}):ne.createElement(Ve,{asset:t.asset,width:64,testID:"witness-media",onSourceChange:e,onLoad:o=>n(o?.nativeEvent?.source?.uri),onRefusal:r})}var U=y(()=>{"use strict"});import{Directory as j,File as H,Paths as re}from"expo-file-system";import{expoSqliteDriver as Ne}from"snapback2/expo";function ae(){let t=ie();return t.exists?t.list().filter(e=>e instanceof H&&/\/\d{16}\.json$/.test(e.uri)).sort((e,n)=>e.uri.localeCompare(n.uri)):[]}async function O(){let t=ae().at(-1);return t?JSON.parse(await t.text()):qe()}function S(t){let e=oe.then(async()=>{let n=ie();n.create({intermediates:!0,idempotent:!0});let r=ae(),o=r.at(-1),i=o?Number(o.uri.match(/(\d{16})\.json$/)[1])+1:1,p=new H(n,"next.tmp");p.write(JSON.stringify({...await O(),...t})),p.move(new H(n,`${String(i).padStart(16,"0")}.json`));for(let x of r)x.delete()});return oe=e.catch(()=>{}),e}function v(t=new j(re.cache,"snapback2")){return t.exists?t.list().flatMap(e=>e instanceof j?v(e):[e.uri]):[]}async function Q(){let t=Ne(q),e={};try{for(let n of["state","rows","answers","outbox","bytes"]){let r=await t.all(`SELECT * FROM ${n} ORDER BY partition${n==="state"?"":", key"}`);e[n]=r.map(o=>Object.fromEntries(Object.entries(o).map(([i,p])=>[i,p instanceof Uint8Array?Array.from(p):p])))}return e}finally{await t.close()}}async function de(t,e,n){if(!n[0])throw new Error("enqueue both messages before the atomicity marker");let r=e==="pre"?11:22,o={deliveries:[{op:"deliveries.thread",args:{conversationId:"witness-marker",c:null},seq:e==="pre"?1:2,state:"complete",next:null,data:{$row:{table:"messages",id:"witness-marker"}},rows:[{table:"messages",id:"witness-marker",row:{id:"witness-marker",conversationId:"witness-marker",authorId:"witness",body:e,photo:{id:se,width:1,height:1},at:1}}],receipt:{ranges:[],absences:[],verdicts:[]}}],outbox:{put:[{...n[0],intent:"__witness",state:"committed",op:"deliveries.post",args:{conversationId:"witness-marker",body:e},predicted:!1}]},bytes:{put:[{assetId:se,width:null,type:`witness/${e}`,bytes:new Uint8Array([r])}]}};await t.commit(o)}var q,ie,qe,oe,se,z=y(()=>{"use strict";q="snapback2-witness.db",ie=()=>new j(re.document,"snapback2-witness-config"),qe=()=>({offline:!1,fault:"none",name:"alice",once:[],assets:[]});oe=Promise.resolve();se="s2id-eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-0000000000000001"});function ce(t){let e=new Map;for(let n of t){let r=e.get(n.conversationId)??{conversationId:n.conversationId,latestAt:n.at,latestBody:void 0,messages:0};n.kind==="message"&&(r.messages+=1),n.at>=r.latestAt&&(r.latestAt=n.at,r.latestBody=n.message?.body??r.latestBody),e.set(n.conversationId,r)}return[...e.values()].sort((n,r)=>r.latestAt-n.latestAt)}function J(t){let e=t.args;return t.op==="deliveries.post"&&e&&typeof e.conversationId=="string"&&typeof e.body=="string"?{conversationId:e.conversationId,body:e.body}:void 0}function De(t){let e=t.args;return t.op==="deliveries.edit"&&e&&typeof e.messageId=="string"&&typeof e.body=="string"?{messageId:e.messageId,body:e.body,baseBody:e.baseBody??""}:void 0}function le(t){switch(t.state){case"queued":return t.reason==="fence"?"Will be checked by the server":"Sending\u2026";case"committed":return"Sent";case"expired":return"Expired";case"rejected":{let e=t.response;return e?.reason==="conflict"||e?.code==="CONFLICT"?"Not sent \xB7 Message changed":`Not sent \xB7 ${e?.rewrite??e?.message??e?.code??"refused"}`}}}function D(t,e,n){let r=new Set(t.filter(s=>s.kind==="removed"||s.kind==="expired").map(s=>s.messageId)),o=new Map(e.map(s=>[De(s)?.messageId,s]).filter(([s])=>s!==void 0)),i=new Set,p=[],x=new Map(e.filter(s=>s.state==="queued"&&J(s)!==void 0).map(s=>[s.ids[0]??s.intent,s])),f=[...t].sort((s,m)=>s.at-m.at||(s.messageId<m.messageId?-1:s.messageId>m.messageId?1:0));for(let s of f){if(s.kind!=="message"||!s.message||r.has(s.messageId)||i.has(s.messageId))continue;i.add(s.messageId);let m=x.get(s.messageId);if(m){p.push({key:s.messageId,kind:"pending",entry:m,body:J(m).body});continue}let w=o.get(s.messageId);p.push({key:s.messageId,kind:"row",message:s.message,at:s.at,...w?{edit:w}:{}})}for(let s of e){let m=J(s);if(!m||m.conversationId!==n)continue;let w=s.ids[0]??s.intent;i.has(w)||p.push({key:w,kind:"pending",entry:s,body:m.body})}return p}function $(t,e){let n=e.trim().toLowerCase();if(!n)return[];let r=new Set;return t.filter(o=>{let i=o.message?.body;return o.kind!=="message"||!i||r.has(o.messageId)||!i.toLowerCase().includes(n)?!1:(r.add(o.messageId),!0)})}var P=y(()=>{});import L from"react";import{Pressable as $e,Text as K,View as _e}from"react-native";function ue({rows:t,onSelect:e}){return L.createElement(_e,{accessibilityLabel:"Inbox"},ce(t).map(n=>L.createElement($e,{key:n.conversationId,onPress:()=>e(n.conversationId),style:{paddingVertical:16}},L.createElement(K,{style:{fontWeight:"600"}},n.conversationId),L.createElement(K,null,n.latestBody??`${n.messages} messages`))),t.length===0?L.createElement(K,null,"No downloaded messages yet."):null)}var fe=y(()=>{"use strict";P()});import k,{useState as pe}from"react";import{Button as Fe,Text as X,TextInput as Re,View as We}from"react-native";import{api as Ue,isSnapbackRefusal as je}from"snapback2/client";import{useMutation as He,useOutbox as Qe}from"snapback2/react-native";function me({conversationId:t}){let e=He(Ue.deliveries.post),{fence:n}=Qe(),[r,o]=pe(""),[i,p]=pe(""),x=async()=>{if(!(!r.trim()||!e.ready))try{let f=await e.run({conversationId:t,body:r.trim()});f.state==="rejected"?p(f.rewrite??f.message):(o(""),p(f.state==="queued"?"Queued until the next sync.":""))}catch(f){p(je(f)?f.envelope.rewrite??f.message:String(f))}};return k.createElement(We,{style:{padding:16}},k.createElement(Re,{accessibilityLabel:"Message",placeholder:"Message",value:r,onChangeText:o,multiline:!0}),k.createElement(Fe,{title:"Send",disabled:!e.ready||e.inFlight||!r.trim(),onPress:()=>{x()}}),e.ready?n==="past"?k.createElement(X,null,"Queued until the next sync."):null:k.createElement(X,null,"Sending opens once the log has downloaded."),i?k.createElement(X,{accessibilityRole:"alert"},i):null)}var ge=y(()=>{"use strict"});import g from"react";import{ScrollView as ze,Text as I,View as we}from"react-native";import{api as Je}from"snapback2/client";import{AssetImage as Ke,AssetVideo as Xe,useOutbox as Ye,useQuery as Ge}from"snapback2/react-native";function ye({conversationId:t}){let e=Ge(Je.deliveries.thread,{conversationId:t,c:null}),{entries:n}=Ye(),r="data"in e&&Array.isArray(e.data)?e.data:[];return g.createElement(we,{style:{flex:1}},g.createElement(ze,{contentContainerStyle:{padding:16}},e.state==="partial"?g.createElement(I,null,"Some messages are not downloaded."):null,e.state==="refused"?g.createElement(I,null,e.refusal.rewrite??e.refusal.message):null,D(r,n,t).map(o=>g.createElement(we,{key:o.key,testID:`message-${o.key}`,style:{padding:12,marginVertical:4,backgroundColor:"#e7edff",borderRadius:12}},o.kind==="pending"?g.createElement(g.Fragment,null,g.createElement(I,null,o.body),g.createElement(I,null,le(o.entry))):g.createElement(g.Fragment,null,g.createElement(I,null,o.message.body),o.message.photo?g.createElement(Ke,{asset:o.message.photo,width:240}):null,o.message.clip?g.createElement(Xe,{asset:o.message.clip,width:240}):null,g.createElement(I,null,new Date(o.at).toLocaleTimeString()))))),g.createElement(me,{conversationId:t}))}var be=y(()=>{"use strict";P();ge()});import B,{useState as Ze}from"react";import{Pressable as et,Text as xe,TextInput as tt,View as nt}from"react-native";function he({rows:t,onOpen:e}){let[n,r]=Ze("");return B.createElement(nt,null,B.createElement(tt,{accessibilityLabel:"Search downloaded messages",placeholder:"Search",value:n,onChangeText:r}),n?B.createElement(xe,null,"in downloaded messages"):null,$(t,n).map(o=>B.createElement(et,{key:o.messageId,onPress:()=>e(o.conversationId)},B.createElement(xe,null,o.message?.body))))}var ve=y(()=>{"use strict";P()});import b,{useState as ot}from"react";import{SafeAreaView as st,ScrollView as rt,Text as Se,Button as it}from"react-native";import{api as at}from"snapback2/client";import{useConnection as dt,useQuery as ct}from"snapback2/react-native";function _(){let t=dt(),e=ct(at.deliveries.inbox,{c:null},{drain:!0}),n="data"in e&&Array.isArray(e.data)?e.data:[],[r,o]=ot();return b.createElement(st,{style:{flex:1,backgroundColor:"#f7f7fb"}},b.createElement(Se,{style:{fontSize:24,padding:16}},"Snapback Chat \xB7 ",t),r?b.createElement(b.Fragment,null,b.createElement(it,{title:"Inbox",onPress:()=>o(void 0)}),b.createElement(ye,{conversationId:r})):b.createElement(rt,{contentContainerStyle:{padding:16}},b.createElement(he,{rows:n,onOpen:o}),b.createElement(ue,{rows:n,onSelect:o}),e.state==="refused"?b.createElement(Se,null,e.refusal.rewrite??e.refusal.message):null))}var Y=y(()=>{"use strict";fe();be();ve()});var V,G=y(()=>{V={onTombstone:t=>t.table==="deliveries"&&(t.row.kind==="removed"||t.row.kind==="expired")&&typeof t.row.messageId=="string"?[{table:"messages",id:t.row.messageId}]:[],viewOnce:(t,e)=>t==="messages"&&e.viewOnce===!0}});var Ae={};Be(Ae,{default:()=>Ie});import{twinLastDecline as lt}from"snapback2/expo/witness";import M,{useEffect as ut,useState as Z}from"react";import{Text as ft,View as pt}from"react-native";import{api as A,SnapbackRefusal as mt}from"snapback2/client";import{createExpoClient as gt,expoSqliteStore as ke,expoSqliteDriver as wt,SnapbackProvider as yt}from"snapback2/expo";import{witnessControl as bt,runSqliteDriverConformance as xt}from"snapback2/expo/witness";function Ie({url:t,controlUrl:e}){let[n,r]=Z(),[o,i]=Z(),[p,x]=Z();return ut(()=>{let f,s,m="conversation-1",w=[],C,Ee=0,te=async()=>{for(let d of w)d.close();w=[],await f?.close(),f=void 0,r(void 0)},Oe=bt({construct:async(d,u)=>{if(await te(),u.fresh===!0){let a=ke(q)();try{await a.wipe("dev:alice")}finally{await a.close?.()}await S({name:"alice",offline:!1,fault:"none"})}typeof u.name=="string"&&await S({name:u.name}),(Array.isArray(u.once)||Array.isArray(u.assets))&&await S({...Array.isArray(u.once)?{once:u.once}:{},...Array.isArray(u.assets)?{assets:u.assets}:{}});let c=await O();s=ke(q,{beforeCommit:d.beforeCommit})(),c.fault==="carrier"&&(s.open=async()=>{throw new mt({code:"E_OFFLINE_CARRIER",family:"offline",site:"witness construct",message:"persisted witness carrier fault",rewrite:"send fault(none), then construct",guide:"offline"})}),f=gt({url:t,as:c.name,startOffline:c.offline,offline:{...V,store:()=>s,viewOnce:(a,l)=>V.viewOnce(a,l)||a==="messages"&&c.once.includes(String(l.id))}});try{return await f.ready,r(f),x(void 0),f}catch(a){throw x(String(a)),a}},persist:S,async command(d,u,c){if(u==="stores")return{stores:await Q(),files:v()};if(!d||!s)throw new Error("construct first");if(u==="drain"){let a=d.subscribe(A.deliveries.inbox,{c:null},()=>{},{drain:!0});return w.push(a),a.until(l=>"caughtUp"in l&&l.caughtUp===!0)}if(u==="open"){m=String(c.conversationId);let a=d.subscribe(A.deliveries.thread,{conversationId:m,c:null},()=>{});return w.push(a),await a.until(l=>l.state==="complete"),await d.query(A.messages.thread,{conversationId:m,c:null}),d.query(A.deliveries.thread,{conversationId:m,c:null})}if(u==="send")return d.mutation(A.deliveries.post,{conversationId:typeof c.conversationId=="string"?c.conversationId:m,body:String(c.body)});if(u==="view"){let l=(await O()).assets.find(h=>h.id===c.assetId);if(!l)throw new Error("unknown witness asset");let T=!!(await d.assetCache?.viewOnce(l.id)||await d.assetCache?.viewed(l.id)),N=await new Promise(h=>{C={asset:l,nonce:++Ee,once:T,hidden:c.hidden===!0,loaded:!1,resolve:F=>h(F)},i(C)});if(!T&&N.loaded===!0){let h=E=>decodeURIComponent(E).replace(/\/+$/,""),F=h(String(N.uri)),Pe=Date.now()+1e4;for(;!v().some(E=>h(E)===F);){if(Date.now()>=Pe)throw new Error(`ordinary media did not materialize a file: served ${N.uri}; listed ${JSON.stringify(v().slice(0,6))}`);await new Promise(E=>setTimeout(E,10))}}return{...N,viewed:await d.assetCache?.viewed(l.id),files:v().filter(h=>h.includes(l.id))}}if(u==="swap"){if(c.phase!=="pre"&&c.phase!=="post")throw new Error("swap needs pre or post");return await de(s,c.phase,await d.outbox.entries()),{phase:c.phase}}if(u==="conformance")return xt(()=>wt("snapback2-conformance.db"));if(u==="settled"){await d.outbox.settled(),(await d.outbox.entries()).some(l=>l.state==="queued")&&(await d.outbox.drain(),await d.outbox.settled());let a=await d.outbox.entries();if(a.some(l=>l.state==="queued"))throw new Error("outbox did not settle");return a}if(u==="state"){let a=typeof c.conversationId=="string"?await d.query(A.deliveries.thread,{conversationId:c.conversationId,c:null}):void 0,l=await d.outbox.entries(),T=a&&"data"in a&&Array.isArray(a.data)?a.data:[];return{snapshot:d.snapshot,coverage:await s.coverage(),outbox:l,bubbles:typeof c.conversationId=="string"?D(T,l,c.conversationId):[],search:typeof c.search=="string"?$(T,c.search):[],answer:a,twin:lt(),stores:await Q(),files:v(),media:C?{loaded:C.loaded,uri:C.uri}:null}}throw new Error(`unknown witness command ${u}`)}},e);return()=>{Oe.close(),te()}},[t,e]),n?M.createElement(yt,{client:n},M.createElement(pt,{style:{flex:1}},M.createElement(_,null),o?M.createElement(W,{key:o.nonce,media:o}):null)):M.createElement(ft,null,p??"Witness connected; waiting for construct.")}var Ce=y(()=>{"use strict";U();G();P();z();Y()});U();z();Y();G();import ee,{lazy as ht,Suspense as An,useEffect as vt,useState as Te}from"react";import{createExpoClient as St,SnapbackProvider as kt}from"snapback2/expo";import{Text as It}from"react-native";var At=process.env.EXPO_PUBLIC_SNAPBACK_URL??"http://127.0.0.1:3210",Ct=process.env.EXPO_PUBLIC_SNAPBACK_WITNESS,Pn=Ct?ht(()=>Promise.resolve().then(()=>(Ce(),Ae))):void 0;function Tt(){let[t,e]=Te(),[n,r]=Te();return vt(()=>{let o=St({url:At,as:"alice",offline:V});return e(o),o.ready.catch(i=>r(String(i))),()=>{o.close()}},[]),!t||n?ee.createElement(It,{accessibilityLiveRegion:"polite"},n??"Connecting\u2026"):ee.createElement(kt,{client:t},ee.createElement(_,null))}export{W as MediaView,Tt as NormalApp,O as configuration,S as persist};
2
+ //# sourceMappingURL=witness-test.mjs.map