@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.
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  var __require = import.meta.require;
3
3
 
4
- // .angular-partial-tmp-0xCaBl/src/core/streamingSlotRegistrar.ts
4
+ // .angular-partial-tmp-WzbAzY/src/core/streamingSlotRegistrar.ts
5
5
  var STREAMING_SLOT_REGISTRAR_KEY = Symbol.for("absolutejs.streamingSlotRegistrar");
6
6
  var STREAMING_SLOT_WARNING_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotWarningController");
7
7
  var STREAMING_SLOT_COLLECTION_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotCollectionController");
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  var __require = import.meta.require;
3
3
 
4
- // .angular-partial-tmp-0xCaBl/src/core/streamingSlotRegistrar.ts
4
+ // .angular-partial-tmp-WzbAzY/src/core/streamingSlotRegistrar.ts
5
5
  var STREAMING_SLOT_REGISTRAR_KEY = Symbol.for("absolutejs.streamingSlotRegistrar");
6
6
  var STREAMING_SLOT_WARNING_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotWarningController");
7
7
  var STREAMING_SLOT_COLLECTION_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotCollectionController");
@@ -48,7 +48,7 @@ var warnMissingStreamingSlotCollector = (primitiveName) => {
48
48
  getWarningController()?.maybeWarn(primitiveName);
49
49
  };
50
50
 
51
- // .angular-partial-tmp-0xCaBl/src/core/streamingSlotRegistry.ts
51
+ // .angular-partial-tmp-WzbAzY/src/core/streamingSlotRegistry.ts
52
52
  var STREAMING_SLOT_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotAsyncLocalStorage");
53
53
  var isObjectRecord2 = (value) => Boolean(value) && typeof value === "object";
54
54
  var isAsyncLocalStorage = (value) => isObjectRecord2(value) && ("getStore" in value) && typeof value.getStore === "function" && ("run" in value) && typeof value.run === "function";
package/dist/build.js CHANGED
@@ -28480,7 +28480,7 @@ import {
28480
28480
  } from "crypto";
28481
28481
  import { readFile as readFile9, stat as stat3 } from "fs/promises";
28482
28482
  import path from "path";
28483
- 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) => {
28483
+ 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) => {
28484
28484
  if (typeof value !== "string" || value.length === 0)
28485
28485
  throw new MobileUpdateRegistryError(`Mobile update ${field} is invalid`);
28486
28486
  return value;
@@ -28647,6 +28647,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
28647
28647
  const releaseRoot = (manifest) => `${root(manifest.appId)}/releases/${manifest.releaseId}`;
28648
28648
  const manifestKey = (manifest) => `${releaseRoot(manifest)}/update.json`;
28649
28649
  const fileKey = (manifest, file5) => `${releaseRoot(manifest)}/files/${file5.path}`;
28650
+ const tombstoneKey = (appId, releaseId) => `${root(appId)}/gc/${releaseId}.json`;
28650
28651
  const channelKey = (appId, channel) => {
28651
28652
  if (!APP_ID.test(appId) || !NAME.test(channel))
28652
28653
  throw new MobileUpdateRegistryError("Mobile update channel identity is invalid");
@@ -28675,6 +28676,12 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
28675
28676
  throw new MobileUpdateRegistryError("Stored mobile update channel identity changed");
28676
28677
  return value;
28677
28678
  };
28679
+ const assertNotMarked = async (appId, releaseId) => {
28680
+ if (!APP_ID.test(appId) || !RELEASE.test(releaseId))
28681
+ throw new MobileUpdateRegistryError("Mobile update release identity is invalid");
28682
+ if (await options.store.head(tombstoneKey(appId, releaseId)))
28683
+ throw new MobileUpdateRegistryError("Mobile update release is marked for collection. Increase retention and apply garbage collection to restore it before promotion");
28684
+ };
28678
28685
  const writeChannel = async (input, signal) => {
28679
28686
  const value = {
28680
28687
  ...input,
@@ -28699,6 +28706,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
28699
28706
  input.signal?.throwIfAborted();
28700
28707
  if (input.rollout <= 0 || input.rollout > 1)
28701
28708
  throw new MobileUpdateRegistryError("Mobile update rollout is invalid");
28709
+ await assertNotMarked(input.appId, input.releaseId);
28702
28710
  const release = await readManifest2(input.appId, input.releaseId);
28703
28711
  if (!release || release.manifest.channel !== input.channel)
28704
28712
  throw new MobileUpdateRegistryError("Mobile update was not published to this channel");
@@ -28744,11 +28752,221 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
28744
28752
  status: "selected"
28745
28753
  };
28746
28754
  };
28755
+ const retentionValues = (input) => {
28756
+ if (!APP_ID.test(input.appId))
28757
+ throw new MobileUpdateRegistryError("Mobile update appId is invalid");
28758
+ const retainRecent = input.retainRecent ?? DEFAULT_RETAIN_RECENT;
28759
+ const minAgeMs = input.minAgeMs ?? DEFAULT_MIN_AGE_MS;
28760
+ if (!Number.isSafeInteger(retainRecent) || retainRecent < 0)
28761
+ throw new MobileUpdateRegistryError("Mobile update retained release count is invalid");
28762
+ if (!Number.isSafeInteger(minAgeMs) || minAgeMs < 0)
28763
+ throw new MobileUpdateRegistryError("Mobile update minimum release age is invalid");
28764
+ return { minAgeMs, retainRecent };
28765
+ };
28766
+ const listObjects = async (appId, signal) => {
28767
+ const list = options.store.list;
28768
+ if (!list)
28769
+ throw new MobileUpdateRegistryError("Mobile update storage does not support lifecycle listing");
28770
+ const objects = [];
28771
+ const cursors = new Set;
28772
+ let cursor;
28773
+ do {
28774
+ signal?.throwIfAborted();
28775
+ const page = await list({
28776
+ ...cursor ? { cursor } : {},
28777
+ prefix: `${root(appId)}/`
28778
+ });
28779
+ objects.push(...page.objects);
28780
+ if (!page.truncated)
28781
+ break;
28782
+ if (!page.cursor || cursors.has(page.cursor))
28783
+ throw new MobileUpdateRegistryError("Mobile update storage returned an invalid lifecycle cursor");
28784
+ cursors.add(page.cursor);
28785
+ cursor = page.cursor;
28786
+ } while (true);
28787
+ return objects;
28788
+ };
28789
+ const inventory = async (input) => {
28790
+ const { minAgeMs, retainRecent } = retentionValues(input);
28791
+ const objects = await listObjects(input.appId, input.signal);
28792
+ const appRoot = `${root(input.appId)}/`;
28793
+ const releasePrefix = `${appRoot}releases/`;
28794
+ const channelPrefix = `${appRoot}channels/`;
28795
+ const markerPrefix = `${appRoot}gc/`;
28796
+ const objectKeysByRelease = new Map;
28797
+ const objectBytesByRelease = new Map;
28798
+ for (const blobObject of objects) {
28799
+ if (!blobObject.key.startsWith(releasePrefix))
28800
+ continue;
28801
+ const suffix = blobObject.key.slice(releasePrefix.length);
28802
+ const releaseId = suffix.slice(0, suffix.indexOf("/"));
28803
+ if (!RELEASE.test(releaseId))
28804
+ continue;
28805
+ objectKeysByRelease.set(releaseId, [
28806
+ ...objectKeysByRelease.get(releaseId) ?? [],
28807
+ blobObject.key
28808
+ ]);
28809
+ objectBytesByRelease.set(releaseId, (objectBytesByRelease.get(releaseId) ?? 0) + blobObject.size);
28810
+ }
28811
+ const channels = [];
28812
+ for (const blobObject of objects) {
28813
+ if (!blobObject.key.startsWith(channelPrefix) || !blobObject.key.endsWith(".json"))
28814
+ continue;
28815
+ const bytes = await options.store.get(blobObject.key);
28816
+ if (!bytes)
28817
+ throw new MobileUpdateRegistryError("Mobile update channel disappeared during lifecycle inspection");
28818
+ const channel = parseChannel(decode(bytes));
28819
+ if (channel.appId !== input.appId)
28820
+ throw new MobileUpdateRegistryError("Stored mobile update channel identity changed");
28821
+ channels.push(channel);
28822
+ }
28823
+ const markedAt = new Map;
28824
+ for (const blobObject of objects) {
28825
+ if (!blobObject.key.startsWith(markerPrefix) || !blobObject.key.endsWith(".json"))
28826
+ continue;
28827
+ const releaseId = blobObject.key.slice(markerPrefix.length, -".json".length);
28828
+ if (!RELEASE.test(releaseId))
28829
+ continue;
28830
+ const bytes = await options.store.get(blobObject.key);
28831
+ if (!bytes)
28832
+ continue;
28833
+ const marker = decode(bytes);
28834
+ if (!object3(marker) || marker.format !== 1 || marker.appId !== input.appId || marker.releaseId !== releaseId || !iso(marker.markedAt))
28835
+ throw new MobileUpdateRegistryError("Mobile update collection marker is invalid");
28836
+ markedAt.set(releaseId, marker.markedAt);
28837
+ }
28838
+ const active = new Set(channels.flatMap((channel) => channel.releaseId ? [channel.releaseId] : []));
28839
+ const fallback = new Set(channels.flatMap((channel) => channel.fallbackReleaseId ? [channel.fallbackReleaseId] : []));
28840
+ const manifests = [];
28841
+ for (const releaseId of objectKeysByRelease.keys()) {
28842
+ const release = await readManifest2(input.appId, releaseId);
28843
+ if (release)
28844
+ manifests.push(release.manifest);
28845
+ }
28846
+ const recent = new Set;
28847
+ const manifestsByChannel = new Map;
28848
+ for (const manifest of manifests)
28849
+ manifestsByChannel.set(manifest.channel, [
28850
+ ...manifestsByChannel.get(manifest.channel) ?? [],
28851
+ manifest
28852
+ ]);
28853
+ for (const releases2 of manifestsByChannel.values())
28854
+ for (const manifest of releases2.toSorted((left, right) => right.createdAt.localeCompare(left.createdAt)).slice(0, retainRecent))
28855
+ recent.add(manifest.releaseId);
28856
+ const now = clock().getTime();
28857
+ const releases = manifests.map((manifest) => {
28858
+ const protectedBy = [];
28859
+ if (active.has(manifest.releaseId))
28860
+ protectedBy.push("active");
28861
+ if (fallback.has(manifest.releaseId))
28862
+ protectedBy.push("fallback");
28863
+ if (recent.has(manifest.releaseId))
28864
+ protectedBy.push("recent");
28865
+ if (now - Date.parse(manifest.createdAt) < minAgeMs)
28866
+ protectedBy.push("age");
28867
+ return {
28868
+ bytes: objectBytesByRelease.get(manifest.releaseId) ?? 0,
28869
+ channel: manifest.channel,
28870
+ createdAt: manifest.createdAt,
28871
+ ...markedAt.has(manifest.releaseId) ? { markedAt: markedAt.get(manifest.releaseId) } : {},
28872
+ objectCount: objectKeysByRelease.get(manifest.releaseId)?.length ?? 0,
28873
+ protectedBy,
28874
+ releaseId: manifest.releaseId
28875
+ };
28876
+ }).toSorted((left, right) => right.createdAt.localeCompare(left.createdAt));
28877
+ const releaseBytes = releases.reduce((total, release) => total + release.bytes, 0);
28878
+ const totalBytes = objects.reduce((total, object22) => total + object22.size, 0);
28879
+ return {
28880
+ objectKeysByRelease,
28881
+ report: {
28882
+ appId: input.appId,
28883
+ channelCount: channels.length,
28884
+ reclaimableBytes: releases.filter((release) => release.protectedBy.length === 0).reduce((total, release) => total + release.bytes, 0),
28885
+ releaseBytes,
28886
+ releaseCount: releases.length,
28887
+ releases,
28888
+ totalBytes,
28889
+ totalObjectCount: objects.length,
28890
+ untrackedBytes: totalBytes - releaseBytes
28891
+ }
28892
+ };
28893
+ };
28894
+ const pruneUpdates = async (input) => {
28895
+ const initial = await inventory(input);
28896
+ const result = {
28897
+ ...initial.report,
28898
+ dryRun: input.apply !== true,
28899
+ marked: [],
28900
+ reclaimedBytes: 0,
28901
+ restored: [],
28902
+ swept: []
28903
+ };
28904
+ if (input.apply !== true)
28905
+ return result;
28906
+ const remove = options.store.delete;
28907
+ if (!remove)
28908
+ throw new MobileUpdateRegistryError("Mobile update storage does not support lifecycle deletion");
28909
+ const gracePeriodMs = input.gracePeriodMs ?? DEFAULT_GRACE_PERIOD_MS;
28910
+ if (!Number.isSafeInteger(gracePeriodMs) || gracePeriodMs < 0)
28911
+ throw new MobileUpdateRegistryError("Mobile update collection grace period is invalid");
28912
+ const now = clock();
28913
+ for (const release of initial.report.releases) {
28914
+ input.signal?.throwIfAborted();
28915
+ const marker = tombstoneKey(input.appId, release.releaseId);
28916
+ if (release.protectedBy.length > 0) {
28917
+ if (release.markedAt) {
28918
+ await remove(marker);
28919
+ result.restored.push(release.releaseId);
28920
+ }
28921
+ continue;
28922
+ }
28923
+ if (!release.markedAt) {
28924
+ const bytes = json({
28925
+ appId: input.appId,
28926
+ format: 1,
28927
+ markedAt: now.toISOString(),
28928
+ releaseId: release.releaseId
28929
+ });
28930
+ await options.store.put(marker, bytes, {
28931
+ cacheControl: "no-cache",
28932
+ contentType: "application/json",
28933
+ maxBytes: bytes.byteLength,
28934
+ metadata: { releaseid: release.releaseId, sha256: digest(bytes) },
28935
+ signal: input.signal
28936
+ });
28937
+ result.marked.push(release.releaseId);
28938
+ }
28939
+ }
28940
+ const current = await inventory(input);
28941
+ for (const release of current.report.releases) {
28942
+ input.signal?.throwIfAborted();
28943
+ if (release.protectedBy.length > 0 || !release.markedAt || now.getTime() - Date.parse(release.markedAt) < gracePeriodMs || result.marked.includes(release.releaseId))
28944
+ continue;
28945
+ const checked = await inventory(input);
28946
+ const candidate = checked.report.releases.find((value) => value.releaseId === release.releaseId);
28947
+ if (!candidate || candidate.protectedBy.length > 0 || !candidate.markedAt)
28948
+ continue;
28949
+ const keys = checked.objectKeysByRelease.get(release.releaseId) ?? [];
28950
+ for (const key of keys.toSorted((left, right) => {
28951
+ const leftManifest = left.endsWith("/update.json");
28952
+ const rightManifest = right.endsWith("/update.json");
28953
+ return Number(leftManifest) - Number(rightManifest);
28954
+ }))
28955
+ await remove(key);
28956
+ await remove(tombstoneKey(input.appId, release.releaseId));
28957
+ result.reclaimedBytes += candidate.bytes;
28958
+ result.swept.push(release.releaseId);
28959
+ }
28960
+ return result;
28961
+ };
28747
28962
  return {
28963
+ inspectUpdateStorage: async (input) => (await inventory(input)).report,
28964
+ pruneUpdates,
28748
28965
  publishUpdate: async (input) => {
28749
28966
  input.signal?.throwIfAborted();
28750
28967
  const manifest = parseMobileUpdateManifest(input.manifest);
28751
28968
  verifyManifestSignature(manifest, options.publicKeys);
28969
+ await assertNotMarked(manifest.appId, manifest.releaseId);
28752
28970
  const localRoot = path.resolve(input.releaseDirectory);
28753
28971
  const localManifest = parseMobileUpdateManifest(JSON.parse(await readFile9(path.join(localRoot, "update.json"), "utf8")));
28754
28972
  if (JSON.stringify(localManifest) !== JSON.stringify(manifest))
@@ -28772,10 +28990,10 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
28772
28990
  cacheControl: "public, max-age=31536000, immutable",
28773
28991
  contentType: "application/octet-stream",
28774
28992
  maxBytes: file5.bytes,
28775
- metadata: { releaseId: manifest.releaseId, sha256: file5.sha256 },
28993
+ metadata: { releaseid: manifest.releaseId, sha256: file5.sha256 },
28776
28994
  signal: input.signal
28777
28995
  });
28778
- } else if (stored.size !== file5.bytes || stored.metadata?.sha256 !== file5.sha256 || stored.metadata?.releaseId !== manifest.releaseId)
28996
+ } else if (stored.size !== file5.bytes || stored.metadata?.sha256 !== file5.sha256 || (stored.metadata?.releaseid ?? stored.metadata?.releaseId) !== manifest.releaseId)
28779
28997
  throw new MobileUpdateRegistryError("Stored mobile update file identity changed");
28780
28998
  }
28781
28999
  const bytes = json(manifest);
@@ -28783,7 +29001,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
28783
29001
  cacheControl: "public, max-age=31536000, immutable",
28784
29002
  contentType: "application/json",
28785
29003
  maxBytes: bytes.byteLength,
28786
- metadata: { releaseId: manifest.releaseId, sha256: digest(bytes) },
29004
+ metadata: { releaseid: manifest.releaseId, sha256: digest(bytes) },
28787
29005
  signal: input.signal
28788
29006
  });
28789
29007
  if (!await readManifest2(manifest.appId, manifest.releaseId))
@@ -28813,6 +29031,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
28813
29031
  if (!existing)
28814
29032
  throw new MobileUpdateRegistryError("Mobile update channel does not exist");
28815
29033
  if (input.releaseId) {
29034
+ await assertNotMarked(input.appId, input.releaseId);
28816
29035
  const release = await readManifest2(input.appId, input.releaseId);
28817
29036
  if (!release || release.manifest.channel !== input.channel)
28818
29037
  throw new MobileUpdateRegistryError("Mobile rollback release was not published");
@@ -28856,7 +29075,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
28856
29075
  ]);
28857
29076
  if (!bytes || !head)
28858
29077
  return null;
28859
- if (bytes.byteLength !== file5.bytes || head.size !== file5.bytes || head.metadata?.sha256 !== file5.sha256 || head.metadata?.releaseId !== release.manifest.releaseId || digest(bytes) !== file5.sha256)
29078
+ if (bytes.byteLength !== file5.bytes || head.size !== file5.bytes || head.metadata?.sha256 !== file5.sha256 || (head.metadata?.releaseid ?? head.metadata?.releaseId) !== release.manifest.releaseId || digest(bytes) !== file5.sha256)
28860
29079
  throw new MobileUpdateRegistryError("Stored mobile update file integrity failed");
28861
29080
  return { bytes, file: file5 };
28862
29081
  }
@@ -29145,6 +29364,9 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
29145
29364
  var init_mobileUpdate = __esm(() => {
29146
29365
  MAX_FILE_BYTES = 32 * 1024 * 1024;
29147
29366
  MAX_TOTAL_BYTES = 128 * 1024 * 1024;
29367
+ DAY_MS = 24 * 60 * 60 * 1000;
29368
+ DEFAULT_MIN_AGE_MS = 30 * DAY_MS;
29369
+ DEFAULT_GRACE_PERIOD_MS = 7 * DAY_MS;
29148
29370
  HASH = /^[a-f0-9]{64}$/;
29149
29371
  RELEASE = /^amu_[a-f0-9]{64}$/;
29150
29372
  APP_ID = /^[A-Za-z][\w]*(?:\.[A-Za-z][\w]*)+$/;
@@ -29215,10 +29437,27 @@ var ABSOLUTE_MOBILE_UPDATE_SERVER_FORMAT = 1, DEFAULT_MOBILE_UPDATE_REGISTRY_MOD
29215
29437
  const registry2 = loaded.default ?? loaded.registry;
29216
29438
  if (!isRegistry2(registry2))
29217
29439
  throw new TypeError("Mobile update registry must implement publication, promotion, rollback, resolution, and file reads.");
29440
+ const metadata = serverMetadata(loaded.absoluteMobileUpdateServer);
29441
+ const verifier = loaded.verifyAbsoluteMobileUpdateServer;
29442
+ if (metadata.storage === "durable" && typeof verifier !== "function")
29443
+ 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.");
29218
29444
  return {
29219
- metadata: serverMetadata(loaded.absoluteMobileUpdateServer),
29220
- registry: registry2
29445
+ metadata,
29446
+ registry: registry2,
29447
+ ...typeof verifier === "function" ? {
29448
+ verifyDurability: async () => {
29449
+ await verifier();
29450
+ }
29451
+ } : {}
29221
29452
  };
29453
+ }, verifyDurableModule = async (module) => {
29454
+ if (module.metadata.storage !== "durable")
29455
+ throw new TypeError("Mobile production updates require durable object storage. Re-run `absolute mobile update provision --storage s3 --force` or configure a durable adapter.");
29456
+ try {
29457
+ await module.verifyDurability?.();
29458
+ } catch (error) {
29459
+ 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 });
29460
+ }
29222
29461
  }, expoSigningOptions = (config) => {
29223
29462
  if (!config.updates?.expoCodeSigning)
29224
29463
  return;
@@ -29250,8 +29489,8 @@ var ABSOLUTE_MOBILE_UPDATE_SERVER_FORMAT = 1, DEFAULT_MOBILE_UPDATE_REGISTRY_MOD
29250
29489
  if (!updates || !server?.autoMount)
29251
29490
  return new Elysia5({ name: "absolutejs-mobile-updates-disabled" });
29252
29491
  const module = await loadAbsoluteMobileUpdateServerModule(projectRoot, server.registryModule);
29253
- if (options.production && module.metadata.storage !== "durable")
29254
- throw new TypeError("Mobile production updates require durable object storage. Re-run `absolute mobile update provision --storage s3 --force` or configure a durable adapter.");
29492
+ if (options.production)
29493
+ await verifyDurableModule(module);
29255
29494
  const manifest = new URL(updates.manifestUrl);
29256
29495
  if (!manifest.pathname.endsWith("/update.json"))
29257
29496
  throw new TypeError("Auto-mounted mobile update manifests must end in /update.json.");
@@ -29269,8 +29508,7 @@ var ABSOLUTE_MOBILE_UPDATE_SERVER_FORMAT = 1, DEFAULT_MOBILE_UPDATE_REGISTRY_MOD
29269
29508
  if (!config.updates)
29270
29509
  return;
29271
29510
  const module = await loadAbsoluteMobileUpdateServerModule(projectRoot, config.updateServer?.registryModule);
29272
- if (module.metadata.storage !== "durable")
29273
- throw new TypeError("Mobile production updates require durable object storage; the configured registry is local-only.");
29511
+ await verifyDurableModule(module);
29274
29512
  if (config.engine === "expo")
29275
29513
  expoSigningOptions(config);
29276
29514
  return module.metadata;
@@ -29297,7 +29535,8 @@ export default createMobileUpdateRegistry({
29297
29535
  store
29298
29536
  });
29299
29537
  `;
29300
- return `import { S3Client } from '@aws-sdk/client-s3';
29538
+ return `import { randomUUID } from 'node:crypto';
29539
+ import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
29301
29540
  import { awsS3BlobStore } from '@absolutejs/blob/aws-s3';
29302
29541
  import { createMobileUpdateRegistry } from '@absolutejs/deploy/mobile-update';
29303
29542
 
@@ -29309,6 +29548,7 @@ const required = (name: string) => {
29309
29548
  return value;
29310
29549
  };
29311
29550
 
29551
+ const bucket = required('ABSOLUTE_MOBILE_UPDATE_S3_BUCKET');
29312
29552
  const client = new S3Client({
29313
29553
  region: process.env.ABSOLUTE_MOBILE_UPDATE_S3_REGION ?? 'auto',
29314
29554
  forcePathStyle: process.env.ABSOLUTE_MOBILE_UPDATE_S3_FORCE_PATH_STYLE === '1',
@@ -29316,10 +29556,22 @@ const client = new S3Client({
29316
29556
  ? { endpoint: process.env.ABSOLUTE_MOBILE_UPDATE_S3_ENDPOINT }
29317
29557
  : {})
29318
29558
  });
29319
- const store = awsS3BlobStore({
29320
- bucket: required('ABSOLUTE_MOBILE_UPDATE_S3_BUCKET'),
29321
- client
29322
- });
29559
+ const store = awsS3BlobStore({ bucket, client });
29560
+
29561
+ export const verifyAbsoluteMobileUpdateServer = async () => {
29562
+ const key = \`absolutejs/mobile-updates/_health/\${randomUUID()}\`;
29563
+ const expected = randomUUID();
29564
+ let stored = false;
29565
+ try {
29566
+ await client.send(new PutObjectCommand({ Bucket: bucket, Key: key, Body: expected }));
29567
+ stored = true;
29568
+ const response = await client.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
29569
+ if ((await response.Body?.transformToString()) !== expected)
29570
+ throw new Error('Durability probe read did not match its write.');
29571
+ } finally {
29572
+ if (stored) await client.send(new DeleteObjectCommand({ Bucket: bucket, Key: key }));
29573
+ }
29574
+ };
29323
29575
 
29324
29576
  export default createMobileUpdateRegistry({
29325
29577
  publicKeys: ${publicKeysSource(options.publicKeys)},
@@ -36939,5 +37191,5 @@ export {
36939
37191
  devBuild
36940
37192
  };
36941
37193
 
36942
- //# debugId=C21B5771D51EC59A64756E2164756E21
37194
+ //# debugId=4B8A1B549AE0AC2164756E2164756E21
36943
37195
  //# sourceMappingURL=build.js.map