@absolutejs/absolute 0.20.0-beta.81 → 0.20.0-beta.83

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.
package/dist/index.js CHANGED
@@ -12288,7 +12288,7 @@ import {
12288
12288
  } from "crypto";
12289
12289
  import { readFile as readFile4, stat as stat2 } from "fs/promises";
12290
12290
  import path from "path";
12291
- var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updates", MAX_FILE_BYTES, MAX_TOTAL_BYTES, HASH, RELEASE, APP_ID, NAME, EXPO_DESCRIPTOR = "_absolute/expo-update.json", EXPO_CODE_SIGNING_ALGORITHM = "rsa-v1_5-sha256", MobileUpdateRegistryError, object3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), text2 = (value, field) => {
12291
+ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updates", MAX_FILE_BYTES, MAX_TOTAL_BYTES, DAY_MS, DEFAULT_MIN_AGE_MS, DEFAULT_GRACE_PERIOD_MS, DEFAULT_RETAIN_RECENT = 5, HASH, RELEASE, APP_ID, NAME, EXPO_DESCRIPTOR = "_absolute/expo-update.json", EXPO_CODE_SIGNING_ALGORITHM = "rsa-v1_5-sha256", MobileUpdateRegistryError, object3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), text2 = (value, field) => {
12292
12292
  if (typeof value !== "string" || value.length === 0)
12293
12293
  throw new MobileUpdateRegistryError(`Mobile update ${field} is invalid`);
12294
12294
  return value;
@@ -12455,6 +12455,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
12455
12455
  const releaseRoot = (manifest) => `${root(manifest.appId)}/releases/${manifest.releaseId}`;
12456
12456
  const manifestKey = (manifest) => `${releaseRoot(manifest)}/update.json`;
12457
12457
  const fileKey = (manifest, file2) => `${releaseRoot(manifest)}/files/${file2.path}`;
12458
+ const tombstoneKey = (appId, releaseId) => `${root(appId)}/gc/${releaseId}.json`;
12458
12459
  const channelKey = (appId, channel) => {
12459
12460
  if (!APP_ID.test(appId) || !NAME.test(channel))
12460
12461
  throw new MobileUpdateRegistryError("Mobile update channel identity is invalid");
@@ -12483,6 +12484,12 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
12483
12484
  throw new MobileUpdateRegistryError("Stored mobile update channel identity changed");
12484
12485
  return value;
12485
12486
  };
12487
+ const assertNotMarked = async (appId, releaseId) => {
12488
+ if (!APP_ID.test(appId) || !RELEASE.test(releaseId))
12489
+ throw new MobileUpdateRegistryError("Mobile update release identity is invalid");
12490
+ if (await options.store.head(tombstoneKey(appId, releaseId)))
12491
+ throw new MobileUpdateRegistryError("Mobile update release is marked for collection. Increase retention and apply garbage collection to restore it before promotion");
12492
+ };
12486
12493
  const writeChannel = async (input, signal) => {
12487
12494
  const value = {
12488
12495
  ...input,
@@ -12507,6 +12514,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
12507
12514
  input.signal?.throwIfAborted();
12508
12515
  if (input.rollout <= 0 || input.rollout > 1)
12509
12516
  throw new MobileUpdateRegistryError("Mobile update rollout is invalid");
12517
+ await assertNotMarked(input.appId, input.releaseId);
12510
12518
  const release = await readManifest(input.appId, input.releaseId);
12511
12519
  if (!release || release.manifest.channel !== input.channel)
12512
12520
  throw new MobileUpdateRegistryError("Mobile update was not published to this channel");
@@ -12552,11 +12560,221 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
12552
12560
  status: "selected"
12553
12561
  };
12554
12562
  };
12563
+ const retentionValues = (input) => {
12564
+ if (!APP_ID.test(input.appId))
12565
+ throw new MobileUpdateRegistryError("Mobile update appId is invalid");
12566
+ const retainRecent = input.retainRecent ?? DEFAULT_RETAIN_RECENT;
12567
+ const minAgeMs = input.minAgeMs ?? DEFAULT_MIN_AGE_MS;
12568
+ if (!Number.isSafeInteger(retainRecent) || retainRecent < 0)
12569
+ throw new MobileUpdateRegistryError("Mobile update retained release count is invalid");
12570
+ if (!Number.isSafeInteger(minAgeMs) || minAgeMs < 0)
12571
+ throw new MobileUpdateRegistryError("Mobile update minimum release age is invalid");
12572
+ return { minAgeMs, retainRecent };
12573
+ };
12574
+ const listObjects = async (appId, signal) => {
12575
+ const list = options.store.list;
12576
+ if (!list)
12577
+ throw new MobileUpdateRegistryError("Mobile update storage does not support lifecycle listing");
12578
+ const objects = [];
12579
+ const cursors = new Set;
12580
+ let cursor;
12581
+ do {
12582
+ signal?.throwIfAborted();
12583
+ const page = await list({
12584
+ ...cursor ? { cursor } : {},
12585
+ prefix: `${root(appId)}/`
12586
+ });
12587
+ objects.push(...page.objects);
12588
+ if (!page.truncated)
12589
+ break;
12590
+ if (!page.cursor || cursors.has(page.cursor))
12591
+ throw new MobileUpdateRegistryError("Mobile update storage returned an invalid lifecycle cursor");
12592
+ cursors.add(page.cursor);
12593
+ cursor = page.cursor;
12594
+ } while (true);
12595
+ return objects;
12596
+ };
12597
+ const inventory = async (input) => {
12598
+ const { minAgeMs, retainRecent } = retentionValues(input);
12599
+ const objects = await listObjects(input.appId, input.signal);
12600
+ const appRoot = `${root(input.appId)}/`;
12601
+ const releasePrefix = `${appRoot}releases/`;
12602
+ const channelPrefix = `${appRoot}channels/`;
12603
+ const markerPrefix = `${appRoot}gc/`;
12604
+ const objectKeysByRelease = new Map;
12605
+ const objectBytesByRelease = new Map;
12606
+ for (const blobObject of objects) {
12607
+ if (!blobObject.key.startsWith(releasePrefix))
12608
+ continue;
12609
+ const suffix = blobObject.key.slice(releasePrefix.length);
12610
+ const releaseId = suffix.slice(0, suffix.indexOf("/"));
12611
+ if (!RELEASE.test(releaseId))
12612
+ continue;
12613
+ objectKeysByRelease.set(releaseId, [
12614
+ ...objectKeysByRelease.get(releaseId) ?? [],
12615
+ blobObject.key
12616
+ ]);
12617
+ objectBytesByRelease.set(releaseId, (objectBytesByRelease.get(releaseId) ?? 0) + blobObject.size);
12618
+ }
12619
+ const channels = [];
12620
+ for (const blobObject of objects) {
12621
+ if (!blobObject.key.startsWith(channelPrefix) || !blobObject.key.endsWith(".json"))
12622
+ continue;
12623
+ const bytes = await options.store.get(blobObject.key);
12624
+ if (!bytes)
12625
+ throw new MobileUpdateRegistryError("Mobile update channel disappeared during lifecycle inspection");
12626
+ const channel = parseChannel(decode(bytes));
12627
+ if (channel.appId !== input.appId)
12628
+ throw new MobileUpdateRegistryError("Stored mobile update channel identity changed");
12629
+ channels.push(channel);
12630
+ }
12631
+ const markedAt = new Map;
12632
+ for (const blobObject of objects) {
12633
+ if (!blobObject.key.startsWith(markerPrefix) || !blobObject.key.endsWith(".json"))
12634
+ continue;
12635
+ const releaseId = blobObject.key.slice(markerPrefix.length, -".json".length);
12636
+ if (!RELEASE.test(releaseId))
12637
+ continue;
12638
+ const bytes = await options.store.get(blobObject.key);
12639
+ if (!bytes)
12640
+ continue;
12641
+ const marker = decode(bytes);
12642
+ if (!object3(marker) || marker.format !== 1 || marker.appId !== input.appId || marker.releaseId !== releaseId || !iso(marker.markedAt))
12643
+ throw new MobileUpdateRegistryError("Mobile update collection marker is invalid");
12644
+ markedAt.set(releaseId, marker.markedAt);
12645
+ }
12646
+ const active = new Set(channels.flatMap((channel) => channel.releaseId ? [channel.releaseId] : []));
12647
+ const fallback = new Set(channels.flatMap((channel) => channel.fallbackReleaseId ? [channel.fallbackReleaseId] : []));
12648
+ const manifests = [];
12649
+ for (const releaseId of objectKeysByRelease.keys()) {
12650
+ const release = await readManifest(input.appId, releaseId);
12651
+ if (release)
12652
+ manifests.push(release.manifest);
12653
+ }
12654
+ const recent = new Set;
12655
+ const manifestsByChannel = new Map;
12656
+ for (const manifest of manifests)
12657
+ manifestsByChannel.set(manifest.channel, [
12658
+ ...manifestsByChannel.get(manifest.channel) ?? [],
12659
+ manifest
12660
+ ]);
12661
+ for (const releases2 of manifestsByChannel.values())
12662
+ for (const manifest of releases2.toSorted((left, right) => right.createdAt.localeCompare(left.createdAt)).slice(0, retainRecent))
12663
+ recent.add(manifest.releaseId);
12664
+ const now = clock().getTime();
12665
+ const releases = manifests.map((manifest) => {
12666
+ const protectedBy = [];
12667
+ if (active.has(manifest.releaseId))
12668
+ protectedBy.push("active");
12669
+ if (fallback.has(manifest.releaseId))
12670
+ protectedBy.push("fallback");
12671
+ if (recent.has(manifest.releaseId))
12672
+ protectedBy.push("recent");
12673
+ if (now - Date.parse(manifest.createdAt) < minAgeMs)
12674
+ protectedBy.push("age");
12675
+ return {
12676
+ bytes: objectBytesByRelease.get(manifest.releaseId) ?? 0,
12677
+ channel: manifest.channel,
12678
+ createdAt: manifest.createdAt,
12679
+ ...markedAt.has(manifest.releaseId) ? { markedAt: markedAt.get(manifest.releaseId) } : {},
12680
+ objectCount: objectKeysByRelease.get(manifest.releaseId)?.length ?? 0,
12681
+ protectedBy,
12682
+ releaseId: manifest.releaseId
12683
+ };
12684
+ }).toSorted((left, right) => right.createdAt.localeCompare(left.createdAt));
12685
+ const releaseBytes = releases.reduce((total, release) => total + release.bytes, 0);
12686
+ const totalBytes = objects.reduce((total, object22) => total + object22.size, 0);
12687
+ return {
12688
+ objectKeysByRelease,
12689
+ report: {
12690
+ appId: input.appId,
12691
+ channelCount: channels.length,
12692
+ reclaimableBytes: releases.filter((release) => release.protectedBy.length === 0).reduce((total, release) => total + release.bytes, 0),
12693
+ releaseBytes,
12694
+ releaseCount: releases.length,
12695
+ releases,
12696
+ totalBytes,
12697
+ totalObjectCount: objects.length,
12698
+ untrackedBytes: totalBytes - releaseBytes
12699
+ }
12700
+ };
12701
+ };
12702
+ const pruneUpdates = async (input) => {
12703
+ const initial = await inventory(input);
12704
+ const result = {
12705
+ ...initial.report,
12706
+ dryRun: input.apply !== true,
12707
+ marked: [],
12708
+ reclaimedBytes: 0,
12709
+ restored: [],
12710
+ swept: []
12711
+ };
12712
+ if (input.apply !== true)
12713
+ return result;
12714
+ const remove = options.store.delete;
12715
+ if (!remove)
12716
+ throw new MobileUpdateRegistryError("Mobile update storage does not support lifecycle deletion");
12717
+ const gracePeriodMs = input.gracePeriodMs ?? DEFAULT_GRACE_PERIOD_MS;
12718
+ if (!Number.isSafeInteger(gracePeriodMs) || gracePeriodMs < 0)
12719
+ throw new MobileUpdateRegistryError("Mobile update collection grace period is invalid");
12720
+ const now = clock();
12721
+ for (const release of initial.report.releases) {
12722
+ input.signal?.throwIfAborted();
12723
+ const marker = tombstoneKey(input.appId, release.releaseId);
12724
+ if (release.protectedBy.length > 0) {
12725
+ if (release.markedAt) {
12726
+ await remove(marker);
12727
+ result.restored.push(release.releaseId);
12728
+ }
12729
+ continue;
12730
+ }
12731
+ if (!release.markedAt) {
12732
+ const bytes = json({
12733
+ appId: input.appId,
12734
+ format: 1,
12735
+ markedAt: now.toISOString(),
12736
+ releaseId: release.releaseId
12737
+ });
12738
+ await options.store.put(marker, bytes, {
12739
+ cacheControl: "no-cache",
12740
+ contentType: "application/json",
12741
+ maxBytes: bytes.byteLength,
12742
+ metadata: { releaseid: release.releaseId, sha256: digest(bytes) },
12743
+ signal: input.signal
12744
+ });
12745
+ result.marked.push(release.releaseId);
12746
+ }
12747
+ }
12748
+ const current = await inventory(input);
12749
+ for (const release of current.report.releases) {
12750
+ input.signal?.throwIfAborted();
12751
+ if (release.protectedBy.length > 0 || !release.markedAt || now.getTime() - Date.parse(release.markedAt) < gracePeriodMs || result.marked.includes(release.releaseId))
12752
+ continue;
12753
+ const checked = await inventory(input);
12754
+ const candidate = checked.report.releases.find((value) => value.releaseId === release.releaseId);
12755
+ if (!candidate || candidate.protectedBy.length > 0 || !candidate.markedAt)
12756
+ continue;
12757
+ const keys = checked.objectKeysByRelease.get(release.releaseId) ?? [];
12758
+ for (const key of keys.toSorted((left, right) => {
12759
+ const leftManifest = left.endsWith("/update.json");
12760
+ const rightManifest = right.endsWith("/update.json");
12761
+ return Number(leftManifest) - Number(rightManifest);
12762
+ }))
12763
+ await remove(key);
12764
+ await remove(tombstoneKey(input.appId, release.releaseId));
12765
+ result.reclaimedBytes += candidate.bytes;
12766
+ result.swept.push(release.releaseId);
12767
+ }
12768
+ return result;
12769
+ };
12555
12770
  return {
12771
+ inspectUpdateStorage: async (input) => (await inventory(input)).report,
12772
+ pruneUpdates,
12556
12773
  publishUpdate: async (input) => {
12557
12774
  input.signal?.throwIfAborted();
12558
12775
  const manifest = parseMobileUpdateManifest(input.manifest);
12559
12776
  verifyManifestSignature(manifest, options.publicKeys);
12777
+ await assertNotMarked(manifest.appId, manifest.releaseId);
12560
12778
  const localRoot = path.resolve(input.releaseDirectory);
12561
12779
  const localManifest = parseMobileUpdateManifest(JSON.parse(await readFile4(path.join(localRoot, "update.json"), "utf8")));
12562
12780
  if (JSON.stringify(localManifest) !== JSON.stringify(manifest))
@@ -12580,10 +12798,10 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
12580
12798
  cacheControl: "public, max-age=31536000, immutable",
12581
12799
  contentType: "application/octet-stream",
12582
12800
  maxBytes: file2.bytes,
12583
- metadata: { releaseId: manifest.releaseId, sha256: file2.sha256 },
12801
+ metadata: { releaseid: manifest.releaseId, sha256: file2.sha256 },
12584
12802
  signal: input.signal
12585
12803
  });
12586
- } else if (stored.size !== file2.bytes || stored.metadata?.sha256 !== file2.sha256 || stored.metadata?.releaseId !== manifest.releaseId)
12804
+ } else if (stored.size !== file2.bytes || stored.metadata?.sha256 !== file2.sha256 || (stored.metadata?.releaseid ?? stored.metadata?.releaseId) !== manifest.releaseId)
12587
12805
  throw new MobileUpdateRegistryError("Stored mobile update file identity changed");
12588
12806
  }
12589
12807
  const bytes = json(manifest);
@@ -12591,7 +12809,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
12591
12809
  cacheControl: "public, max-age=31536000, immutable",
12592
12810
  contentType: "application/json",
12593
12811
  maxBytes: bytes.byteLength,
12594
- metadata: { releaseId: manifest.releaseId, sha256: digest(bytes) },
12812
+ metadata: { releaseid: manifest.releaseId, sha256: digest(bytes) },
12595
12813
  signal: input.signal
12596
12814
  });
12597
12815
  if (!await readManifest(manifest.appId, manifest.releaseId))
@@ -12621,6 +12839,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
12621
12839
  if (!existing)
12622
12840
  throw new MobileUpdateRegistryError("Mobile update channel does not exist");
12623
12841
  if (input.releaseId) {
12842
+ await assertNotMarked(input.appId, input.releaseId);
12624
12843
  const release = await readManifest(input.appId, input.releaseId);
12625
12844
  if (!release || release.manifest.channel !== input.channel)
12626
12845
  throw new MobileUpdateRegistryError("Mobile rollback release was not published");
@@ -12664,7 +12883,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
12664
12883
  ]);
12665
12884
  if (!bytes || !head)
12666
12885
  return null;
12667
- if (bytes.byteLength !== file2.bytes || head.size !== file2.bytes || head.metadata?.sha256 !== file2.sha256 || head.metadata?.releaseId !== release.manifest.releaseId || digest(bytes) !== file2.sha256)
12886
+ if (bytes.byteLength !== file2.bytes || head.size !== file2.bytes || head.metadata?.sha256 !== file2.sha256 || (head.metadata?.releaseid ?? head.metadata?.releaseId) !== release.manifest.releaseId || digest(bytes) !== file2.sha256)
12668
12887
  throw new MobileUpdateRegistryError("Stored mobile update file integrity failed");
12669
12888
  return { bytes, file: file2 };
12670
12889
  }
@@ -12953,6 +13172,9 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
12953
13172
  var init_mobileUpdate = __esm(() => {
12954
13173
  MAX_FILE_BYTES = 32 * 1024 * 1024;
12955
13174
  MAX_TOTAL_BYTES = 128 * 1024 * 1024;
13175
+ DAY_MS = 24 * 60 * 60 * 1000;
13176
+ DEFAULT_MIN_AGE_MS = 30 * DAY_MS;
13177
+ DEFAULT_GRACE_PERIOD_MS = 7 * DAY_MS;
12956
13178
  HASH = /^[a-f0-9]{64}$/;
12957
13179
  RELEASE = /^amu_[a-f0-9]{64}$/;
12958
13180
  APP_ID = /^[A-Za-z][\w]*(?:\.[A-Za-z][\w]*)+$/;
@@ -13023,10 +13245,27 @@ var ABSOLUTE_MOBILE_UPDATE_SERVER_FORMAT = 1, DEFAULT_MOBILE_UPDATE_REGISTRY_MOD
13023
13245
  const registry2 = loaded.default ?? loaded.registry;
13024
13246
  if (!isRegistry2(registry2))
13025
13247
  throw new TypeError("Mobile update registry must implement publication, promotion, rollback, resolution, and file reads.");
13248
+ const metadata2 = serverMetadata(loaded.absoluteMobileUpdateServer);
13249
+ const verifier = loaded.verifyAbsoluteMobileUpdateServer;
13250
+ if (metadata2.storage === "durable" && typeof verifier !== "function")
13251
+ throw new TypeError("Mobile update registries marked durable must export verifyAbsoluteMobileUpdateServer(). Re-run `absolute mobile update provision --storage s3 --force` or provide an active durability check.");
13026
13252
  return {
13027
- metadata: serverMetadata(loaded.absoluteMobileUpdateServer),
13028
- registry: registry2
13253
+ metadata: metadata2,
13254
+ registry: registry2,
13255
+ ...typeof verifier === "function" ? {
13256
+ verifyDurability: async () => {
13257
+ await verifier();
13258
+ }
13259
+ } : {}
13029
13260
  };
13261
+ }, verifyDurableModule = async (module) => {
13262
+ if (module.metadata.storage !== "durable")
13263
+ throw new TypeError("Mobile production updates require durable object storage. Re-run `absolute mobile update provision --storage s3 --force` or configure a durable adapter.");
13264
+ try {
13265
+ await module.verifyDurability?.();
13266
+ } catch (error) {
13267
+ throw new TypeError(`Durable mobile update storage verification failed for ${module.metadata.provider}. Check the bucket, endpoint, credentials, and read/write/delete permissions.`, { cause: error });
13268
+ }
13030
13269
  }, expoSigningOptions = (config) => {
13031
13270
  if (!config.updates?.expoCodeSigning)
13032
13271
  return;
@@ -13058,8 +13297,8 @@ var ABSOLUTE_MOBILE_UPDATE_SERVER_FORMAT = 1, DEFAULT_MOBILE_UPDATE_REGISTRY_MOD
13058
13297
  if (!updates || !server?.autoMount)
13059
13298
  return new Elysia4({ name: "absolutejs-mobile-updates-disabled" });
13060
13299
  const module = await loadAbsoluteMobileUpdateServerModule(projectRoot, server.registryModule);
13061
- if (options.production && module.metadata.storage !== "durable")
13062
- throw new TypeError("Mobile production updates require durable object storage. Re-run `absolute mobile update provision --storage s3 --force` or configure a durable adapter.");
13300
+ if (options.production)
13301
+ await verifyDurableModule(module);
13063
13302
  const manifest = new URL(updates.manifestUrl);
13064
13303
  if (!manifest.pathname.endsWith("/update.json"))
13065
13304
  throw new TypeError("Auto-mounted mobile update manifests must end in /update.json.");
@@ -13077,8 +13316,7 @@ var ABSOLUTE_MOBILE_UPDATE_SERVER_FORMAT = 1, DEFAULT_MOBILE_UPDATE_REGISTRY_MOD
13077
13316
  if (!config.updates)
13078
13317
  return;
13079
13318
  const module = await loadAbsoluteMobileUpdateServerModule(projectRoot, config.updateServer?.registryModule);
13080
- if (module.metadata.storage !== "durable")
13081
- throw new TypeError("Mobile production updates require durable object storage; the configured registry is local-only.");
13319
+ await verifyDurableModule(module);
13082
13320
  if (config.engine === "expo")
13083
13321
  expoSigningOptions(config);
13084
13322
  return module.metadata;
@@ -13105,7 +13343,8 @@ export default createMobileUpdateRegistry({
13105
13343
  store
13106
13344
  });
13107
13345
  `;
13108
- return `import { S3Client } from '@aws-sdk/client-s3';
13346
+ return `import { randomUUID } from 'node:crypto';
13347
+ import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
13109
13348
  import { awsS3BlobStore } from '@absolutejs/blob/aws-s3';
13110
13349
  import { createMobileUpdateRegistry } from '@absolutejs/deploy/mobile-update';
13111
13350
 
@@ -13117,6 +13356,7 @@ const required = (name: string) => {
13117
13356
  return value;
13118
13357
  };
13119
13358
 
13359
+ const bucket = required('ABSOLUTE_MOBILE_UPDATE_S3_BUCKET');
13120
13360
  const client = new S3Client({
13121
13361
  region: process.env.ABSOLUTE_MOBILE_UPDATE_S3_REGION ?? 'auto',
13122
13362
  forcePathStyle: process.env.ABSOLUTE_MOBILE_UPDATE_S3_FORCE_PATH_STYLE === '1',
@@ -13124,10 +13364,22 @@ const client = new S3Client({
13124
13364
  ? { endpoint: process.env.ABSOLUTE_MOBILE_UPDATE_S3_ENDPOINT }
13125
13365
  : {})
13126
13366
  });
13127
- const store = awsS3BlobStore({
13128
- bucket: required('ABSOLUTE_MOBILE_UPDATE_S3_BUCKET'),
13129
- client
13130
- });
13367
+ const store = awsS3BlobStore({ bucket, client });
13368
+
13369
+ export const verifyAbsoluteMobileUpdateServer = async () => {
13370
+ const key = \`absolutejs/mobile-updates/_health/\${randomUUID()}\`;
13371
+ const expected = randomUUID();
13372
+ let stored = false;
13373
+ try {
13374
+ await client.send(new PutObjectCommand({ Bucket: bucket, Key: key, Body: expected }));
13375
+ stored = true;
13376
+ const response = await client.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
13377
+ if ((await response.Body?.transformToString()) !== expected)
13378
+ throw new Error('Durability probe read did not match its write.');
13379
+ } finally {
13380
+ if (stored) await client.send(new DeleteObjectCommand({ Bucket: bucket, Key: key }));
13381
+ }
13382
+ };
13131
13383
 
13132
13384
  export default createMobileUpdateRegistry({
13133
13385
  publicKeys: ${publicKeysSource(options.publicKeys)},
@@ -44557,5 +44809,5 @@ export {
44557
44809
  wrapPageHandlerWithStreamingSlots
44558
44810
  };
44559
44811
 
44560
- //# debugId=3A3B05A58CB535F264756E2164756E21
44812
+ //# debugId=F0D48E431F12D4EB64756E2164756E21
44561
44813
  //# sourceMappingURL=index.js.map