@hot-updater/protocol 0.0.0-stage → 1.0.0-rc.21

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,548 @@
1
+ //#region src/appVersion.d.ts
2
+ /**
3
+ * The form of an app version that the device and the server compare:
4
+ * `major.minor.patch` of the first version in `appVersion`, such as `1.4.0`
5
+ * for `v1.4`, or null when it holds none. Prerelease and build parts are
6
+ * dropped.
7
+ */
8
+ declare function canonicalizeAppVersion(appVersion: string): string | null;
9
+ //#endregion
10
+ //#region src/baseURL.d.ts
11
+ /** The server's base URL, or a function that returns it, as the SDK takes it. */
12
+ type HotUpdaterBaseURL = string | (() => string | Promise<string>);
13
+ /** Resolves a functional baseURL and drops trailing slashes. */
14
+ declare const resolveBaseURL: (baseURL: HotUpdaterBaseURL) => Promise<string>;
15
+ //#endregion
16
+ //#region src/types.d.ts
17
+ type Platform = "ios" | "android";
18
+ type BundleMetadata = {
19
+ app_version?: string;
20
+ };
21
+ interface BundlePatchArtifact {
22
+ baseBundleId: string;
23
+ baseFileHash: string;
24
+ byteSize: number;
25
+ patchFileHash: string;
26
+ patchStorageUri: string;
27
+ }
28
+ interface ChangedAssetPatch {
29
+ algorithm: "bsdiff";
30
+ baseBundleId: string;
31
+ baseFileHash: string;
32
+ patchFileHash: string;
33
+ patchUrl: string;
34
+ /** Transferred patch bytes, used as a conservative installation cost hint. */
35
+ byteSize?: number;
36
+ }
37
+ /** Authenticated by the target manifest; always describes bundle.tar.br. */
38
+ interface ManifestArchive {
39
+ downloadFileHash: string;
40
+ downloadByteSize: number;
41
+ tarByteSize: number;
42
+ }
43
+ interface ArtifactAssetFile {
44
+ compression?: "br" | null;
45
+ url: string;
46
+ }
47
+ interface ArtifactAsset {
48
+ file: ArtifactAssetFile;
49
+ fileHash: string;
50
+ patch?: ChangedAssetPatch | null;
51
+ }
52
+ declare const ARTIFACT_PROTOCOL_VERSION: 1;
53
+ interface Bundle {
54
+ /**
55
+ * The unique identifier for the bundle. uuidv7
56
+ */
57
+ id: string;
58
+ /**
59
+ * The platform the bundle is for.
60
+ */
61
+ platform: Platform;
62
+ /**
63
+ * The git commit hash of the bundle.
64
+ */
65
+ gitCommitHash: string | null;
66
+ /**
67
+ * The metadata of the bundle.
68
+ */
69
+ metadata?: BundleMetadata;
70
+ /**
71
+ * Storage URI for the bundle manifest artifact.
72
+ */
73
+ manifestStorageUri: string;
74
+ /**
75
+ * SHA256 hash of the manifest artifact, optionally signed as sig:<signature>.
76
+ */
77
+ manifestFileHash: string;
78
+ /**
79
+ * Storage URI prefix for manifest assets.
80
+ */
81
+ assetBaseStorageUri: string;
82
+ /**
83
+ * Binary patch artifacts keyed by base bundle in array order.
84
+ * Earlier entries take precedence when a single "primary" patch is needed.
85
+ */
86
+ patches?: BundlePatchArtifact[] | null;
87
+ }
88
+ type UpdateStatus = "ROLLBACK" | "UPDATE";
89
+ interface ArtifactInfo {
90
+ /** Manifest artifact protocol used by current native clients. */
91
+ artifactProtocolVersion: typeof ARTIFACT_PROTOCOL_VERSION;
92
+ /**
93
+ * Manifest artifact for protocol v1 updates.
94
+ */
95
+ manifestUrl: string;
96
+ /**
97
+ * SHA256 hash of the manifest file, optionally with embedded signature.
98
+ * Uses `sig:<base64_signature>` or a plain SHA256 hash.
99
+ */
100
+ manifestFileHash: string;
101
+ /** Optional bulk transport. Hash and sizes come from the verified manifest. */
102
+ archiveUrl?: string;
103
+ /**
104
+ * Full target manifest file map. Protocol v1 requires one original file
105
+ * descriptor for every target asset; patches are optional optimizations.
106
+ */
107
+ assets: Record<string, ArtifactAsset>;
108
+ }
109
+ //#endregion
110
+ //#region src/bundleArtifacts.d.ts
111
+ declare const stripBundleArtifactMetadata: (metadata: BundleMetadata | undefined) => BundleMetadata | undefined;
112
+ declare const getManifestStorageUri: (bundle: Pick<Bundle, "manifestStorageUri">) => string;
113
+ declare const getManifestFileHash: (bundle: Pick<Bundle, "manifestFileHash">) => string;
114
+ declare const getAssetBaseStorageUri: (bundle: Pick<Bundle, "assetBaseStorageUri">) => string;
115
+ declare const getBundlePatches: (bundle: Pick<Bundle, "patches">) => BundlePatchArtifact[];
116
+ declare const getBundlePatch: (bundle: Pick<Bundle, "patches">, baseBundleId: string) => BundlePatchArtifact | null;
117
+ declare const getPatchBaseBundleId: (bundle: Pick<Bundle, "patches">) => string;
118
+ declare const getPatchBaseFileHash: (bundle: Pick<Bundle, "patches">) => string;
119
+ declare const getPatchFileHash: (bundle: Pick<Bundle, "patches">) => string;
120
+ declare const getPatchStorageUri: (bundle: Pick<Bundle, "patches">) => string;
121
+ //#endregion
122
+ //#region src/clientPlugin.d.ts
123
+ /**
124
+ * The client plugin contract. Built-in plugins, such as Insights, and
125
+ * third-party plugins use the same one, as server plugins do. Plugins import
126
+ * it from here, and apps through `@hot-updater/react-native`.
127
+ *
128
+ * Keep this module free of state and of classes that code checks with
129
+ * `instanceof`. A plugin can load it in one module format while the app loads
130
+ * it in the other, so two copies can run side by side.
131
+ */
132
+ type UpdateStrategy = "fingerprint" | "appVersion";
133
+ /** How an update moves the app's selection. */
134
+ type ReleaseTransitionKind = "INSTALL" | "ADOPT_RELEASE" | "USE_EMBEDDED" | "USE_BUILTIN";
135
+ /** The launch outcome native reports once per JavaScript runtime. */
136
+ type AppReadyResult = ({
137
+ /** No update was applied or recovered at this launch. */
138
+ readonly status: "UNCHANGED";
139
+ readonly channel: string;
140
+ /** The running bundle. */
141
+ readonly bundleId: string;
142
+ /** The Release the running bundle was selected as, when known. */
143
+ readonly releaseId: string | null;
144
+ } | {
145
+ /**
146
+ * `UPDATE_APPLIED`: a staged update became active at this launch.
147
+ * `RECOVERED`: a failed update was rolled back to `toBundleId`.
148
+ */
149
+ readonly status: "UPDATE_APPLIED" | "RECOVERED";
150
+ readonly channel: string;
151
+ readonly fromBundleId: string;
152
+ readonly fromReleaseId: string | null;
153
+ readonly toBundleId: string;
154
+ readonly toReleaseId: string | null;
155
+ readonly updateStrategy: UpdateStrategy;
156
+ }) & {
157
+ /**
158
+ * Android 11+: why the app's previous main process exited, the
159
+ * `ApplicationExitInfo` reason without its `REASON_` prefix, such as
160
+ * `CRASH`, `ANR`, `LOW_MEMORY`, or `USER_REQUESTED`. Null on iOS and on
161
+ * older Android. It tells a crash from the system or the user closing the
162
+ * app.
163
+ */
164
+ readonly previousProcessExit: string | null;
165
+ };
166
+ /** What an update check found. */
167
+ type UpdateCheckResult = {
168
+ /**
169
+ * The app keeps running its bundle: the check found nothing to
170
+ * install, or the app adopted a newer Release of the running bundle.
171
+ */
172
+ readonly status: "UNCHANGED";
173
+ readonly channel: string;
174
+ readonly bundleId: string;
175
+ /** The Release the running bundle is selected as after the check. */
176
+ readonly releaseId: string | null;
177
+ /** The Release before the check; it differs after an adoption. */
178
+ readonly previousReleaseId: string | null;
179
+ } | {
180
+ /** The check found a Release to install, adopt, or roll back to. */
181
+ readonly status: "UPDATE_AVAILABLE";
182
+ readonly channel: string;
183
+ readonly fromBundleId: string;
184
+ readonly fromReleaseId: string | null;
185
+ readonly toBundleId: string;
186
+ readonly toReleaseId: string | null;
187
+ readonly transitionKind: ReleaseTransitionKind;
188
+ readonly updateStatus: "UPDATE" | "ROLLBACK";
189
+ readonly shouldForceUpdate: boolean;
190
+ readonly updateStrategy: UpdateStrategy;
191
+ };
192
+ /** A bundle the SDK downloaded, verified, and staged for the next launch. */
193
+ interface BundleDownloadedInfo {
194
+ readonly channel: string;
195
+ readonly fromBundleId: string;
196
+ readonly fromReleaseId: string | null;
197
+ readonly toBundleId: string;
198
+ readonly toReleaseId: string | null;
199
+ readonly updateStrategy: UpdateStrategy;
200
+ /**
201
+ * How the bundle arrived: `patch` when a bsdiff patch produced a file,
202
+ * `manifest` when only the changed files were downloaded, `archive` for
203
+ * the full archive.
204
+ */
205
+ readonly delivery: "patch" | "manifest" | "archive";
206
+ /** A patch was tried, but the file or the archive was downloaded instead. */
207
+ readonly patchFallback: boolean;
208
+ }
209
+ /**
210
+ * Where an update failed: `check` covers the Release catalog request,
211
+ * `download` the transfer and verification of the update's files, and
212
+ * `install` patching, extracting, and moving them into place.
213
+ */
214
+ type UpdateErrorStage = "check" | "download" | "install";
215
+ /** What the SDK was fetching or applying when an update failed. */
216
+ type UpdateErrorResource = "catalog" | "artifact" | "manifest" | "file" | "patch" | "archive";
217
+ /** Why no response arrived, for a `network` failure. */
218
+ type UpdateErrorTransport = "timeout" | "dns" | "tls" | "connection" | "offline" | "cancelled";
219
+ type UpdateErrorReason = "network" | "http" | "invalid_response" | "hash_mismatch" | "signature" | "patch" | "extract" | "storage" | "unknown";
220
+ /** An update check, download, or install that failed. */
221
+ interface UpdateError {
222
+ readonly stage: UpdateErrorStage;
223
+ readonly reason: UpdateErrorReason;
224
+ /** What was being fetched or applied, when the SDK knows. */
225
+ readonly resource?: UpdateErrorResource;
226
+ /** The response status when `reason` is `"http"`. */
227
+ readonly httpStatus?: number;
228
+ /**
229
+ * The storage origin's XML error `<Code>`, such as `AccessDenied`,
230
+ * `NoSuchKey`, or `ExpiredToken`, when an `http` failure's body names one.
231
+ * It tells an expired signed URL from a missing file.
232
+ */
233
+ readonly originCode?: string;
234
+ /** Why no response arrived, for a `network` failure, when the SDK knows. */
235
+ readonly transport?: UpdateErrorTransport;
236
+ /** Android 11+: why the previous main process exited (see `AppReadyResult`). */
237
+ readonly previousProcessExit?: string;
238
+ /** The bundle the update targeted; absent for the check stage. */
239
+ readonly targetBundleId?: string;
240
+ /** The Release the update targeted, when it names one. */
241
+ readonly targetReleaseId?: string;
242
+ readonly channel: string;
243
+ /** The running bundle and the Release it was selected as. */
244
+ readonly bundleId: string;
245
+ readonly releaseId: string | null;
246
+ readonly updateStrategy: UpdateStrategy;
247
+ /** The error the SDK caught. */
248
+ readonly cause: unknown;
249
+ }
250
+ /**
251
+ * Lifecycle hooks a plugin returns from `setup`. Hooks observe: the SDK
252
+ * never waits for one and ignores what it returns, and a hook that throws or
253
+ * rejects is reported through `onError`, or a console warning without one,
254
+ * so a plugin can never delay or change an update.
255
+ */
256
+ interface HotUpdaterClientHooks {
257
+ /** Called once per JavaScript runtime, after native reports the launch. */
258
+ onAppReady?(result: AppReadyResult): void | Promise<void>;
259
+ /** Called after each update check, and after a Release adoption commits. */
260
+ onUpdateCheck?(result: UpdateCheckResult): void | Promise<void>;
261
+ /** Called after the SDK stages a downloaded bundle. */
262
+ onBundleDownloaded?(info: BundleDownloadedInfo): void | Promise<void>;
263
+ /** Called when an update check, download, or install fails. */
264
+ onUpdateError?(error: UpdateError): void | Promise<void>;
265
+ }
266
+ /**
267
+ * A plugin's persistent key-value store. Native keeps it on the device,
268
+ * outside device backups, and the SDK scopes its keys to the plugin's id.
269
+ * It is meant for small values, up to 64 KB each.
270
+ */
271
+ interface HotUpdaterClientStorage {
272
+ get(key: string): string | null;
273
+ /** Writes the value; `null` removes the key. */
274
+ set(key: string, value: string | null): void;
275
+ }
276
+ interface HotUpdaterClientContext {
277
+ /**
278
+ * Requests a path relative to the configured `baseURL`, with the SDK's
279
+ * `requestHeaders` and `requestTimeout` applied. Rejects with
280
+ * `Request timed out` when the timeout passes before a response arrives.
281
+ */
282
+ fetch(path: string, init?: RequestInit): Promise<Response>;
283
+ /** A random id native creates once per installation. */
284
+ readonly installId: string;
285
+ readonly platform: "ios" | "android";
286
+ /** The native app version, or null when the native build has none. */
287
+ readonly appVersion: string | null;
288
+ readonly sdkVersion: string;
289
+ /** Whether this is a debug build (`__DEV__`). */
290
+ readonly isDebugBuild: boolean;
291
+ /** The bundle native reports as current. */
292
+ getBundleId(): string;
293
+ getChannel(): string;
294
+ getCohort(): string;
295
+ getFingerprintHash(): string | null;
296
+ readonly storage: HotUpdaterClientStorage;
297
+ /** The current time in milliseconds since the epoch. */
298
+ now(): number;
299
+ }
300
+ interface HotUpdaterClientPlugin {
301
+ /** Unique among an app's plugins; two plugins with one id throw at init. */
302
+ readonly id: string;
303
+ /**
304
+ * Called once, when `HotUpdater.init` or `HotUpdater.wrap` first
305
+ * configures the runtime with this plugin.
306
+ */
307
+ setup(context: HotUpdaterClientContext): HotUpdaterClientHooks | void;
308
+ }
309
+ /** Declares a client plugin; built-in and third-party plugins use the same contract. */
310
+ declare const defineClientPlugin: <const P extends HotUpdaterClientPlugin>(plugin: P) => P;
311
+ //#endregion
312
+ //#region src/createPluginHost.d.ts
313
+ type PluginHookName = keyof HotUpdaterClientHooks;
314
+ type PluginHookPayload<K extends PluginHookName> = Parameters<NonNullable<HotUpdaterClientHooks[K]>>[0];
315
+ /** The init or wrap settings plugins see through their context. */
316
+ interface PluginHostConfig {
317
+ readonly baseURL: HotUpdaterBaseURL;
318
+ readonly requestHeaders?: Record<string, string>;
319
+ readonly requestTimeout?: number;
320
+ readonly onError?: (error: unknown) => void;
321
+ }
322
+ /**
323
+ * What plugins read from the app and the device. A device SDK backs it with
324
+ * its native module; `@hot-updater/test-utils/react-native` backs it with
325
+ * fakes.
326
+ */
327
+ interface PluginHostEnvironment {
328
+ fetch(url: string, init: RequestInit): Promise<Response>;
329
+ readonly platform: "ios" | "android";
330
+ readonly isDebugBuild: boolean;
331
+ readonly sdkVersion: string;
332
+ getInstallId(): string;
333
+ getAppVersion(): string | null;
334
+ getBundleId(): string;
335
+ getChannel(): string;
336
+ getCohort(): string;
337
+ getFingerprintHash(): string | null;
338
+ getStorageItem(key: string): string | null;
339
+ setStorageItem(key: string, value: string | null): void;
340
+ now(): number;
341
+ }
342
+ interface PluginHost {
343
+ /**
344
+ * Sets up the plugins of an init or wrap call. A plugin passed to an
345
+ * earlier call keeps its hooks, and a plugin left out stops receiving
346
+ * events.
347
+ */
348
+ configurePlugins(plugins: readonly HotUpdaterClientPlugin[] | undefined, config: PluginHostConfig): void;
349
+ /** Whether any plugin listens to this hook, so callers build events only when one does. */
350
+ hasPluginHook(name: PluginHookName): boolean;
351
+ /**
352
+ * Builds a hook's event when a plugin listens. A failure to build it is
353
+ * reported, not thrown, since reporting never changes an update.
354
+ */
355
+ buildPluginEvent<K extends PluginHookName>(name: K, createPayload: () => PluginHookPayload<K> | null): PluginHookPayload<K> | null;
356
+ /**
357
+ * Calls a hook on every plugin that listens, without waiting for any. It
358
+ * returns when each asynchronous hook settles, for observers such as
359
+ * `@hot-updater/test-utils`; the SDK ignores it.
360
+ */
361
+ dispatchPluginHook<K extends PluginHookName>(name: K, payload: PluginHookPayload<K>): readonly Promise<void>[];
362
+ /** Builds a hook's event when a plugin listens and calls the hook right away. */
363
+ emitPluginHook<K extends PluginHookName>(name: K, createPayload: () => PluginHookPayload<K> | null): void;
364
+ }
365
+ /** Where a plugin's key lives in native storage. */
366
+ declare const pluginStorageKey: (pluginId: string, key: string) => string;
367
+ declare const createPluginHost: (environment: PluginHostEnvironment) => PluginHost;
368
+ //#endregion
369
+ //#region src/releaseCatalog.d.ts
370
+ declare const RELEASE_CATALOG_SCHEMA_VERSION: 1;
371
+ declare const RELEASE_CATALOG_FALLBACK_POLICY: "BUILTIN_IF_ACTIVE_INELIGIBLE";
372
+ declare const MAX_CRASHED_BUNDLES = 10;
373
+ declare const MAX_TARGET_COHORTS_PER_RELEASE = 100;
374
+ declare const MAX_DISTINCT_TARGET_COHORTS_PER_SCOPE = 512;
375
+ declare const MAX_COMPILED_CATALOG_BYTES: number;
376
+ type ReleaseKind = "BUNDLE" | "EMBEDDED";
377
+ type ReleaseStrategy = "APP_VERSION" | "FINGERPRINT";
378
+ type ReleaseOperation = "DEPLOY" | "PROMOTE" | "ROLLBACK";
379
+ type SelectionKind = ReleaseKind | "BUILTIN";
380
+ interface Release {
381
+ readonly id: string;
382
+ readonly revision: number;
383
+ readonly channelId: string;
384
+ readonly platform: "ios" | "android";
385
+ readonly kind: ReleaseKind;
386
+ readonly bundleId: string | null;
387
+ readonly strategy: ReleaseStrategy;
388
+ readonly targetAppVersion: string | null;
389
+ readonly fingerprintHash: string | null;
390
+ readonly enabled: boolean;
391
+ readonly shouldForceUpdate: boolean;
392
+ readonly message: string | null;
393
+ readonly rolloutCohortCount: number;
394
+ readonly targetCohorts: readonly string[];
395
+ readonly operation: ReleaseOperation;
396
+ readonly sourceReleaseId: string | null;
397
+ readonly createdAtMs: number;
398
+ readonly updatedAtMs: number;
399
+ }
400
+ interface ReleaseCatalogDescriptor {
401
+ readonly releaseId: string;
402
+ readonly kind: ReleaseKind;
403
+ readonly bundleId: string | null;
404
+ readonly rolloutCohortCount: number;
405
+ readonly targetCohorts: readonly string[];
406
+ readonly shouldForceUpdate: boolean;
407
+ readonly message: string | null;
408
+ }
409
+ interface ReleaseCatalog {
410
+ readonly schemaVersion: typeof RELEASE_CATALOG_SCHEMA_VERSION;
411
+ readonly catalogId: string;
412
+ readonly scopeKey: string;
413
+ readonly generation: number;
414
+ readonly catalogHash: string;
415
+ readonly fallbackPolicy: typeof RELEASE_CATALOG_FALLBACK_POLICY;
416
+ /** Release descriptors ordered newest first. */
417
+ readonly releases: readonly ReleaseCatalogDescriptor[];
418
+ /** Enabled compatible Releases ordered newest first for v0-style rollback. */
419
+ readonly rollbackReleases?: readonly ReleaseCatalogDescriptor[];
420
+ }
421
+ interface CatalogHighWater {
422
+ readonly generation: number;
423
+ readonly catalogHash: string;
424
+ }
425
+ interface PersistedSelectionReceipt {
426
+ readonly kind: SelectionKind;
427
+ readonly releaseId: string | null;
428
+ readonly bundleId: string;
429
+ readonly catalogId: string | null;
430
+ readonly scopeKey: string | null;
431
+ readonly generation: number | null;
432
+ readonly catalogHash: string | null;
433
+ readonly channel: string;
434
+ readonly selectionContextHash: string | null;
435
+ }
436
+ interface ReleaseSelectionInput {
437
+ readonly builtInBundleId: string;
438
+ readonly currentBundleId: string;
439
+ readonly activeReleaseId?: string | null;
440
+ readonly minimumReleaseId: string;
441
+ readonly cohort: string | null | undefined;
442
+ readonly crashedBundleIds: readonly string[];
443
+ }
444
+ interface ReleaseSelectionContextInput {
445
+ readonly activeBundleId?: string | null;
446
+ readonly activeReleaseId?: string | null;
447
+ readonly cohort: string | null | undefined;
448
+ readonly minimumReleaseId: string;
449
+ readonly strategy: ReleaseStrategy;
450
+ readonly strategyValue: string;
451
+ readonly crashedBundleIds: readonly string[];
452
+ }
453
+ declare function createReleaseSelectionContextHash(input: ReleaseSelectionContextInput): string;
454
+ type DesiredRelease = {
455
+ readonly kind: SelectionKind;
456
+ readonly releaseId: string | null;
457
+ readonly bundleId: string;
458
+ readonly release: ReleaseCatalogDescriptor | null;
459
+ readonly status: "UPDATE" | "ROLLBACK";
460
+ };
461
+ declare function isReleaseEligibleForCohort(release: Pick<ReleaseCatalogDescriptor, "releaseId" | "rolloutCohortCount" | "targetCohorts">, cohort: string | null | undefined): boolean;
462
+ declare function selectDesiredRelease(catalog: ReleaseCatalog, input: ReleaseSelectionInput): DesiredRelease | null;
463
+ type CatalogAcceptance = {
464
+ readonly accepted: true;
465
+ readonly shouldAdvanceHighWater: boolean;
466
+ } | {
467
+ readonly accepted: false;
468
+ readonly reason: "STALE_GENERATION" | "GENERATION_HASH_MISMATCH";
469
+ };
470
+ declare function assessCatalogAcceptance(incoming: CatalogHighWater, highestSeen: CatalogHighWater | null): CatalogAcceptance;
471
+ type ReleaseTransitionAuthorization = {
472
+ readonly authorized: true;
473
+ readonly reason: "FIRST_AUTHENTICATED_SELECTION" | "NEWER_POLICY" | "CONTEXT_RESELECTION" | "EXPLICIT_SCOPE_SWITCH";
474
+ } | {
475
+ readonly authorized: false;
476
+ readonly reason: "BACKWARD_NOT_AUTHORIZED" | "EMPTY_TARGET_SCOPE" | "STALE_GENERATION" | "UNSOLICITED_SCOPE";
477
+ };
478
+ declare function authorizeReleaseTransition(input: {
479
+ readonly active: PersistedSelectionReceipt | null;
480
+ readonly desired: PersistedSelectionReceipt;
481
+ readonly explicitScopeSwitch: boolean;
482
+ }): ReleaseTransitionAuthorization;
483
+ //#endregion
484
+ //#region src/releaseCatalogScope.d.ts
485
+ declare function normalizeChannelName(channel: string): string;
486
+ declare function assertCanonicalChannelName(channel: string): void;
487
+ declare function encodeChannelKey(channel: string): string;
488
+ declare function decodeChannelKey(channelKey: string): string;
489
+ type ReleaseCatalogScopeKeyInput = {
490
+ readonly platform: Platform;
491
+ readonly channelKey: string;
492
+ } & ({
493
+ readonly strategy: "APP_VERSION";
494
+ readonly fingerprintHash?: never;
495
+ } | {
496
+ readonly strategy: "FINGERPRINT";
497
+ readonly fingerprintHash: string;
498
+ });
499
+ declare function createReleaseCatalogScopeKey(input: ReleaseCatalogScopeKeyInput): string;
500
+ declare function parseReleaseCatalogScopeKey(scopeKey: string): ReleaseCatalogScopeKeyInput;
501
+ //#endregion
502
+ //#region src/releaseCatalogWire.d.ts
503
+ /** The largest release catalog response body a client accepts, in UTF-8 bytes. */
504
+ declare const MAX_RELEASE_CATALOG_WIRE_BYTES: number;
505
+ /** The scope a client asked for: the catalog it accepts must be this scope's. */
506
+ type ExpectedReleaseCatalogScope = {
507
+ readonly channelKey: string;
508
+ readonly platform: "ios" | "android";
509
+ } & ({
510
+ readonly strategy: "APP_VERSION";
511
+ readonly fingerprintHash?: never;
512
+ } | {
513
+ readonly strategy: "FINGERPRINT";
514
+ readonly fingerprintHash: string;
515
+ });
516
+ /** The length of `value` in UTF-8 bytes, without an encoder the device may lack. */
517
+ declare const getUtf8ByteLength: (value: string) => number;
518
+ /** Whether the catalog is the expected scope's. */
519
+ declare const hasExpectedReleaseCatalogScope: (catalog: Pick<ReleaseCatalog, "scopeKey">, expected: ExpectedReleaseCatalogScope) => boolean;
520
+ /**
521
+ * Parses a release catalog response body as a client accepts it: within the
522
+ * wire limit, in the schema's shape, for the expected scope, and within the
523
+ * cohort limits. Any other body is null. The device's update client and
524
+ * doctor's server checks both run this, so they accept the same catalogs.
525
+ */
526
+ declare const parseReleaseCatalog: (body: string, expectedScope: ExpectedReleaseCatalogScope) => ReleaseCatalog | null;
527
+ //#endregion
528
+ //#region src/rollout.d.ts
529
+ declare const NUMERIC_COHORT_SIZE = 1000;
530
+ declare const DEFAULT_ROLLOUT_COHORT_COUNT = 1000;
531
+ declare const MAX_COHORT_LENGTH = 64;
532
+ declare const INVALID_COHORT_ERROR_MESSAGE = "Invalid cohort. Use 1-1000 or a lowercase slug without spaces, up to 64 characters.";
533
+ declare function normalizeRolloutCohortCount(rolloutCohortCount: number | null | undefined): number;
534
+ declare function normalizeCohortValue(cohort: string): string;
535
+ declare function getNumericCohortValue(cohort: string): number | null;
536
+ declare function isNumericCohort(cohort: string): boolean;
537
+ declare function isCustomCohort(cohort: string): boolean;
538
+ declare function isValidCohort(cohort: string): boolean;
539
+ declare function getDefaultNumericCohort(identifier: string): string;
540
+ declare function getNumericCohortRolloutPosition(bundleId: string, cohortValue: number): number;
541
+ declare function getRolledOutNumericCohorts(bundleId: string, rolloutCohortCount: number | null | undefined): number[];
542
+ declare function isCohortEligibleForUpdate(bundleId: string, cohort: string | null | undefined, rolloutCohortCount: number | null | undefined, targetCohorts: readonly string[] | null | undefined): boolean;
543
+ //#endregion
544
+ //#region src/uuid.d.ts
545
+ declare const NIL_UUID = "00000000-0000-0000-0000-000000000000";
546
+ declare const isUUIDv7: (value: unknown) => value is string;
547
+ //#endregion
548
+ export { ARTIFACT_PROTOCOL_VERSION, AppReadyResult, ArtifactAsset, ArtifactAssetFile, ArtifactInfo, Bundle, BundleDownloadedInfo, BundleMetadata, BundlePatchArtifact, CatalogAcceptance, CatalogHighWater, ChangedAssetPatch, DEFAULT_ROLLOUT_COHORT_COUNT, DesiredRelease, ExpectedReleaseCatalogScope, HotUpdaterBaseURL, HotUpdaterClientContext, HotUpdaterClientHooks, HotUpdaterClientPlugin, HotUpdaterClientStorage, INVALID_COHORT_ERROR_MESSAGE, MAX_COHORT_LENGTH, MAX_COMPILED_CATALOG_BYTES, MAX_CRASHED_BUNDLES, MAX_DISTINCT_TARGET_COHORTS_PER_SCOPE, MAX_RELEASE_CATALOG_WIRE_BYTES, MAX_TARGET_COHORTS_PER_RELEASE, ManifestArchive, NIL_UUID, NUMERIC_COHORT_SIZE, PersistedSelectionReceipt, Platform, PluginHookName, PluginHookPayload, PluginHost, PluginHostConfig, PluginHostEnvironment, RELEASE_CATALOG_FALLBACK_POLICY, RELEASE_CATALOG_SCHEMA_VERSION, Release, ReleaseCatalog, ReleaseCatalogDescriptor, ReleaseCatalogScopeKeyInput, ReleaseKind, ReleaseOperation, ReleaseSelectionContextInput, ReleaseSelectionInput, ReleaseStrategy, ReleaseTransitionAuthorization, ReleaseTransitionKind, SelectionKind, UpdateCheckResult, UpdateError, UpdateErrorReason, UpdateErrorResource, UpdateErrorStage, UpdateErrorTransport, UpdateStatus, UpdateStrategy, assertCanonicalChannelName, assessCatalogAcceptance, authorizeReleaseTransition, canonicalizeAppVersion, createPluginHost, createReleaseCatalogScopeKey, createReleaseSelectionContextHash, decodeChannelKey, defineClientPlugin, encodeChannelKey, getAssetBaseStorageUri, getBundlePatch, getBundlePatches, getDefaultNumericCohort, getManifestFileHash, getManifestStorageUri, getNumericCohortRolloutPosition, getNumericCohortValue, getPatchBaseBundleId, getPatchBaseFileHash, getPatchFileHash, getPatchStorageUri, getRolledOutNumericCohorts, getUtf8ByteLength, hasExpectedReleaseCatalogScope, isCohortEligibleForUpdate, isCustomCohort, isNumericCohort, isReleaseEligibleForCohort, isUUIDv7, isValidCohort, normalizeChannelName, normalizeCohortValue, normalizeRolloutCohortCount, parseReleaseCatalog, parseReleaseCatalogScopeKey, pluginStorageKey, resolveBaseURL, selectDesiredRelease, stripBundleArtifactMetadata };