@honua/app-platform 0.1.11-beta.0 → 0.1.13
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/columnar/apache-arrow.d.ts +17 -0
- package/columnar/apache-arrow.d.ts.map +1 -0
- package/columnar/apache-arrow.js +1036 -0
- package/columnar/batch-cache-conformance.d.ts +66 -0
- package/columnar/batch-cache-conformance.d.ts.map +1 -0
- package/columnar/batch-cache-conformance.js +317 -0
- package/columnar/batch-cache-indexeddb.d.ts +31 -0
- package/columnar/batch-cache-indexeddb.d.ts.map +1 -0
- package/columnar/batch-cache-indexeddb.js +148 -0
- package/columnar/batch-cache.d.ts +298 -0
- package/columnar/batch-cache.d.ts.map +1 -0
- package/columnar/batch-cache.js +757 -0
- package/columnar/geoarrow-aggregate.d.ts +178 -0
- package/columnar/geoarrow-aggregate.d.ts.map +1 -0
- package/columnar/geoarrow-aggregate.js +976 -0
- package/columnar/geoarrow-patch.d.ts +382 -0
- package/columnar/geoarrow-patch.d.ts.map +1 -0
- package/columnar/geoarrow-patch.js +1656 -0
- package/columnar/geoarrow-reproject.d.ts +22 -0
- package/columnar/geoarrow-reproject.d.ts.map +1 -0
- package/columnar/geoarrow-reproject.js +119 -0
- package/columnar/geoarrow-serialization.d.ts +68 -0
- package/columnar/geoarrow-serialization.d.ts.map +1 -0
- package/columnar/geoarrow-serialization.js +331 -0
- package/columnar/geoarrow-types.d.ts +271 -0
- package/columnar/geoarrow-types.d.ts.map +1 -0
- package/columnar/geoarrow-types.js +15 -0
- package/columnar/geoarrow.d.ts +52 -0
- package/columnar/geoarrow.d.ts.map +1 -0
- package/columnar/geoarrow.js +1469 -0
- package/columnar/index.d.ts +43 -0
- package/columnar/index.d.ts.map +1 -0
- package/columnar/index.js +29 -0
- package/columnar/operators.d.ts +70 -0
- package/columnar/operators.d.ts.map +1 -0
- package/columnar/operators.js +307 -0
- package/columnar/result.d.ts +369 -0
- package/columnar/result.d.ts.map +1 -0
- package/columnar/result.js +815 -0
- package/columnar/telemetry.d.ts +152 -0
- package/columnar/telemetry.d.ts.map +1 -0
- package/columnar/telemetry.js +265 -0
- package/columnar/transfer.d.ts +28 -0
- package/columnar/transfer.d.ts.map +1 -0
- package/columnar/transfer.js +669 -0
- package/columnar/types.d.ts +175 -0
- package/columnar/types.d.ts.map +1 -0
- package/columnar/types.js +22 -0
- package/columnar/worker.d.ts +157 -0
- package/columnar/worker.d.ts.map +1 -0
- package/columnar/worker.js +1059 -0
- package/contract/pmtiles.d.ts +6 -5
- package/contract/pmtiles.d.ts.map +1 -1
- package/contract/pmtiles.js +30 -5
- package/control-plane/generated/admin-operations.d.ts +3 -3
- package/control-plane/generated/admin-operations.d.ts.map +1 -1
- package/control-plane/generated/admin-operations.js +3 -3
- package/controls/catalog.d.ts.map +1 -1
- package/controls/catalog.js +110 -0
- package/controls/index.d.ts +1 -1
- package/controls/index.js +1 -1
- package/controls/types.d.ts +1 -1
- package/controls/types.js +1 -1
- package/core/client.d.ts.map +1 -1
- package/core/client.js +87 -19
- package/core/deprecation-notice.d.ts +28 -0
- package/core/deprecation-notice.d.ts.map +1 -0
- package/core/deprecation-notice.js +22 -0
- package/core/esri-geojson.d.ts.map +1 -1
- package/core/esri-geojson.js +10 -6
- package/core/optional-grpc-peer.d.ts +15 -0
- package/core/optional-grpc-peer.d.ts.map +1 -0
- package/core/optional-grpc-peer.js +60 -0
- package/esri-compat/basemap-gallery.d.ts +2 -0
- package/esri-compat/basemap-gallery.d.ts.map +1 -1
- package/esri-compat/basemap-gallery.js +12 -0
- package/esri-compat/basemap.d.ts +4 -0
- package/esri-compat/basemap.d.ts.map +1 -1
- package/esri-compat/basemap.js +70 -0
- package/esri-compat/bookmarks.d.ts +2 -0
- package/esri-compat/bookmarks.d.ts.map +1 -1
- package/esri-compat/bookmarks.js +12 -0
- package/esri-compat/controls.d.ts +18 -0
- package/esri-compat/controls.d.ts.map +1 -1
- package/esri-compat/controls.js +120 -57
- package/esri-compat/directions.d.ts +4 -0
- package/esri-compat/directions.d.ts.map +1 -1
- package/esri-compat/directions.js +30 -20
- package/esri-compat/editor.d.ts +4 -0
- package/esri-compat/editor.d.ts.map +1 -1
- package/esri-compat/editor.js +16 -0
- package/esri-compat/esri-config.d.ts +2 -1
- package/esri-compat/esri-config.d.ts.map +1 -1
- package/esri-compat/feature-effect.d.ts +23 -0
- package/esri-compat/feature-effect.d.ts.map +1 -0
- package/esri-compat/feature-effect.js +12 -0
- package/esri-compat/feature-form.d.ts +6 -0
- package/esri-compat/feature-form.d.ts.map +1 -1
- package/esri-compat/feature-form.js +63 -0
- package/esri-compat/feature-layer.d.ts +21 -2
- package/esri-compat/feature-layer.d.ts.map +1 -1
- package/esri-compat/feature-layer.js +172 -1
- package/esri-compat/feature-table.d.ts +5 -0
- package/esri-compat/feature-table.d.ts.map +1 -1
- package/esri-compat/feature-table.js +23 -0
- package/esri-compat/identity-manager.d.ts +1 -1
- package/esri-compat/identity-manager.js +1 -1
- package/esri-compat/legend.d.ts +2 -0
- package/esri-compat/legend.d.ts.map +1 -1
- package/esri-compat/legend.js +98 -0
- package/esri-compat/locator.d.ts +1 -1
- package/esri-compat/locator.js +1 -1
- package/esri-compat/map-view-mount.d.ts +1 -0
- package/esri-compat/map-view-mount.d.ts.map +1 -1
- package/esri-compat/map-view-mount.js +46 -25
- package/esri-compat/map-view.d.ts +13 -0
- package/esri-compat/map-view.d.ts.map +1 -1
- package/esri-compat/map-view.js +94 -8
- package/esri-compat/map.d.ts +5 -1
- package/esri-compat/map.d.ts.map +1 -1
- package/esri-compat/map.js +10 -0
- package/esri-compat/measurement.d.ts +2 -0
- package/esri-compat/measurement.d.ts.map +1 -1
- package/esri-compat/measurement.js +14 -0
- package/esri-compat/point.d.ts +7 -0
- package/esri-compat/point.d.ts.map +1 -1
- package/esri-compat/point.js +33 -3
- package/esri-compat/popup.d.ts +2 -0
- package/esri-compat/popup.d.ts.map +1 -1
- package/esri-compat/popup.js +12 -0
- package/esri-compat/portal.d.ts +1 -1
- package/esri-compat/portal.js +1 -1
- package/esri-compat/print.d.ts +3 -0
- package/esri-compat/print.d.ts.map +1 -1
- package/esri-compat/print.js +15 -0
- package/esri-compat/search.d.ts +4 -0
- package/esri-compat/search.d.ts.map +1 -1
- package/esri-compat/search.js +25 -28
- package/esri-compat/sketch.d.ts +2 -0
- package/esri-compat/sketch.d.ts.map +1 -1
- package/esri-compat/sketch.js +12 -0
- package/esri-compat/widget-host.d.ts +46 -17
- package/esri-compat/widget-host.d.ts.map +1 -1
- package/esri-compat/widget-host.js +84 -20
- package/esri-compat/widget-shell-hosts.d.ts +61 -0
- package/esri-compat/widget-shell-hosts.d.ts.map +1 -0
- package/esri-compat/widget-shell-hosts.js +76 -0
- package/kernel/renderer.d.ts +82 -0
- package/kernel/renderer.js +1 -0
- package/offline/conflict-resolution.d.ts +183 -0
- package/offline/conflict-resolution.d.ts.map +1 -0
- package/offline/conflict-resolution.js +286 -0
- package/offline/credential-screen.d.ts +34 -0
- package/offline/credential-screen.d.ts.map +1 -0
- package/offline/credential-screen.js +78 -0
- package/offline/digest.d.ts +15 -0
- package/offline/digest.d.ts.map +1 -0
- package/offline/digest.js +58 -0
- package/offline/edit-queue.d.ts +403 -0
- package/offline/edit-queue.d.ts.map +1 -0
- package/offline/edit-queue.js +1807 -0
- package/offline/edit-replay.d.ts +116 -0
- package/offline/edit-replay.d.ts.map +1 -0
- package/offline/edit-replay.js +392 -0
- package/offline/fetch-handler.d.ts +12 -0
- package/offline/fetch-handler.d.ts.map +1 -0
- package/offline/fetch-handler.js +65 -0
- package/offline/index.d.ts +52 -0
- package/offline/index.d.ts.map +1 -0
- package/offline/index.js +34 -0
- package/offline/indexeddb.d.ts +135 -0
- package/offline/indexeddb.d.ts.map +1 -0
- package/offline/indexeddb.js +888 -0
- package/offline/memory-store.d.ts +17 -0
- package/offline/memory-store.d.ts.map +1 -0
- package/offline/memory-store.js +183 -0
- package/offline/quota.d.ts +161 -0
- package/offline/quota.d.ts.map +1 -0
- package/offline/quota.js +370 -0
- package/offline/read-gate.d.ts +175 -0
- package/offline/read-gate.d.ts.map +1 -0
- package/offline/read-gate.js +252 -0
- package/offline/read-resource.d.ts +99 -0
- package/offline/read-resource.d.ts.map +1 -0
- package/offline/read-resource.js +156 -0
- package/offline/read.d.ts +50 -0
- package/offline/read.d.ts.map +1 -0
- package/offline/read.js +65 -0
- package/offline/region.d.ts +29 -0
- package/offline/region.d.ts.map +1 -0
- package/offline/region.js +1007 -0
- package/offline/replay-conflict.d.ts +183 -0
- package/offline/replay-conflict.d.ts.map +1 -0
- package/offline/replay-conflict.js +482 -0
- package/offline/selection.d.ts +140 -0
- package/offline/selection.d.ts.map +1 -0
- package/offline/selection.js +391 -0
- package/offline/snapshot.d.ts +132 -0
- package/offline/snapshot.d.ts.map +1 -0
- package/offline/snapshot.js +312 -0
- package/offline/status.d.ts +136 -0
- package/offline/status.d.ts.map +1 -0
- package/offline/status.js +479 -0
- package/offline/store-conformance.d.ts +48 -0
- package/offline/store-conformance.d.ts.map +1 -0
- package/offline/store-conformance.js +233 -0
- package/offline/tile-protocol.d.ts +81 -0
- package/offline/tile-protocol.d.ts.map +1 -0
- package/offline/tile-protocol.js +119 -0
- package/offline/types.d.ts +349 -0
- package/offline/types.d.ts.map +1 -0
- package/offline/types.js +75 -0
- package/package.json +14 -2
- package/scene-workspace/index.d.ts +2 -2
- package/scene-workspace/index.d.ts.map +1 -1
- package/scene-workspace/index.js +1 -1
- package/scene-workspace/scene-discovery.d.ts +111 -91
- package/scene-workspace/scene-discovery.d.ts.map +1 -1
- package/scene-workspace/scene-discovery.js +340 -92
- package/scene-workspace/scene-view.d.ts +6 -6
- package/scene-workspace/scene-view.d.ts.map +1 -1
- package/scene-workspace/scene-view.js +32 -6
- package/web-components/elements.d.ts +34 -0
- package/web-components/elements.d.ts.map +1 -1
- package/web-components/elements.js +82 -8
- package/web-components/index.d.ts +2 -0
- package/web-components/index.d.ts.map +1 -1
- package/web-components/index.js +3 -1
- package/web-components/map-chrome.d.ts +103 -0
- package/web-components/map-chrome.d.ts.map +1 -0
- package/web-components/map-chrome.js +325 -0
- package/web-components/widget-shell.d.ts +68 -0
- package/web-components/widget-shell.d.ts.map +1 -0
- package/web-components/widget-shell.js +224 -0
- package/widget-capabilities.d.ts +67 -0
- package/widget-capabilities.js +152 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type OfflineRegionCacheAdmin, type OfflineRegionCacheInventory, type OfflineRegionResourceRead, type OfflineRegionStore, type OfflineRegionWriteTransaction } from "./types.js";
|
|
2
|
+
export interface MemoryOfflineRegionStoreOptions {
|
|
3
|
+
/** Injectable clock so tests can observe deterministic access timestamps. */
|
|
4
|
+
readonly now?: () => Date;
|
|
5
|
+
}
|
|
6
|
+
export declare class MemoryOfflineRegionStore implements OfflineRegionStore, OfflineRegionCacheAdmin {
|
|
7
|
+
#private;
|
|
8
|
+
constructor(options?: MemoryOfflineRegionStoreOptions);
|
|
9
|
+
inventory(): Promise<OfflineRegionCacheInventory>;
|
|
10
|
+
readResource(regionId: string, resourceId: string): Promise<OfflineRegionResourceRead | undefined>;
|
|
11
|
+
removeRegion(regionId: string): Promise<boolean>;
|
|
12
|
+
setRegionPinned(regionId: string, pinned: boolean): Promise<boolean>;
|
|
13
|
+
pruneExpired(now?: Date): Promise<readonly string[]>;
|
|
14
|
+
beginWrite(regionId: string): Promise<OfflineRegionWriteTransaction>;
|
|
15
|
+
}
|
|
16
|
+
export declare function createMemoryOfflineRegionStore(options?: MemoryOfflineRegionStoreOptions): MemoryOfflineRegionStore;
|
|
17
|
+
//# sourceMappingURL=memory-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-store.d.ts","sourceRoot":"","sources":["../../../src/offline/memory-store.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,uBAAuB,EAC5B,KAAK,2BAA2B,EAGhC,KAAK,yBAAyB,EAC9B,KAAK,kBAAkB,EAEvB,KAAK,6BAA6B,EACnC,MAAM,YAAY,CAAC;AA0BpB,MAAM,WAAW,+BAA+B;IAC9C,6EAA6E;IAC7E,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED,qBAAa,wBAAyB,YAAW,kBAAkB,EAAE,uBAAuB;;gBAOvE,OAAO,GAAE,+BAAoC;IAInD,SAAS,IAAI,OAAO,CAAC,2BAA2B,CAAC;IAejD,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,GAAG,SAAS,CAAC;IAqBlG,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAOhD,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAUpE,YAAY,CAAC,GAAG,GAAE,IAAkB,GAAG,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC;IAajE,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,6BAA6B,CAAC;CAiGlF;AAED,wBAAgB,8BAA8B,CAC5C,OAAO,GAAE,+BAAoC,GAC5C,wBAAwB,CAE1B"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { assertCredentialFreeManifest, credentialScreenMessage, screenPersistedString } from "./credential-screen.js";
|
|
2
|
+
import { compareCodeUnits } from "./digest.js";
|
|
3
|
+
import { HonuaOfflineRegionError, } from "./types.js";
|
|
4
|
+
export class MemoryOfflineRegionStore {
|
|
5
|
+
#regions = new Map();
|
|
6
|
+
#staging = new Map();
|
|
7
|
+
#now;
|
|
8
|
+
#revision = "0";
|
|
9
|
+
#sequence = 0;
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
this.#now = options.now ?? (() => new Date());
|
|
12
|
+
}
|
|
13
|
+
async inventory() {
|
|
14
|
+
const regions = [];
|
|
15
|
+
for (const [id, record] of this.#regions) {
|
|
16
|
+
regions.push({
|
|
17
|
+
id,
|
|
18
|
+
logicalByteLength: record.logicalByteLength,
|
|
19
|
+
lastAccessedAt: record.lastAccessedAt,
|
|
20
|
+
...(record.expiresAt ? { expiresAt: record.expiresAt } : {}),
|
|
21
|
+
...(record.pinned ? { pinned: true } : {}),
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
regions.sort((left, right) => compareCodeUnits(left.id, right.id));
|
|
25
|
+
return { revision: this.#revision, regions };
|
|
26
|
+
}
|
|
27
|
+
async readResource(regionId, resourceId) {
|
|
28
|
+
requireId(regionId, "regionId");
|
|
29
|
+
requireId(resourceId, "resourceId");
|
|
30
|
+
const record = this.#regions.get(regionId);
|
|
31
|
+
if (!record)
|
|
32
|
+
return undefined;
|
|
33
|
+
const resource = record.manifest.resources.find((candidate) => candidate.id === resourceId);
|
|
34
|
+
if (!resource)
|
|
35
|
+
return undefined;
|
|
36
|
+
const bytes = record.resources.get(resourceId);
|
|
37
|
+
if (!bytes)
|
|
38
|
+
return undefined;
|
|
39
|
+
if (bytes.byteLength !== resource.byteLength) {
|
|
40
|
+
throw new HonuaOfflineRegionError("integrity-mismatch", `Offline resource ${resourceId} failed its stored byte-length check.`, { resourceId });
|
|
41
|
+
}
|
|
42
|
+
record.lastAccessedAt = this.#now().toISOString();
|
|
43
|
+
this.#advance();
|
|
44
|
+
return { regionId, manifest: record.manifest, resource, bytes: Uint8Array.from(bytes) };
|
|
45
|
+
}
|
|
46
|
+
async removeRegion(regionId) {
|
|
47
|
+
requireId(regionId, "regionId");
|
|
48
|
+
if (!this.#regions.delete(regionId))
|
|
49
|
+
return false;
|
|
50
|
+
this.#advance();
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
async setRegionPinned(regionId, pinned) {
|
|
54
|
+
requireId(regionId, "regionId");
|
|
55
|
+
if (typeof pinned !== "boolean")
|
|
56
|
+
throw new TypeError("pinned must be a boolean.");
|
|
57
|
+
const record = this.#regions.get(regionId);
|
|
58
|
+
if (!record || Boolean(record.pinned) === pinned)
|
|
59
|
+
return false;
|
|
60
|
+
record.pinned = pinned || undefined;
|
|
61
|
+
this.#advance();
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
async pruneExpired(now = this.#now()) {
|
|
65
|
+
if (!(now instanceof Date) || !Number.isFinite(now.getTime()))
|
|
66
|
+
throw new TypeError("now must be a valid Date.");
|
|
67
|
+
const cutoff = now.getTime();
|
|
68
|
+
const expired = [...this.#regions.entries()]
|
|
69
|
+
.filter(([, record]) => record.expiresAt !== undefined && Date.parse(record.expiresAt) <= cutoff)
|
|
70
|
+
.map(([id]) => id)
|
|
71
|
+
.sort(compareCodeUnits);
|
|
72
|
+
if (expired.length === 0)
|
|
73
|
+
return expired;
|
|
74
|
+
for (const id of expired)
|
|
75
|
+
this.#regions.delete(id);
|
|
76
|
+
this.#advance();
|
|
77
|
+
return expired;
|
|
78
|
+
}
|
|
79
|
+
async beginWrite(regionId) {
|
|
80
|
+
requireId(regionId, "regionId");
|
|
81
|
+
assertCredentialFreeIdentity(regionId, "regionId");
|
|
82
|
+
const evictions = [];
|
|
83
|
+
const staged = this.#staging.get(regionId) ?? new Map();
|
|
84
|
+
this.#staging.set(regionId, staged);
|
|
85
|
+
return {
|
|
86
|
+
evict: async (id) => {
|
|
87
|
+
if (!evictions.includes(id))
|
|
88
|
+
evictions.push(id);
|
|
89
|
+
},
|
|
90
|
+
readStaged: async (resource) => {
|
|
91
|
+
const bytes = staged.get(resource.id);
|
|
92
|
+
if (!bytes || bytes.byteLength !== resource.byteLength)
|
|
93
|
+
return undefined;
|
|
94
|
+
return Uint8Array.from(bytes);
|
|
95
|
+
},
|
|
96
|
+
discardStaged: async (resource) => {
|
|
97
|
+
staged.delete(resource.id);
|
|
98
|
+
},
|
|
99
|
+
write: async (resource, bytes) => {
|
|
100
|
+
assertCredentialFreeIdentity(resource.id, "resource.id");
|
|
101
|
+
const copy = Uint8Array.from(bytes);
|
|
102
|
+
if (copy.byteLength !== resource.byteLength) {
|
|
103
|
+
throw new HonuaOfflineRegionError("integrity-mismatch", `Resource ${resource.id} byte length does not match its descriptor.`, { resourceId: resource.id });
|
|
104
|
+
}
|
|
105
|
+
staged.set(resource.id, copy);
|
|
106
|
+
},
|
|
107
|
+
commit: async (manifest, receipt, guard) => {
|
|
108
|
+
// `commit` is public, so the credential-free manifest invariant is
|
|
109
|
+
// re-enforced here rather than trusted from the coordinator.
|
|
110
|
+
assertCredentialFreeManifest(manifest);
|
|
111
|
+
if (this.#revision !== guard.expectedInventoryRevision)
|
|
112
|
+
return "inventory-changed";
|
|
113
|
+
const existing = this.#regions.get(manifest.id);
|
|
114
|
+
const evicted = new Set(evictions);
|
|
115
|
+
let logicalBytesAfter = manifest.totalLogicalBytes;
|
|
116
|
+
for (const [id, record] of this.#regions) {
|
|
117
|
+
if (id === manifest.id || evicted.has(id))
|
|
118
|
+
continue;
|
|
119
|
+
logicalBytesAfter += record.logicalByteLength;
|
|
120
|
+
}
|
|
121
|
+
if (logicalBytesAfter > guard.logicalQuotaBytes ||
|
|
122
|
+
logicalBytesAfter !== guard.admission.logicalBytesAfter ||
|
|
123
|
+
guard.admission.requiredLogicalBytes !== manifest.totalLogicalBytes ||
|
|
124
|
+
guard.admission.replacementLogicalBytes !== (existing?.logicalByteLength ?? 0) ||
|
|
125
|
+
!sameIds(guard.admission.evictRegionIds, evictions)) {
|
|
126
|
+
throw new HonuaOfflineRegionError("store-failed", "Memory store rejected inconsistent offline-region admission.");
|
|
127
|
+
}
|
|
128
|
+
const expected = new Set(manifest.resources.map((resource) => resource.id));
|
|
129
|
+
for (const resource of manifest.resources) {
|
|
130
|
+
const bytes = staged.get(resource.id);
|
|
131
|
+
if (!bytes || bytes.byteLength !== resource.byteLength) {
|
|
132
|
+
throw new HonuaOfflineRegionError("store-failed", "Memory store requires every manifest resource to be written before commit.", { resourceId: resource.id });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
for (const id of staged.keys()) {
|
|
136
|
+
if (!expected.has(id)) {
|
|
137
|
+
throw new HonuaOfflineRegionError("store-failed", "Memory store found an unexpected staged resource.", {
|
|
138
|
+
resourceId: id,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
for (const id of evictions)
|
|
143
|
+
this.#regions.delete(id);
|
|
144
|
+
this.#regions.set(manifest.id, {
|
|
145
|
+
manifest,
|
|
146
|
+
receipt,
|
|
147
|
+
logicalByteLength: manifest.totalLogicalBytes,
|
|
148
|
+
lastAccessedAt: receipt.completedAt,
|
|
149
|
+
...(manifest.expiresAt ? { expiresAt: manifest.expiresAt } : {}),
|
|
150
|
+
...(existing?.pinned ? { pinned: true } : {}),
|
|
151
|
+
resources: new Map(staged),
|
|
152
|
+
});
|
|
153
|
+
this.#staging.delete(manifest.id);
|
|
154
|
+
this.#advance();
|
|
155
|
+
return "committed";
|
|
156
|
+
},
|
|
157
|
+
rollback: async (options) => {
|
|
158
|
+
if (!options?.preserveStaged)
|
|
159
|
+
this.#staging.delete(regionId);
|
|
160
|
+
evictions.length = 0;
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
#advance() {
|
|
165
|
+
this.#sequence += 1;
|
|
166
|
+
this.#revision = `${this.#sequence}`;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
export function createMemoryOfflineRegionStore(options = {}) {
|
|
170
|
+
return new MemoryOfflineRegionStore(options);
|
|
171
|
+
}
|
|
172
|
+
function requireId(value, name) {
|
|
173
|
+
if (typeof value !== "string" || value.length === 0)
|
|
174
|
+
throw new TypeError(`${name} must be non-empty.`);
|
|
175
|
+
}
|
|
176
|
+
function assertCredentialFreeIdentity(value, path) {
|
|
177
|
+
const reason = screenPersistedString(value, "identity");
|
|
178
|
+
if (reason)
|
|
179
|
+
throw new HonuaOfflineRegionError("invalid-manifest", credentialScreenMessage(path, reason), { path });
|
|
180
|
+
}
|
|
181
|
+
function sameIds(left, right) {
|
|
182
|
+
return left.length === right.length && left.every((id, index) => id === right[index]);
|
|
183
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Real browser storage-quota interrogation for offline-region admission.
|
|
3
|
+
*
|
|
4
|
+
* `logicalQuotaBytes` is an honest accounting of declared payload lengths, but
|
|
5
|
+
* it has no relationship to the space a browser will actually grant. This module
|
|
6
|
+
* closes that gap in the only way the platform supports: it reads
|
|
7
|
+
* `navigator.storage.estimate()` through an injectable interface, derives a
|
|
8
|
+
* conservative logical budget with an explicit reserve, and reports an explicit
|
|
9
|
+
* `unavailable` result rather than inventing a number when the platform cannot
|
|
10
|
+
* estimate.
|
|
11
|
+
*
|
|
12
|
+
* The estimate is deliberately imprecise and origin-scoped, and the accounting
|
|
13
|
+
* unit stays logical payload bytes: physical occupancy, deduplication, and index
|
|
14
|
+
* overhead are outside the contract and are exactly why a reserve exists. The
|
|
15
|
+
* derived budget is advisory, never a guarantee, so a device can still refuse a
|
|
16
|
+
* write that the budget admitted — which is why storage-pressure failures are
|
|
17
|
+
* classified here too.
|
|
18
|
+
*
|
|
19
|
+
* Nothing in this module performs network I/O, mutates the store, or reads or
|
|
20
|
+
* returns a credential, origin secret, or request URL. Persistence is never
|
|
21
|
+
* requested implicitly: `requestOfflinePersistentStorage()` is the only path to
|
|
22
|
+
* `navigator.storage.persist()` and it exists solely because the caller asked.
|
|
23
|
+
*/
|
|
24
|
+
/** Stable discriminator and version for storage-budget probe results. */
|
|
25
|
+
export declare const HONUA_OFFLINE_STORAGE_BUDGET_KIND: "honua.offline-storage-budget";
|
|
26
|
+
export declare const HONUA_OFFLINE_STORAGE_BUDGET_VERSION: "1.0";
|
|
27
|
+
/** Stable discriminator and version for explicit persistence-request results. */
|
|
28
|
+
export declare const HONUA_OFFLINE_STORAGE_PERSISTENCE_KIND: "honua.offline-storage-persistence";
|
|
29
|
+
export declare const HONUA_OFFLINE_STORAGE_PERSISTENCE_VERSION: "1.0";
|
|
30
|
+
/** Fraction of the remaining estimate held back from the derived logical budget. */
|
|
31
|
+
export declare const DEFAULT_OFFLINE_STORAGE_HEADROOM_RATIO = 0.2;
|
|
32
|
+
/** Floor for the reserve, so a large-quota origin still keeps real headroom. */
|
|
33
|
+
export declare const DEFAULT_OFFLINE_STORAGE_MIN_RESERVE_BYTES: number;
|
|
34
|
+
/**
|
|
35
|
+
* Whether the origin's storage survives eviction pressure. `unknown` is a real
|
|
36
|
+
* answer: a platform without `StorageManager.persisted()` has not told us.
|
|
37
|
+
*/
|
|
38
|
+
export type OfflineStoragePersistence = "persisted" | "best-effort" | "unknown";
|
|
39
|
+
/** Why no budget could be derived. Never accompanied by a fabricated number. */
|
|
40
|
+
export type OfflineStorageBudgetUnavailableReason = "storage-manager-unavailable" | "estimate-unsupported" | "estimate-failed" | "estimate-incomplete";
|
|
41
|
+
/**
|
|
42
|
+
* Injectable subset of the platform `StorageManager`.
|
|
43
|
+
*
|
|
44
|
+
* Every member is optional so a partial host implementation is describable
|
|
45
|
+
* rather than assumed; the probe degrades to an explicit `unavailable` result
|
|
46
|
+
* instead of throwing. A real `navigator.storage` satisfies this structurally.
|
|
47
|
+
*/
|
|
48
|
+
export interface OfflineStorageManagerLike {
|
|
49
|
+
estimate?: () => Promise<{
|
|
50
|
+
readonly quota?: number;
|
|
51
|
+
readonly usage?: number;
|
|
52
|
+
} | undefined>;
|
|
53
|
+
persisted?: () => Promise<boolean>;
|
|
54
|
+
persist?: () => Promise<boolean>;
|
|
55
|
+
}
|
|
56
|
+
/** A derived, advisory logical budget backed by a real platform estimate. */
|
|
57
|
+
export interface OfflineStorageBudgetAvailableV1 {
|
|
58
|
+
readonly kind: typeof HONUA_OFFLINE_STORAGE_BUDGET_KIND;
|
|
59
|
+
readonly version: typeof HONUA_OFFLINE_STORAGE_BUDGET_VERSION;
|
|
60
|
+
readonly status: "available";
|
|
61
|
+
readonly observedAt: string;
|
|
62
|
+
/** Platform-reported origin quota. Imprecise and subject to change. */
|
|
63
|
+
readonly quotaBytes: number;
|
|
64
|
+
readonly usageBytes: number;
|
|
65
|
+
/** `max(0, quota - usage)`; the hard upper bound on any derived budget. */
|
|
66
|
+
readonly remainingBytes: number;
|
|
67
|
+
readonly reserveBytes: number;
|
|
68
|
+
readonly headroomRatio: number;
|
|
69
|
+
/** Pass to `logicalQuotaBytes`. Never exceeds `remainingBytes`. */
|
|
70
|
+
readonly logicalBudgetBytes: number;
|
|
71
|
+
readonly persistence: OfflineStoragePersistence;
|
|
72
|
+
}
|
|
73
|
+
/** An explicit refusal to estimate. Carries no budget of any kind. */
|
|
74
|
+
export interface OfflineStorageBudgetUnavailableV1 {
|
|
75
|
+
readonly kind: typeof HONUA_OFFLINE_STORAGE_BUDGET_KIND;
|
|
76
|
+
readonly version: typeof HONUA_OFFLINE_STORAGE_BUDGET_VERSION;
|
|
77
|
+
readonly status: "unavailable";
|
|
78
|
+
readonly observedAt: string;
|
|
79
|
+
readonly reason: OfflineStorageBudgetUnavailableReason;
|
|
80
|
+
readonly persistence: OfflineStoragePersistence;
|
|
81
|
+
}
|
|
82
|
+
export type OfflineStorageBudgetV1 = OfflineStorageBudgetAvailableV1 | OfflineStorageBudgetUnavailableV1;
|
|
83
|
+
export interface ProbeOfflineStorageBudgetOptions {
|
|
84
|
+
/** Defaults to `navigator.storage`. Inject for tests and non-browser hosts. */
|
|
85
|
+
readonly storage?: OfflineStorageManagerLike;
|
|
86
|
+
/** Fraction of remaining space held in reserve. `0` to `1`; defaults to `0.2`. */
|
|
87
|
+
readonly headroomRatio?: number;
|
|
88
|
+
/** Reserve floor in bytes; defaults to 16 MiB. Clamped by remaining space. */
|
|
89
|
+
readonly minimumReserveBytes?: number;
|
|
90
|
+
/** Explicit clock input so a persisted probe result stays reproducible. */
|
|
91
|
+
readonly now?: Date;
|
|
92
|
+
}
|
|
93
|
+
export type OfflineStoragePersistenceRequestStatus = "granted" | "denied" | "unavailable";
|
|
94
|
+
export type OfflineStoragePersistenceUnavailableReason = "storage-manager-unavailable" | "persist-unsupported" | "persist-failed";
|
|
95
|
+
/** Result of an explicit, caller-initiated persistence request. */
|
|
96
|
+
export interface OfflineStoragePersistenceRequestV1 {
|
|
97
|
+
readonly kind: typeof HONUA_OFFLINE_STORAGE_PERSISTENCE_KIND;
|
|
98
|
+
readonly version: typeof HONUA_OFFLINE_STORAGE_PERSISTENCE_VERSION;
|
|
99
|
+
readonly status: OfflineStoragePersistenceRequestStatus;
|
|
100
|
+
readonly persistence: OfflineStoragePersistence;
|
|
101
|
+
readonly reason?: OfflineStoragePersistenceUnavailableReason;
|
|
102
|
+
}
|
|
103
|
+
export interface RequestOfflinePersistentStorageOptions {
|
|
104
|
+
/** Defaults to `navigator.storage`. Inject for tests and non-browser hosts. */
|
|
105
|
+
readonly storage?: OfflineStorageManagerLike;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Read the origin's real storage estimate and derive a conservative logical
|
|
109
|
+
* budget, or report explicitly that the platform cannot estimate.
|
|
110
|
+
*
|
|
111
|
+
* Performs no network I/O, writes nothing, and never calls
|
|
112
|
+
* `StorageManager.persist()`. `persisted()` is read when the platform offers it
|
|
113
|
+
* because persisted state changes the eviction risk of every cached region; a
|
|
114
|
+
* platform that does not offer it yields `persistence: "unknown"` rather than an
|
|
115
|
+
* assumption.
|
|
116
|
+
*
|
|
117
|
+
* Derivation is deterministic integer arithmetic over the reported values:
|
|
118
|
+
*
|
|
119
|
+
* ```text
|
|
120
|
+
* remaining = max(0, quota - usage)
|
|
121
|
+
* reserve = min(remaining, max(minimumReserveBytes, floor(remaining * headroomRatio)))
|
|
122
|
+
* budget = remaining - reserve
|
|
123
|
+
* ```
|
|
124
|
+
*
|
|
125
|
+
* so the budget can never exceed the platform-reported remaining quota, and a
|
|
126
|
+
* nearly full origin yields `0` instead of a number that cannot be honoured.
|
|
127
|
+
*/
|
|
128
|
+
export declare function probeOfflineStorageBudget(options?: ProbeOfflineStorageBudgetOptions): Promise<OfflineStorageBudgetV1>;
|
|
129
|
+
/**
|
|
130
|
+
* Ask the browser to make this origin's storage persistent.
|
|
131
|
+
*
|
|
132
|
+
* This is the only call site of `StorageManager.persist()` in the SDK and it
|
|
133
|
+
* runs only because an application called it. It is never invoked by
|
|
134
|
+
* `downloadOfflineRegion`, by the probe, or by any store adapter: the request
|
|
135
|
+
* can show a permission prompt, and a download is not consent to ask.
|
|
136
|
+
*/
|
|
137
|
+
export declare function requestOfflinePersistentStorage(options?: RequestOfflinePersistentStorageOptions): Promise<OfflineStoragePersistenceRequestV1>;
|
|
138
|
+
/**
|
|
139
|
+
* Report whether a thrown value is, or wraps, a platform storage-pressure
|
|
140
|
+
* failure — the device refusing a write for lack of space.
|
|
141
|
+
*
|
|
142
|
+
* Recognizes the `QuotaExceededError` `DOMException` every engine raises, the
|
|
143
|
+
* Firefox `NS_ERROR_DOM_QUOTA_REACHED` name, and the legacy numeric
|
|
144
|
+
* `DOMException` codes, then walks a bounded `cause` and `AggregateError` graph
|
|
145
|
+
* so a store adapter that wrapped the failure is still classified correctly.
|
|
146
|
+
* Bounded and cycle-safe: it inspects at most 32 nodes and visits none twice.
|
|
147
|
+
*
|
|
148
|
+
* Exported because a host-supplied `OfflineRegionStore` must classify the same
|
|
149
|
+
* condition the same way; without a shared predicate an adapter's wrapper would
|
|
150
|
+
* silently downgrade a full device to an internal store failure.
|
|
151
|
+
*/
|
|
152
|
+
export declare function isStorageQuotaPressureError(error: unknown): boolean;
|
|
153
|
+
/**
|
|
154
|
+
* Validate and take ownership of a caller-supplied budget envelope.
|
|
155
|
+
*
|
|
156
|
+
* Internal to the offline area: the diagnostic accepts a budget produced by
|
|
157
|
+
* `probeOfflineStorageBudget()`, and a public entry point cannot assume it was
|
|
158
|
+
* not hand-built.
|
|
159
|
+
*/
|
|
160
|
+
export declare function captureOfflineStorageBudget(value: unknown, path: string): OfflineStorageBudgetV1;
|
|
161
|
+
//# sourceMappingURL=quota.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quota.d.ts","sourceRoot":"","sources":["../../../src/offline/quota.ts"],"names":[],"mappings":"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"}
|