@marktiderman/genesis-native 0.2.0 → 0.3.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 (87) hide show
  1. package/README.md +354 -10
  2. package/dist/block-C5-noMhB.d.ts +223 -0
  3. package/dist/blocks-a1YQ1Elf.d.ts +1540 -0
  4. package/dist/chunk-65P7RU4V.js +49 -0
  5. package/dist/chunk-65P7RU4V.js.map +1 -0
  6. package/dist/chunk-6BT4PTGH.js +37 -0
  7. package/dist/chunk-6BT4PTGH.js.map +1 -0
  8. package/dist/chunk-75YP72G6.js +374 -0
  9. package/dist/chunk-75YP72G6.js.map +1 -0
  10. package/dist/chunk-NGYNYJPL.js +90 -0
  11. package/dist/chunk-NGYNYJPL.js.map +1 -0
  12. package/dist/chunk-VKTIHNAV.js +95 -0
  13. package/dist/chunk-VKTIHNAV.js.map +1 -0
  14. package/dist/{chunk-MMTPWUWI.js → chunk-VTJDIKFC.js} +9 -6
  15. package/dist/chunk-VTJDIKFC.js.map +1 -0
  16. package/dist/chunk-XE7LC7K6.js +29 -0
  17. package/dist/chunk-XE7LC7K6.js.map +1 -0
  18. package/dist/dev-warnings.d.ts +34 -0
  19. package/dist/dev-warnings.js +15 -0
  20. package/dist/dev-warnings.js.map +1 -0
  21. package/dist/devtools.d.ts +144 -0
  22. package/dist/devtools.js +390 -0
  23. package/dist/devtools.js.map +1 -0
  24. package/dist/index.d.ts +12 -368
  25. package/dist/index.js +88 -191
  26. package/dist/index.js.map +1 -1
  27. package/dist/observability.d.ts +170 -0
  28. package/dist/observability.js +123 -0
  29. package/dist/observability.js.map +1 -0
  30. package/dist/persist.d.ts +1 -1
  31. package/dist/persist.js +1 -1
  32. package/dist/push.d.ts +169 -0
  33. package/dist/push.js +996 -0
  34. package/dist/push.js.map +1 -0
  35. package/dist/{query-DhH3lLFa.d.ts → query-DUmVAfsF.d.ts} +11 -6
  36. package/dist/updates.d.ts +8 -1
  37. package/dist/updates.js +44 -69
  38. package/dist/updates.js.map +1 -1
  39. package/package.json +222 -5
  40. package/src/app.tsx +491 -0
  41. package/src/auth.tsx +176 -0
  42. package/src/blocks.tsx +144 -0
  43. package/src/boot.tsx +188 -0
  44. package/src/dev-warnings.ts +43 -0
  45. package/src/devtools/block.tsx +488 -0
  46. package/src/devtools/gestures.ts +107 -0
  47. package/src/devtools/index.ts +22 -0
  48. package/src/devtools/info.ts +45 -0
  49. package/src/devtools/slots.ts +21 -0
  50. package/src/errors.tsx +195 -0
  51. package/src/held-link.ts +90 -0
  52. package/src/hydration-gate.ts +88 -0
  53. package/src/index.ts +83 -0
  54. package/src/links.ts +140 -0
  55. package/src/nativewind.d.ts +41 -0
  56. package/src/observability/block.tsx +300 -0
  57. package/src/observability/events.ts +89 -0
  58. package/src/observability/index.ts +44 -0
  59. package/src/observability/scrub.ts +166 -0
  60. package/src/observability/sentry.ts +222 -0
  61. package/src/observability/sink.ts +147 -0
  62. package/src/persist.ts +83 -0
  63. package/src/push/backend.ts +129 -0
  64. package/src/push/block.tsx +545 -0
  65. package/src/push/index.ts +102 -0
  66. package/src/push/modules.ts +111 -0
  67. package/src/push/permission.ts +191 -0
  68. package/src/push/preferences.ts +136 -0
  69. package/src/push/quiet-hours.ts +71 -0
  70. package/src/push/registration.ts +189 -0
  71. package/src/push/routing.ts +27 -0
  72. package/src/push/screens.tsx +46 -0
  73. package/src/push/settings.tsx +378 -0
  74. package/src/query.ts +100 -0
  75. package/src/updates/block.tsx +442 -0
  76. package/src/updates/channels.ts +131 -0
  77. package/src/updates/checker.ts +190 -0
  78. package/src/updates/index.ts +87 -0
  79. package/src/updates/modules.ts +64 -0
  80. package/src/updates/policy.ts +314 -0
  81. package/src/updates/screens.tsx +159 -0
  82. package/src/updates/signature.ts +80 -0
  83. package/src/updates/store.ts +33 -0
  84. package/src/updates/version.ts +39 -0
  85. package/src/updates/whats-new.ts +122 -0
  86. package/dist/block-Dh5ny06W.d.ts +0 -535
  87. package/dist/chunk-MMTPWUWI.js.map +0 -1
@@ -1,535 +0,0 @@
1
- import * as react from 'react';
2
- import { ComponentType, ReactNode } from 'react';
3
- import { Slots, SlotPropsFor } from '@marktiderman/genesis-core/slots';
4
-
5
- /** A tiny external store for `useSyncExternalStore`: immutable state, one notify per change. */
6
- interface Store<T> {
7
- getState(): T;
8
- setState(next: Partial<T>): void;
9
- subscribe(listener: () => void): () => void;
10
- }
11
-
12
- /**
13
- * The native modules the updates block reads, as the shapes it needs. Pass
14
- * the real ones (`import * as Updates from "expo-updates"`,
15
- * `import * as Application from "expo-application"`); tests pass fakes.
16
- * Both are optional peers: without `expo-updates` there is no update banner
17
- * and no channel switching, and without `expo-application` no force upgrade.
18
- */
19
- /** `expo-updates` (SDK 58): the constants and calls this block uses. */
20
- interface UpdatesModuleLike {
21
- /** False in development, Expo Go, or a build without updates configured. */
22
- readonly isEnabled: boolean;
23
- /** The running update's id; `null` when updates are off. */
24
- readonly updateId: string | null;
25
- /** The build's channel; `null` in development builds and Expo Go. */
26
- readonly channel: string | null;
27
- readonly runtimeVersion: string | null;
28
- /** True when the code that shipped in the binary is running, not a downloaded update. */
29
- readonly isEmbeddedLaunch: boolean;
30
- /** True when expo-updates fell back to the embedded update after a failed launch. */
31
- readonly isEmergencyLaunch?: boolean;
32
- readonly emergencyLaunchReason?: string | null;
33
- checkForUpdateAsync(): Promise<{
34
- isAvailable: boolean;
35
- isRollBackToEmbedded?: boolean;
36
- manifest?: unknown;
37
- }>;
38
- fetchUpdateAsync(): Promise<{
39
- isNew: boolean;
40
- isRollBackToEmbedded?: boolean;
41
- manifest?: unknown;
42
- }>;
43
- reloadAsync(options?: {
44
- reloadScreenOptions?: object;
45
- }): Promise<void>;
46
- /** Overrides the `expo-channel-name` header (persists on the device); `null` removes the override. */
47
- setUpdateRequestHeadersOverride?(requestHeaders: Record<string, string> | null): void;
48
- }
49
- /** `expo-application`: the installed store version and build number. */
50
- interface ApplicationLike {
51
- /** `CFBundleShortVersionString` / `versionName`, e.g. `"1.4.0"`. */
52
- readonly nativeApplicationVersion: string | null;
53
- /** `CFBundleVersion` / `versionCode`, e.g. `"37"`. */
54
- readonly nativeBuildVersion: string | null;
55
- }
56
- /** AsyncStorage, MMKV's async adapter, or a stand-in. */
57
- interface UpdatesStorageLike {
58
- getItem(key: string): Promise<string | null>;
59
- setItem(key: string, value: string): Promise<void>;
60
- removeItem?(key: string): Promise<void>;
61
- }
62
- /** React Native's `AppState`. */
63
- interface UpdatesAppStateLike {
64
- addEventListener(type: "change", listener: (state: string) => void): {
65
- remove(): void;
66
- };
67
- }
68
- /** Where a reported error came from. */
69
- type UpdatesErrorSource = "policy" | "check" | "reload" | "storage" | "channel";
70
- type UpdatesOnError = (error: Error, source: UpdatesErrorSource) => void;
71
-
72
- /**
73
- * The update banner's engine: ask expo-updates for a newer update, download
74
- * it, and reload only when the person taps and nothing is mid-edit.
75
- *
76
- * - It never reloads on its own. A downloaded update also applies on the next
77
- * cold start, so dismissing the banner loses nothing.
78
- * - The reload guard says no while any `useBlockReload(true)` is mounted (a
79
- * form with unsaved changes) or your `canReload()` returns false (or
80
- * throws). The banner then asks the person to finish first.
81
- * - expo-updates only serves updates built for this binary's runtime
82
- * version; an update whose manifest names another runtime is ignored too.
83
- */
84
-
85
- interface ReloadGuard {
86
- /** Hold reloads off until the returned function is called. */
87
- block(): () => void;
88
- /** True when nothing blocks and your `canReload` agrees. */
89
- canReload(): boolean;
90
- /** Fires when a block starts or ends. */
91
- subscribe(listener: () => void): () => void;
92
- }
93
- declare function createReloadGuard(canReload?: () => boolean): ReloadGuard;
94
- type UpdateCheckStatus = "disabled" | "idle" | "checking" | "downloading" | "ready" | "reloading" | "error";
95
- interface UpdateCheckState {
96
- status: UpdateCheckStatus;
97
- /** The downloaded update's id, when the manifest has one. */
98
- updateId: string | null;
99
- /** The person closed the banner; the update still applies on the next cold start. */
100
- dismissed: boolean;
101
- /** The last tap was refused by the reload guard; cleared when the guard opens. */
102
- blocked: boolean;
103
- error: Error | null;
104
- }
105
- interface UpdateCheckerOptions {
106
- updates?: UpdatesModuleLike;
107
- guard: ReloadGuard;
108
- /** Passed to `reloadAsync` (a branded reload screen). */
109
- reloadScreenOptions?: object;
110
- now?: () => number;
111
- onError?: UpdatesOnError;
112
- }
113
- interface UpdateChecker extends Pick<Store<UpdateCheckState>, "getState" | "subscribe"> {
114
- /** Check and download. Never rejects; does nothing while one is in flight or an update is ready. */
115
- check(): Promise<void>;
116
- /** `check()` unless the last one started less than `minIntervalMs` ago. */
117
- checkIfDue(minIntervalMs: number): Promise<void>;
118
- /** Reload into the downloaded update. Resolves false when there is none or the guard refuses. */
119
- reload(): Promise<boolean>;
120
- dismiss(): void;
121
- /** Stop following the guard. */
122
- dispose(): void;
123
- }
124
- declare function createUpdateChecker(options: UpdateCheckerOptions): UpdateChecker;
125
-
126
- /**
127
- * Switching a test build to another update channel (EAS "channel surfing"):
128
- * override the `expo-channel-name` header, check, fetch, reload. The
129
- * override persists on the device until it is reset or the app is removed.
130
- *
131
- * Off unless you turn it on, and turn it on only in development and preview
132
- * builds (`enabled: __DEV__ || isPreviewBuild`): a store build that can be
133
- * pointed at any channel can be pointed at an unreviewed one. Going back
134
- * (`reset`) always works, for everyone, because it only removes the
135
- * override.
136
- *
137
- * Switching reloads, so it respects the reload guard: with unsaved changes
138
- * it sets the channel and says `blocked`; the channel applies on the next
139
- * launch.
140
- */
141
-
142
- type ChannelSwitchResult =
143
- /** The reload was sent; JS may stop at any moment. */
144
- {
145
- kind: "reloading";
146
- }
147
- /** The channel is set, but something is mid-edit: it applies on the next launch. */
148
- | {
149
- kind: "blocked";
150
- }
151
- /** expo-updates is off or too old here (development, Expo Go). Nothing changed. */
152
- | {
153
- kind: "unavailable";
154
- reason: string;
155
- }
156
- /** The channel is set but checking, fetching or reloading failed. */
157
- | {
158
- kind: "error";
159
- error: Error;
160
- };
161
- interface ChannelSwitcher {
162
- /** True when this build may switch channels (`channels.enabled`). */
163
- readonly enabled: boolean;
164
- /** Switch to `channel` and reload. Throws when switching is off. */
165
- switchTo(channel: string): Promise<ChannelSwitchResult>;
166
- /** Back to the build's own channel. Always allowed. */
167
- reset(): Promise<ChannelSwitchResult>;
168
- /** The channel picked on this device, or `null` when it follows the build. */
169
- requested(): Promise<string | null>;
170
- }
171
- interface ChannelSwitcherOptions {
172
- updates?: UpdatesModuleLike;
173
- /** Must be explicitly true (development and preview builds only). */
174
- enabled: boolean;
175
- guard: ReloadGuard;
176
- /** Remembers the picked channel, for the version signature. */
177
- storage?: UpdatesStorageLike;
178
- /** Default `"genesis:updates:channel"`. */
179
- storageKey?: string;
180
- reloadScreenOptions?: object;
181
- onError?: UpdatesOnError;
182
- }
183
- declare function createChannelSwitcher(options: ChannelSwitcherOptions): ChannelSwitcher;
184
-
185
- /**
186
- * The release policy: the oldest store version each platform may run, a
187
- * maintenance switch, and release notes. It comes from an
188
- * `UpdatePolicySource` (a static object, Supabase, or your own), is cached on
189
- * the phone, and **fails open**: no answer, a timeout, a malformed value or
190
- * an untrusted cache never blocks anyone.
191
- *
192
- * What may block, and from where:
193
- * - Force upgrade: a policy fetched this session, or a cached one no older
194
- * than `cacheMaxAgeMs` (7 days) by the phone's clock. A cache whose time is
195
- * in the future (the clock moved back) is not trusted. A minimum without a
196
- * store link for the platform forces nothing (there would be no way out).
197
- * - Maintenance: only a policy fetched from the network this session. A
198
- * cached maintenance flag never blocks a cold start.
199
- */
200
-
201
- type UpdatePlatform = "ios" | "android";
202
- interface ReleaseNote {
203
- /** The store version these notes describe, e.g. `"1.4.0"`. */
204
- version: string;
205
- title?: string;
206
- body: string;
207
- }
208
- interface UpdatePolicy {
209
- /** The oldest store version still supported, per platform. Missing or malformed: no minimum. */
210
- minimumVersion?: Partial<Record<UpdatePlatform, string>>;
211
- /** Where "Update" goes (`https://apps.apple.com/…`, `itms-apps://…`, `market://…`, `https://play.google.com/…`). */
212
- storeUrl?: Partial<Record<UpdatePlatform, string>>;
213
- maintenance?: {
214
- active: boolean;
215
- message: string | null;
216
- };
217
- notes?: readonly ReleaseNote[];
218
- }
219
- /** Where the policy comes from. `load` throws (or rejects) when it can't answer; resolving `null` means "no policy". */
220
- interface UpdatePolicySource {
221
- load(): Promise<UpdatePolicy | null | undefined>;
222
- }
223
- /** A policy written in the app (or a test). */
224
- declare function staticPolicySource(policy: UpdatePolicy): UpdatePolicySource;
225
- /** A Supabase client: anything with `from(table)` (a real `SupabaseClient` fits without a cast). */
226
- interface PolicyClientLike {
227
- from(table: string): unknown;
228
- }
229
- interface SupabasePolicySourceOptions {
230
- /** The `app_id` row to read. Default `"default"`. */
231
- appId?: string;
232
- /** Default `"app_release_policy"` (migration `0002_genesis_app_release_policy.sql` in `@marktiderman/genesis-supabase`). */
233
- table?: string;
234
- }
235
- /**
236
- * Reads one row of `public.app_release_policy` (readable by anon and
237
- * authenticated, written only with the service role). No row is no policy.
238
- */
239
- declare function supabasePolicySource(client: PolicyClientLike, options?: SupabasePolicySourceOptions): UpdatePolicySource;
240
- /** Keeps only well-formed fields; throws when `raw` isn't an object at all (so the cache answers instead). */
241
- declare function sanitizePolicy(raw: unknown): UpdatePolicy;
242
- type PolicyOrigin = "network" | "cache" | "none";
243
- interface PolicyState {
244
- /** `loading` until the first answer (or the timeout); then `ready`, with or without a policy. */
245
- status: "loading" | "ready";
246
- policy: UpdatePolicy | null;
247
- /** `network`: fetched this session. `cache`: read from the phone. `none`: no answer yet (fail open). */
248
- origin: PolicyOrigin;
249
- /** When it was fetched, by the phone's clock (ms). */
250
- fetchedAt: number | null;
251
- /** The last failed fetch, if the most recent one failed. */
252
- error: Error | null;
253
- /** A fetch is in flight. */
254
- refreshing: boolean;
255
- }
256
- interface PolicyStoreOptions {
257
- source: UpdatePolicySource;
258
- storage?: UpdatesStorageLike;
259
- /** Default `"genesis:updates:policy"`. */
260
- cacheKey?: string;
261
- /** The longest `load()` waits before carrying on without an answer. Default 3000 ms. */
262
- timeoutMs?: number;
263
- now?: () => number;
264
- onError?: UpdatesOnError;
265
- }
266
- interface PolicyStore extends Pick<Store<PolicyState>, "getState" | "subscribe"> {
267
- /** The first load: the cache and the network together, for at most `timeoutMs`. Never rejects; runs once. */
268
- load(): Promise<void>;
269
- /** Fetch again (maintenance "Try again", the app coming to the front). Never rejects; resolves true on an answer. */
270
- refresh(): Promise<boolean>;
271
- /** `refresh()` unless one ran less than `minIntervalMs` ago. */
272
- refreshIfDue(minIntervalMs: number): Promise<boolean>;
273
- }
274
- declare function createPolicyStore(options: PolicyStoreOptions): PolicyStore;
275
- /** What the policy says this install must do. */
276
- type UpdateVerdict = {
277
- kind: "ok";
278
- } | {
279
- kind: "forceUpgrade";
280
- minimumVersion: string;
281
- installedVersion: string;
282
- storeUrl: string;
283
- } | {
284
- kind: "maintenance";
285
- message: string | null;
286
- };
287
- interface EvaluatePolicyOptions {
288
- /** `Platform.OS`. Only `ios` and `android` can be forced to upgrade. */
289
- platform: string;
290
- /** The installed store version (`Application.nativeApplicationVersion`). `null`: never forced. */
291
- installedVersion: string | null;
292
- now: number;
293
- /** How old a cached policy may be and still force an upgrade. Default 7 days. */
294
- cacheMaxAgeMs?: number;
295
- }
296
- /** How far in the future a cache's time may be (clocks drift) before it isn't trusted. */
297
- declare const CLOCK_SKEW_MS: number;
298
- declare const WEEK_MS: number;
299
- /** Force upgrade first (the store is the way out), then maintenance, else ok. Fails open. */
300
- declare function evaluatePolicy(state: PolicyState, options: EvaluatePolicyOptions): UpdateVerdict;
301
-
302
- /**
303
- * The version signature: which binary and which update this is, in one
304
- * line for Settings "About" and in a few lines for a support report.
305
- *
306
- * 1.4.0 (37) · preview · 3f2a9c1b
307
- * 1.4.0 (37) · production · embedded
308
- * 1.4.0 (37) · updates off
309
- *
310
- * A value the modules can't give is printed as `unknown`, never left out.
311
- */
312
-
313
- interface VersionSignature {
314
- version: string | null;
315
- build: string | null;
316
- platform: string;
317
- /** The build's channel (expo-updates). */
318
- channel: string | null;
319
- /** A channel picked on this device (a test build switched channels); `null` when it follows the build. */
320
- requestedChannel: string | null;
321
- runtimeVersion: string | null;
322
- updateId: string | null;
323
- /** `update`: a downloaded update runs. `embedded`: the binary's own code. `off`: updates are off (development, Expo Go). */
324
- launch: "update" | "embedded" | "off";
325
- /** expo-updates fell back to the embedded code after a failed launch. */
326
- emergency: boolean;
327
- /** One line, for an About row. */
328
- short: string;
329
- /** Several lines, for a support report. */
330
- text: string;
331
- }
332
- interface VersionSignatureInput {
333
- application?: ApplicationLike;
334
- updates?: UpdatesModuleLike;
335
- /** `Platform.OS`. */
336
- platform: string;
337
- requestedChannel?: string | null;
338
- }
339
- declare function versionSignature({ application, updates, platform, requestedChannel }: VersionSignatureInput): VersionSignature;
340
-
341
- /**
342
- * What's new: release notes shown once per version, remembered in storage.
343
- *
344
- * - First launch after an install (nothing stored): nothing shows; the
345
- * version is remembered, so a new install never opens on a sheet.
346
- * - A newer version than the one remembered: the notes for every version
347
- * after it, up to this one, newest first. Closing them remembers this
348
- * version. With no notes for it, the version is remembered once the policy
349
- * has answered from the network (or there is no policy source), so notes
350
- * that arrive late still show.
351
- * - Storage that can't be read shows nothing (rather than every launch).
352
- */
353
-
354
- interface WhatsNewState {
355
- status: "loading" | "hidden" | "showing";
356
- notes: readonly ReleaseNote[];
357
- }
358
- interface WhatsNewOptions {
359
- storage?: UpdatesStorageLike;
360
- /** The installed version notes are matched against. `null`: never shows. */
361
- version: string | null;
362
- /** Default `"genesis:updates:seen-version"`. */
363
- storageKey?: string;
364
- onError?: UpdatesOnError;
365
- }
366
- interface WhatsNewStore extends Pick<Store<WhatsNewState>, "getState" | "subscribe"> {
367
- /** Read the remembered version. Never rejects; runs once. */
368
- load(): Promise<void>;
369
- /** Recompute with the notes known now; `final` once no more notes can arrive. */
370
- update(notes: readonly ReleaseNote[], final: boolean): void;
371
- /** Close the notes and remember this version. */
372
- dismiss(): void;
373
- }
374
- /** The notes after `seen`, up to `version`, newest first. */
375
- declare function notesSince(notes: readonly ReleaseNote[], seen: string, version: string): ReleaseNote[];
376
- declare function createWhatsNewStore(options: WhatsNewOptions): WhatsNewStore;
377
-
378
- interface ForceUpgradeProps {
379
- minimumVersion: string;
380
- installedVersion: string;
381
- /** The policy's store link for this platform (a minimum without one forces nothing). */
382
- storeUrl: string;
383
- /** Opens `storeUrl`. */
384
- openStore: () => void;
385
- /** Ask the policy again (it may have been lowered). */
386
- retry: () => void;
387
- checking: boolean;
388
- }
389
- /** Blocking: this version is below the minimum. "Update" opens the store. */
390
- declare function ForceUpgradeScreen({ minimumVersion, installedVersion, openStore, retry, checking }: ForceUpgradeProps): react.JSX.Element;
391
- interface MaintenanceProps {
392
- /** The policy's message, or `null` for the default copy. */
393
- message: string | null;
394
- retry: () => void;
395
- retrying: boolean;
396
- }
397
- /** Blocking: the policy says maintenance is on. "Try again" asks again. */
398
- declare function MaintenanceScreen({ message, retry, retrying }: MaintenanceProps): react.JSX.Element;
399
- interface UpdateBannerProps {
400
- /** Reload into the downloaded update (refused while `canReload` is false). */
401
- reload: () => void;
402
- /** Hide the banner; the update still applies on the next cold start. */
403
- dismiss: () => void;
404
- /** False while something is mid-edit (`useBlockReload`) or your `canReload` says no. */
405
- canReload: boolean;
406
- /** The last tap was refused because something is mid-edit. */
407
- blocked: boolean;
408
- reloading: boolean;
409
- }
410
- /** Non-blocking: floats over the top of the screens while an update is downloaded. */
411
- declare function UpdateBanner({ reload, dismiss, canReload, blocked, reloading }: UpdateBannerProps): react.JSX.Element;
412
- interface WhatsNewProps {
413
- /** Newest first. */
414
- notes: readonly ReleaseNote[];
415
- /** The installed version. */
416
- version: string | null;
417
- /** Close and remember this version. */
418
- dismiss: () => void;
419
- }
420
- /** The release notes for this version, once, in a sheet. */
421
- declare function WhatsNewSheet({ notes, dismiss }: WhatsNewProps): react.JSX.Element;
422
-
423
- type UpdatesSlots = {
424
- /** Blocking. Default `ForceUpgradeScreen`. */
425
- forceUpgrade: ForceUpgradeProps;
426
- /** Blocking. Default `MaintenanceScreen`. */
427
- maintenance: MaintenanceProps;
428
- /** Over your screens while an update is downloaded. Default `UpdateBanner`. */
429
- updateBanner: UpdateBannerProps;
430
- /** Once per version. Default `WhatsNewSheet`. */
431
- whatsNew: WhatsNewProps;
432
- };
433
- interface UpdatesOptions {
434
- /** `Platform.OS`. Force upgrade applies to `ios` and `android` only. */
435
- platform: string;
436
- /** `expo-application`: the installed version. Without it, nothing is ever forced and notes never show. */
437
- application?: ApplicationLike;
438
- /** `expo-updates`. Without it (or with updates off), no banner and no channel switching. */
439
- updates?: UpdatesModuleLike;
440
- /** Min versions, maintenance and notes. Without it, only `notes` and the banner. */
441
- policy?: UpdatePolicySource;
442
- /** AsyncStorage: the policy cache, the version whose notes were seen, the picked channel. */
443
- storage?: UpdatesStorageLike;
444
- /** `AppState`: ask again when the app comes to the front. */
445
- appState?: UpdatesAppStateLike;
446
- /** `Linking.openURL`, for the store link. */
447
- openURL?: (url: string) => unknown;
448
- /** Extra say over reloading (return false to hold it off); `useBlockReload` covers forms. */
449
- canReload?: () => boolean;
450
- /** Release notes written in the app, merged with the policy's. */
451
- notes?: readonly ReleaseNote[];
452
- /** `false` turns what's new off. */
453
- whatsNew?: boolean;
454
- /** Channel switching, for development and preview builds only: `{ enabled: __DEV__ || isPreview }`. */
455
- channels?: {
456
- enabled: boolean;
457
- };
458
- /** The longest the first policy load holds the splash. Default 3000 ms. */
459
- policyTimeoutMs?: number;
460
- /** How old a cached policy may be and still force an upgrade. Default 7 days. */
461
- cacheMaxAgeMs?: number;
462
- /** Ask for the policy again on coming to the front at most this often. Default 5 minutes. */
463
- policyRefreshMs?: number;
464
- /** Check for an update on coming to the front at most this often. Default 15 minutes. */
465
- checkIntervalMs?: number;
466
- /** `reloadAsync`'s reload screen (background color, spinner). */
467
- reloadScreenOptions?: object;
468
- /** Storage key prefix. Default `"genesis:updates"`. */
469
- storageKeyPrefix?: string;
470
- /** Every caught error, for reporting. None of them blocks anyone. */
471
- onError?: UpdatesOnError;
472
- now?: () => number;
473
- }
474
- interface UpdatesBlock {
475
- readonly options: UpdatesOptions;
476
- readonly policy: PolicyStore;
477
- readonly checker: UpdateChecker;
478
- readonly guard: ReloadGuard;
479
- readonly whatsNew: WhatsNewStore;
480
- /** Channel switching: `switchTo` throws unless `channels.enabled` was passed; `reset` always works. */
481
- readonly channels: ChannelSwitcher;
482
- /** The signature from the modules, without the picked channel (see `useVersionSignature`). */
483
- readonly signature: VersionSignature;
484
- /** Loads the policy and the seen version behind the splash; never fails, done within `policyTimeoutMs`. */
485
- readonly bootStep: {
486
- id: string;
487
- run: () => Promise<void>;
488
- timeoutMs: number;
489
- };
490
- /** Start following the app's state; returns the stop. `UpdatesRoot` calls it. */
491
- start(): () => void;
492
- /** The block's root, for `GenesisNativeApp`. */
493
- readonly Root: ComponentType<Omit<UpdatesRootProps, "block">>;
494
- }
495
- /** Build the block once, at module scope, next to your Supabase client. */
496
- declare function createUpdates(options: UpdatesOptions): UpdatesBlock;
497
- /** Provides the block to the hooks, without rendering any of its parts (rung 4). */
498
- declare function UpdatesProvider({ block, children }: {
499
- block: UpdatesBlock;
500
- children?: ReactNode;
501
- }): react.JSX.Element;
502
- /** The block from `UpdatesProvider` / `UpdatesRoot` / `GenesisNativeApp`. Throws outside one. */
503
- declare function useUpdates(): UpdatesBlock;
504
- /**
505
- * Hold reloads off while `active` (a form with unsaved changes):
506
- * `useBlockReload(form.formState.isDirty)`. Does nothing without an updates
507
- * block, so a screen can call it whether or not the block is on.
508
- */
509
- declare function useBlockReload(active: boolean): void;
510
- declare function useUpdatePolicy(): PolicyState;
511
- /** What the policy says now: `ok`, `forceUpgrade` or `maintenance`. */
512
- declare function useUpdateVerdict(): UpdateVerdict;
513
- /** The force-upgrade screen's props, or `null` when this version is supported. */
514
- declare function useForceUpgrade(): ForceUpgradeProps | null;
515
- /** The maintenance screen's props, or `null` when maintenance is off (or not known live). */
516
- declare function useMaintenance(): MaintenanceProps | null;
517
- declare function useUpdateCheck(): UpdateCheckState;
518
- /** True when a reload would be allowed now. */
519
- declare function useCanReload(): boolean;
520
- /** The banner's props, or `null` when there is nothing to show. */
521
- declare function useUpdateBanner(): UpdateBannerProps | null;
522
- /** The notes to show now, or `null`. */
523
- declare function useWhatsNew(): WhatsNewProps | null;
524
- /** The version signature, with the channel picked on this device once it has been read. */
525
- declare function useVersionSignature(): VersionSignature;
526
- interface UpdatesRootProps {
527
- block: UpdatesBlock;
528
- slots?: Slots<UpdatesSlots>;
529
- slotProps?: SlotPropsFor<UpdatesSlots>;
530
- children?: ReactNode;
531
- }
532
- /** The block's parts around your screens. */
533
- declare function UpdatesRoot({ block, slots, slotProps, children }: UpdatesRootProps): react.JSX.Element;
534
-
535
- export { evaluatePolicy as $, type ApplicationLike as A, UpdatesProvider as B, CLOCK_SKEW_MS as C, UpdatesRoot as D, type EvaluatePolicyOptions as E, type ForceUpgradeProps as F, type UpdatesRootProps as G, type UpdatesStorageLike as H, type VersionSignatureInput as I, type WhatsNewOptions as J, type WhatsNewProps as K, WhatsNewSheet as L, type MaintenanceProps as M, type WhatsNewState as N, type WhatsNewStore as O, type PolicyClientLike as P, createChannelSwitcher as Q, type ReleaseNote as R, type SupabasePolicySourceOptions as S, createPolicyStore as T, type UpdatesBlock as U, type VersionSignature as V, WEEK_MS as W, createReloadGuard as X, createUpdateChecker as Y, createUpdates as Z, createWhatsNewStore as _, type UpdatesSlots as a, notesSince as a0, sanitizePolicy as a1, staticPolicySource as a2, supabasePolicySource as a3, useBlockReload as a4, useCanReload as a5, useForceUpgrade as a6, useMaintenance as a7, useUpdateBanner as a8, useUpdateCheck as a9, useUpdatePolicy as aa, useUpdateVerdict as ab, useUpdates as ac, useVersionSignature as ad, useWhatsNew as ae, versionSignature as af, type ChannelSwitchResult as b, type ChannelSwitcher as c, type ChannelSwitcherOptions as d, ForceUpgradeScreen as e, MaintenanceScreen as f, type PolicyOrigin as g, type PolicyState as h, type PolicyStore as i, type PolicyStoreOptions as j, type ReloadGuard as k, UpdateBanner as l, type UpdateBannerProps as m, type UpdateCheckState as n, type UpdateCheckStatus as o, type UpdateChecker as p, type UpdateCheckerOptions as q, type UpdatePlatform as r, type UpdatePolicy as s, type UpdatePolicySource as t, type UpdateVerdict as u, type UpdatesAppStateLike as v, type UpdatesErrorSource as w, type UpdatesModuleLike as x, type UpdatesOnError as y, type UpdatesOptions as z };
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/query.ts"],"names":[],"mappings":";;;AAgBO,IAAM,MAAA,GAAS,EAAA,GAAK,EAAA,GAAK,EAAA,GAAK;AAS9B,SAAS,uBAAA,CAAwB,MAAA,GAA4B,EAAC,EAAgB;AACnF,EAAA,MAAM,EAAE,SAAS,SAAA,EAAW,GAAG,MAAK,GAAI,MAAA,CAAO,kBAAkB,EAAC;AAClE,EAAA,OAAO,IAAI,WAAA,CAAY;AAAA,IACrB,GAAG,MAAA;AAAA,IACH,cAAA,EAAgB;AAAA,MACd,GAAG,IAAA;AAAA,MACH,OAAA,EAAS,EAAE,WAAA,EAAa,QAAA,EAAU,QAAQ,MAAA,EAAQ,SAAA,EAAW,GAAA,EAAQ,GAAG,OAAA,EAAQ;AAAA,MAChF,SAAA,EAAW,EAAE,WAAA,EAAa,QAAA,EAAU,GAAG,SAAA;AAAU;AACnD,GACD,CAAA;AACH;AAuCO,SAAS,qBAAA,CAAsB,EAAE,QAAA,EAAU,OAAA,EAAQ,EAAsC;AAC9F,EAAA,IAAI,QAAA,EAAU;AACZ,IAAA,YAAA,CAAa,gBAAA,CAAiB,CAAC,UAAA,KAAe;AAC5C,MAAA,MAAM,YAAA,GAAe,SAAS,gBAAA,CAAiB,QAAA,EAAU,CAAC,KAAA,KAAU,UAAA,CAAW,KAAA,KAAU,QAAQ,CAAC,CAAA;AAClG,MAAA,OAAO,MAAM,aAAa,MAAA,EAAO;AAAA,IACnC,CAAC,CAAA;AAAA,EACH;AACA,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,aAAA,CAAc,gBAAA,CAAiB,CAAC,SAAA,KAAc,OAAA,CAAQ,gBAAA,CAAiB,CAAC,KAAA,KAAU,SAAA,CAAU,KAAA,CAAM,WAAA,KAAgB,KAAK,CAAC,CAAC,CAAA;AAAA,EAC3H;AACA,EAAA,OAAO,MAAM;AAEX,IAAA,IAAI,QAAA,EAAU,YAAA,CAAa,gBAAA,CAAiB,MAAM,MAAM;AAAA,IAAC,CAAC,CAAA;AAC1D,IAAA,IAAI,OAAA,EAAS,aAAA,CAAc,gBAAA,CAAiB,MAAM,MAAM;AAAA,IAAC,CAAC,CAAA;AAAA,EAC5D,CAAA;AACF","file":"chunk-MMTPWUWI.js","sourcesContent":["/**\n * Query: a TanStack Query client with offline-safe defaults, and the contract\n * for saving it to the phone. The persister itself is in\n * `@marktiderman/genesis-native/persist`, so an app that saves nothing doesn't\n * install TanStack's persister packages.\n */\nimport { QueryClient, focusManager, onlineManager, type QueryClientConfig } from \"@tanstack/react-query\";\n\n/** The part of AsyncStorage (or any async key-value store) Genesis uses. */\nexport interface AsyncStorageLike {\n getItem(key: string): Promise<string | null>;\n setItem(key: string, value: string): Promise<void>;\n removeItem(key: string): Promise<void>;\n}\n\n/** One day in milliseconds: the default max age of the saved query cache. */\nexport const DAY_MS = 24 * 60 * 60 * 1000;\n\n/**\n * A QueryClient for a phone. `networkMode: \"always\"` runs a query or mutation\n * even when the device reports offline, so it fails (and shows its error or\n * cached data) instead of waiting forever for a network that may never come\n * back. `gcTime` matches the persisted max age, or restored queries would be\n * collected before they are used. Your `config` wins.\n */\nexport function createNativeQueryClient(config: QueryClientConfig = {}): QueryClient {\n const { queries, mutations, ...rest } = config.defaultOptions ?? {};\n return new QueryClient({\n ...config,\n defaultOptions: {\n ...rest,\n queries: { networkMode: \"always\", gcTime: DAY_MS, staleTime: 60_000, ...queries },\n mutations: { networkMode: \"always\", ...mutations },\n },\n });\n}\n\n/**\n * The saved query cache. A `scope` (the signed-in user's id, `\"\"` for nobody)\n * is part of what is saved: a cache saved for one user is discarded, on disk\n * too, when another scope restores it.\n */\nexport interface QueryPersistence {\n /** Load the saved cache into the client, if it was saved for `scope`. */\n restore(scope: string): Promise<void>;\n /** Save on every cache change, for `scope`; returns the unsubscribe. */\n subscribe(scope: string): () => void;\n /**\n * Delete the saved cache. Clear the QueryClient first: the subscription\n * then saves the empty cache, so a save already in flight can't leave the\n * old queries on disk.\n */\n clear(): Promise<void>;\n}\n\n/** React Native's `AppState`, or a stand-in. */\nexport interface AppStateLike {\n addEventListener(type: \"change\", listener: (state: string) => void): { remove(): void };\n}\n\n/** `@react-native-community/netinfo`'s default export, or a stand-in. */\nexport interface NetInfoLike {\n addEventListener(listener: (state: { isConnected: boolean | null }) => void): () => void;\n}\n\nexport interface QueryLifecycleOptions {\n appState?: AppStateLike;\n netInfo?: NetInfoLike;\n}\n\n/**\n * Tell TanStack Query when the app comes to the front (refetch stale\n * queries) and when the network comes and goes. Returns the cleanup.\n */\nexport function connectQueryLifecycle({ appState, netInfo }: QueryLifecycleOptions): () => void {\n if (appState) {\n focusManager.setEventListener((setFocused) => {\n const subscription = appState.addEventListener(\"change\", (state) => setFocused(state === \"active\"));\n return () => subscription.remove();\n });\n }\n if (netInfo) {\n onlineManager.setEventListener((setOnline) => netInfo.addEventListener((state) => setOnline(state.isConnected !== false)));\n }\n return () => {\n // Replacing the listener runs the cleanup of the one set above.\n if (appState) focusManager.setEventListener(() => () => {});\n if (netInfo) onlineManager.setEventListener(() => () => {});\n };\n}\n"]}