@mappedin/mvf-fetch 3.0.0-beta.15

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/CHANGELOG.md ADDED
@@ -0,0 +1,133 @@
1
+ # Changelog
2
+
3
+ ## [3.0.0-beta.15](https://github.com/MappedIn/mvf/compare/mvf-fetch-v3.0.0-beta.14...mvf-fetch-v3.0.0-beta.15) (2026-09-24)
4
+
5
+
6
+ ### Build
7
+
8
+ * **deps:** bump the npm_and_yarn group across 2 directories with 1 update ([#352](https://github.com/MappedIn/mvf/issues/352)) ([a64d7d3](https://github.com/MappedIn/mvf/commit/a64d7d333497dbe3ab6c49b37583f3df21c0f52c))
9
+
10
+
11
+ ### Dependencies
12
+
13
+ * The following workspace dependencies were updated
14
+ * devDependencies
15
+ * @mappedin/mvf-annotations bumped to 3.0.0-beta.15
16
+ * @mappedin/mvf-cms bumped to 3.0.0-beta.15
17
+ * @mappedin/mvf-connections bumped to 3.0.0-beta.15
18
+ * @mappedin/mvf-core bumped to 3.0.0-beta.15
19
+ * @mappedin/mvf-default-style bumped to 3.0.0-beta.15
20
+ * @mappedin/mvf-entrance-aesthetic bumped to 3.0.0-beta.15
21
+ * @mappedin/mvf-facade bumped to 3.0.0-beta.15
22
+ * @mappedin/mvf-floor-images bumped to 3.0.0-beta.15
23
+ * @mappedin/mvf-floor-stacks bumped to 3.0.0-beta.15
24
+ * @mappedin/mvf-kinds bumped to 3.0.0-beta.15
25
+ * @mappedin/mvf-locations bumped to 3.0.0-beta.15
26
+ * @mappedin/mvf-navigation-flags bumped to 3.0.0-beta.15
27
+ * @mappedin/mvf-nodes bumped to 3.0.0-beta.15
28
+ * @mappedin/mvf-outdoors bumped to 3.0.0-beta.15
29
+ * @mappedin/mvf-tileset bumped to 3.0.0-beta.15
30
+ * @mappedin/mvf-traversability bumped to 3.0.0-beta.15
31
+ * @mappedin/mvf-v2 bumped to 2.100.0-final.15
32
+ * peerDependencies
33
+ * @mappedin/mvf-core bumped to 3.0.0-beta.15
34
+ * @mappedin/mvf-v2 bumped to 2.100.0-final.15
35
+
36
+ ## [3.0.0-beta.14](https://github.com/MappedIn/mvf/compare/mvf-fetch-v3.0.0-beta.13...mvf-fetch-v3.0.0-beta.14) (2026-09-21)
37
+
38
+
39
+ ### Bug Fixes
40
+
41
+ * **EINT-1520:** remove azure login that is no longer used ([#357](https://github.com/MappedIn/mvf/issues/357)) ([48ebfa8](https://github.com/MappedIn/mvf/commit/48ebfa8d64788b90ce781d8e7a6fde8976678ccb))
42
+
43
+
44
+ ### Dependencies
45
+
46
+ * The following workspace dependencies were updated
47
+ * devDependencies
48
+ * @mappedin/mvf-annotations bumped to 3.0.0-beta.14
49
+ * @mappedin/mvf-cms bumped to 3.0.0-beta.14
50
+ * @mappedin/mvf-connections bumped to 3.0.0-beta.14
51
+ * @mappedin/mvf-core bumped to 3.0.0-beta.14
52
+ * @mappedin/mvf-default-style bumped to 3.0.0-beta.14
53
+ * @mappedin/mvf-entrance-aesthetic bumped to 3.0.0-beta.14
54
+ * @mappedin/mvf-facade bumped to 3.0.0-beta.14
55
+ * @mappedin/mvf-floor-images bumped to 3.0.0-beta.14
56
+ * @mappedin/mvf-floor-stacks bumped to 3.0.0-beta.14
57
+ * @mappedin/mvf-kinds bumped to 3.0.0-beta.14
58
+ * @mappedin/mvf-locations bumped to 3.0.0-beta.14
59
+ * @mappedin/mvf-navigation-flags bumped to 3.0.0-beta.14
60
+ * @mappedin/mvf-nodes bumped to 3.0.0-beta.14
61
+ * @mappedin/mvf-outdoors bumped to 3.0.0-beta.14
62
+ * @mappedin/mvf-tileset bumped to 3.0.0-beta.14
63
+ * @mappedin/mvf-traversability bumped to 3.0.0-beta.14
64
+ * @mappedin/mvf-v2 bumped to 2.100.0-final.14
65
+ * peerDependencies
66
+ * @mappedin/mvf-core bumped to 3.0.0-beta.14
67
+ * @mappedin/mvf-v2 bumped to 2.100.0-final.14
68
+
69
+ ## [3.0.0-beta.13](https://github.com/MappedIn/mvf/compare/mvf-fetch-v3.0.0-beta.12...mvf-fetch-v3.0.0-beta.13) (2026-09-21)
70
+
71
+
72
+ ### chore
73
+
74
+ * **mvf-fetch:** Synchronize mvf versions
75
+
76
+
77
+ ### Dependencies
78
+
79
+ * The following workspace dependencies were updated
80
+ * devDependencies
81
+ * @mappedin/mvf-annotations bumped to 3.0.0-beta.13
82
+ * @mappedin/mvf-cms bumped to 3.0.0-beta.13
83
+ * @mappedin/mvf-connections bumped to 3.0.0-beta.13
84
+ * @mappedin/mvf-core bumped to 3.0.0-beta.13
85
+ * @mappedin/mvf-default-style bumped to 3.0.0-beta.13
86
+ * @mappedin/mvf-entrance-aesthetic bumped to 3.0.0-beta.13
87
+ * @mappedin/mvf-facade bumped to 3.0.0-beta.13
88
+ * @mappedin/mvf-floor-images bumped to 3.0.0-beta.13
89
+ * @mappedin/mvf-floor-stacks bumped to 3.0.0-beta.13
90
+ * @mappedin/mvf-kinds bumped to 3.0.0-beta.13
91
+ * @mappedin/mvf-locations bumped to 3.0.0-beta.13
92
+ * @mappedin/mvf-navigation-flags bumped to 3.0.0-beta.13
93
+ * @mappedin/mvf-nodes bumped to 3.0.0-beta.13
94
+ * @mappedin/mvf-outdoors bumped to 3.0.0-beta.13
95
+ * @mappedin/mvf-tileset bumped to 3.0.0-beta.13
96
+ * @mappedin/mvf-traversability bumped to 3.0.0-beta.13
97
+ * @mappedin/mvf-v2 bumped to 2.100.0-final.13
98
+ * peerDependencies
99
+ * @mappedin/mvf-core bumped to 3.0.0-beta.13
100
+ * @mappedin/mvf-v2 bumped to 2.100.0-final.13
101
+
102
+ ## [3.0.0-beta.12](https://github.com/MappedIn/mvf/compare/mvf-fetch-v3.0.0-beta.11...mvf-fetch-v3.0.0-beta.12) (2026-08-18)
103
+
104
+
105
+ ### Features
106
+
107
+ * **mvf-fetch:** add package for range-based MVF zip reads ([#345](https://github.com/MappedIn/mvf/issues/345)) ([55d5a24](https://github.com/MappedIn/mvf/commit/55d5a24b410db4e654701bdc460cbb629ac0fee4))
108
+
109
+
110
+ ### Dependencies
111
+
112
+ * The following workspace dependencies were updated
113
+ * devDependencies
114
+ * @mappedin/mvf-annotations bumped to 3.0.0-beta.12
115
+ * @mappedin/mvf-cms bumped to 3.0.0-beta.12
116
+ * @mappedin/mvf-connections bumped to 3.0.0-beta.12
117
+ * @mappedin/mvf-core bumped to 3.0.0-beta.12
118
+ * @mappedin/mvf-default-style bumped to 3.0.0-beta.12
119
+ * @mappedin/mvf-entrance-aesthetic bumped to 3.0.0-beta.12
120
+ * @mappedin/mvf-facade bumped to 3.0.0-beta.12
121
+ * @mappedin/mvf-floor-images bumped to 3.0.0-beta.12
122
+ * @mappedin/mvf-floor-stacks bumped to 3.0.0-beta.12
123
+ * @mappedin/mvf-kinds bumped to 3.0.0-beta.12
124
+ * @mappedin/mvf-locations bumped to 3.0.0-beta.12
125
+ * @mappedin/mvf-navigation-flags bumped to 3.0.0-beta.12
126
+ * @mappedin/mvf-nodes bumped to 3.0.0-beta.12
127
+ * @mappedin/mvf-outdoors bumped to 3.0.0-beta.12
128
+ * @mappedin/mvf-tileset bumped to 3.0.0-beta.12
129
+ * @mappedin/mvf-traversability bumped to 3.0.0-beta.12
130
+ * @mappedin/mvf-v2 bumped to 2.100.0-final.12
131
+ * peerDependencies
132
+ * @mappedin/mvf-core bumped to 3.0.0-beta.12
133
+ * @mappedin/mvf-v2 bumped to 2.100.0-final.12
package/README.md ADDED
@@ -0,0 +1,23 @@
1
+ # MVF Fetch
2
+
3
+ Isomorphic utilities for efficiently reading published MVF v2/v3 zip archives from the cloud via HTTP Range requests.
4
+
5
+ This package focuses on extracting and optionally validating individual zip entries. It does not render maps or convert between MVF versions.
6
+
7
+ ## Usage
8
+
9
+ ```ts
10
+ import { createMvfFetcher } from '@mappedin/mvf-fetch';
11
+ import { autoMvfValidator } from '@mappedin/mvf-fetch/validators';
12
+
13
+ const fetcher = createMvfFetcher({
14
+ url: async () => ({
15
+ url: signedZipUrl,
16
+ cacheKey: `${venueId}:${updatedAt}`,
17
+ }),
18
+ validate: autoMvfValidator(),
19
+ });
20
+
21
+ const version = await fetcher.getVersion();
22
+ const { files } = await fetcher.getEntries(['manifest.geojson', 'floors.geojson']);
23
+ ```
@@ -0,0 +1,51 @@
1
+ import { MvfZipSource } from './source.js';
2
+ import type { UrlResolver } from './url/resolver.js';
3
+ import type { FileValidator } from './validate.js';
4
+ import { type ManifestFeatureCollection, type MvfVersion, groupEntriesByVersion } from './version.js';
5
+ import type { ZipIndex } from './zip/index.js';
6
+ import type { FetchFn } from './zip/range.js';
7
+ export interface CreateMvfFetcherOptions {
8
+ url: UrlResolver;
9
+ validate?: FileValidator;
10
+ concurrency?: number;
11
+ fetchFn?: FetchFn;
12
+ indexCacheKey?: string;
13
+ /**
14
+ * Merge entry ranges separated by at most this many unwanted bytes into one request.
15
+ * @default 0
16
+ */
17
+ maxGapBytes?: number;
18
+ /** An already-parsed index for this archive, so opening costs no request at all. */
19
+ index?: ZipIndex;
20
+ }
21
+ export interface FetchResult {
22
+ version: MvfVersion;
23
+ files: Record<string, unknown>;
24
+ }
25
+ export interface GetEntriesOptions {
26
+ allowMissing?: boolean;
27
+ }
28
+ /**
29
+ * High-level orchestrator for fetching MVF zip entries from the cloud.
30
+ */
31
+ export declare class MvfFetcher {
32
+ #private;
33
+ constructor(options: CreateMvfFetcherOptions);
34
+ get source(): MvfZipSource;
35
+ getVersion(): Promise<MvfVersion>;
36
+ getManifest(): Promise<ManifestFeatureCollection>;
37
+ getMapId(): Promise<string | undefined>;
38
+ listEntries(): Promise<string[]>;
39
+ getEntry(path: string): Promise<unknown | undefined>;
40
+ getEntries(paths: string[], options?: GetEntriesOptions): Promise<FetchResult>;
41
+ getFloor(floorId: string): Promise<FetchResult>;
42
+ getGlobals(): Promise<FetchResult>;
43
+ getAll(): Promise<FetchResult>;
44
+ getFloorIds(): Promise<string[]>;
45
+ groupEntries(): Promise<ReturnType<typeof groupEntriesByVersion>>;
46
+ }
47
+ /**
48
+ * Creates an {@link MvfFetcher} for reading MVF zip entries from the cloud.
49
+ */
50
+ export declare function createMvfFetcher(options: CreateMvfFetcherOptions): MvfFetcher;
51
+ //# sourceMappingURL=fetcher.d.ts.map
@@ -0,0 +1,93 @@
1
+ import { MvfZipSource } from './source.js';
2
+ import { validateFetchedFiles } from './validate.js';
3
+ import { detectVersion, extractFloorIds, getFloorEntryPaths, getGlobalPaths, getManifest, getMapIdFromManifest, groupEntriesByVersion, } from './version.js';
4
+ /**
5
+ * High-level orchestrator for fetching MVF zip entries from the cloud.
6
+ */
7
+ export class MvfFetcher {
8
+ #source;
9
+ #validate;
10
+ #version = null;
11
+ #manifest = null;
12
+ constructor(options) {
13
+ this.#source = new MvfZipSource({
14
+ url: options.url,
15
+ fetchFn: options.fetchFn,
16
+ indexCacheKey: options.indexCacheKey,
17
+ concurrency: options.concurrency,
18
+ maxGapBytes: options.maxGapBytes,
19
+ index: options.index,
20
+ });
21
+ this.#validate = options.validate;
22
+ }
23
+ get source() {
24
+ return this.#source;
25
+ }
26
+ async getVersion() {
27
+ if (this.#version != null) {
28
+ return this.#version;
29
+ }
30
+ this.#version = await detectVersion(this.#source);
31
+ return this.#version;
32
+ }
33
+ async getManifest() {
34
+ if (this.#manifest != null) {
35
+ return this.#manifest;
36
+ }
37
+ this.#manifest = await getManifest(this.#source);
38
+ return this.#manifest;
39
+ }
40
+ async getMapId() {
41
+ const manifest = await this.getManifest();
42
+ return getMapIdFromManifest(manifest);
43
+ }
44
+ async listEntries() {
45
+ return this.#source.listEntries();
46
+ }
47
+ async getEntry(path) {
48
+ const result = await this.getEntries([path], { allowMissing: true });
49
+ return result.files[path];
50
+ }
51
+ async getEntries(paths, options = {}) {
52
+ const version = await this.getVersion();
53
+ const files = await this.#source.getEntries(paths, options);
54
+ await validateFetchedFiles(this.#validate, version, files);
55
+ return { version, files };
56
+ }
57
+ async getFloor(floorId) {
58
+ const version = await this.getVersion();
59
+ const availablePaths = await this.#source.listEntries();
60
+ const paths = getFloorEntryPaths(version, floorId, availablePaths);
61
+ return this.getEntries(paths, { allowMissing: true });
62
+ }
63
+ async getGlobals() {
64
+ const version = await this.getVersion();
65
+ const availablePaths = new Set(await this.#source.listEntries());
66
+ const paths = getGlobalPaths(version).filter((path) => availablePaths.has(path));
67
+ return this.getEntries(paths, { allowMissing: true });
68
+ }
69
+ async getAll() {
70
+ const version = await this.getVersion();
71
+ const paths = await this.#source.listEntries();
72
+ const files = await this.#source.getEntries(paths);
73
+ await validateFetchedFiles(this.#validate, version, files);
74
+ return { version, files };
75
+ }
76
+ async getFloorIds() {
77
+ const version = await this.getVersion();
78
+ const paths = await this.#source.listEntries();
79
+ return extractFloorIds(version, paths);
80
+ }
81
+ async groupEntries() {
82
+ const version = await this.getVersion();
83
+ const paths = await this.#source.listEntries();
84
+ return groupEntriesByVersion(version, paths);
85
+ }
86
+ }
87
+ /**
88
+ * Creates an {@link MvfFetcher} for reading MVF zip entries from the cloud.
89
+ */
90
+ export function createMvfFetcher(options) {
91
+ return new MvfFetcher(options);
92
+ }
93
+ //# sourceMappingURL=fetcher.js.map
@@ -0,0 +1,20 @@
1
+ export { createMvfFetcher, MvfFetcher } from './fetcher.js';
2
+ export type { CreateMvfFetcherOptions, FetchResult, GetEntriesOptions } from './fetcher.js';
3
+ export { MvfZipSource } from './source.js';
4
+ export type { GetZipEntriesOptions, MvfZipSourceOptions } from './source.js';
5
+ export { CachedUrlResolver, createIndexCacheKey, resolveUrl, } from './url/resolver.js';
6
+ export type { CachedUrlResolverOptions, ResolvedUrl, UrlResolver } from './url/resolver.js';
7
+ export { detectVersion, extractFloorIds, getFloorEntryPaths, getGlobalPaths, getManifest, getMapIdFromManifest, groupEntriesByVersion, getPerFloorFolders, } from './version.js';
8
+ export type { ManifestFeatureCollection, MvfVersion } from './version.js';
9
+ export { ValidationError, validateFetchedFile, validateFetchedFiles, } from './validate.js';
10
+ export type { FetchedFile, FileValidator } from './validate.js';
11
+ export { coalesceRanges, fetchCoalescedRanges, RangeBufferCache } from './zip/ranges.js';
12
+ export type { ByteRange } from './zip/ranges.js';
13
+ export { computeEntryEstimatedRange, countCoalescedRangesForEntries, decodeZipEntryJson, decompressEntry, extractCompressedBytes, readZipEntryBytes, readZipEntries, } from './zip/entry.js';
14
+ export type { ReadZipEntriesOptions } from './zip/entry.js';
15
+ export { DEFAULT_TAIL_READ_LENGTH, openRemoteZipIndex, parseCentralDirectory, parseEndOfCentralDirectory, } from './zip/index.js';
16
+ export type { ZipEndOfCentralDirectory, ZipEntryMeta, ZipIndex } from './zip/index.js';
17
+ export { fetchByteRange, fetchZipTail, getTotalSize, HttpRangeError, parseContentRange } from './zip/range.js';
18
+ export type { FetchFn, ZipTail } from './zip/range.js';
19
+ export { chunkArray, readUint16LE, readUint32LE, uniquePaths } from './zip/bytes.js';
20
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ export { createMvfFetcher, MvfFetcher } from './fetcher.js';
2
+ export { MvfZipSource } from './source.js';
3
+ export { CachedUrlResolver, createIndexCacheKey, resolveUrl, } from './url/resolver.js';
4
+ export { detectVersion, extractFloorIds, getFloorEntryPaths, getGlobalPaths, getManifest, getMapIdFromManifest, groupEntriesByVersion, getPerFloorFolders, } from './version.js';
5
+ export { ValidationError, validateFetchedFile, validateFetchedFiles, } from './validate.js';
6
+ export { coalesceRanges, fetchCoalescedRanges, RangeBufferCache } from './zip/ranges.js';
7
+ export { computeEntryEstimatedRange, countCoalescedRangesForEntries, decodeZipEntryJson, decompressEntry, extractCompressedBytes, readZipEntryBytes, readZipEntries, } from './zip/entry.js';
8
+ export { DEFAULT_TAIL_READ_LENGTH, openRemoteZipIndex, parseCentralDirectory, parseEndOfCentralDirectory, } from './zip/index.js';
9
+ export { fetchByteRange, fetchZipTail, getTotalSize, HttpRangeError, parseContentRange } from './zip/range.js';
10
+ export { chunkArray, readUint16LE, readUint32LE, uniquePaths } from './zip/bytes.js';
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,51 @@
1
+ import { type UrlResolver } from './url/resolver.js';
2
+ import { type ZipIndex } from './zip/index.js';
3
+ import type { FetchFn } from './zip/range.js';
4
+ export interface MvfZipSourceOptions {
5
+ url: UrlResolver;
6
+ fetchFn?: FetchFn;
7
+ indexCacheKey?: string;
8
+ concurrency?: number;
9
+ /**
10
+ * Merge entry ranges separated by at most this many unwanted bytes into one request.
11
+ * Useful when the wanted entries are scattered through the archive.
12
+ * @default 0
13
+ */
14
+ maxGapBytes?: number;
15
+ /**
16
+ * An already-parsed index for this archive, so opening costs no request at all.
17
+ *
18
+ * The index is plain data, so it survives `structuredClone`: a caller that has
19
+ * already opened the archive can hand it to a worker, or persist it against
20
+ * {@link MvfZipSourceOptions.indexCacheKey}, instead of reading the central
21
+ * directory a second time.
22
+ */
23
+ index?: ZipIndex;
24
+ }
25
+ export interface GetZipEntriesOptions {
26
+ allowMissing?: boolean;
27
+ }
28
+ /**
29
+ * Low-level source for reading individual entries from a remote MVF zip.
30
+ */
31
+ export declare class MvfZipSource {
32
+ #private;
33
+ constructor(options: MvfZipSourceOptions);
34
+ get indexCacheKey(): string | undefined;
35
+ listEntries(): Promise<string[]>;
36
+ getEntryBytes(path: string): Promise<Uint8Array | undefined>;
37
+ getEntry<T = unknown>(path: string): Promise<T | undefined>;
38
+ /**
39
+ * Reads several entries as decompressed bytes, coalescing their byte ranges.
40
+ *
41
+ * Prefer this over calling {@link MvfZipSource.getEntryBytes} per path: that issues one
42
+ * request per entry, while this reads them in as few requests as their layout allows.
43
+ * Consumers that need bytes rather than objects — a parser with its own decoder, or a
44
+ * worker the bytes are posted to — should use this instead of
45
+ * {@link MvfZipSource.getEntries}, which decodes and discards them.
46
+ */
47
+ getEntriesBytes(paths: string[], options?: GetZipEntriesOptions): Promise<Map<string, Uint8Array>>;
48
+ getEntries(paths: string[], options?: GetZipEntriesOptions): Promise<Record<string, unknown>>;
49
+ invalidate(): void;
50
+ }
51
+ //# sourceMappingURL=source.d.ts.map
package/dist/source.js ADDED
@@ -0,0 +1,112 @@
1
+ import { CachedUrlResolver } from './url/resolver.js';
2
+ import { uniquePaths } from './zip/bytes.js';
3
+ import { decodeZipEntryJson, readZipEntries } from './zip/entry.js';
4
+ import { openRemoteZipIndex } from './zip/index.js';
5
+ /**
6
+ * Low-level source for reading individual entries from a remote MVF zip.
7
+ */
8
+ export class MvfZipSource {
9
+ #urlResolver;
10
+ #fetchFn;
11
+ #indexCacheKey;
12
+ #concurrency;
13
+ #maxGapBytes;
14
+ /** Handed in by the caller rather than read from the archive. Survives cache-key changes. */
15
+ #providedIndex;
16
+ #index = null;
17
+ #indexCacheKeyUsed;
18
+ #pendingIndex = null;
19
+ constructor(options) {
20
+ this.#urlResolver = new CachedUrlResolver({ resolver: options.url });
21
+ this.#fetchFn = options.fetchFn ?? fetch;
22
+ this.#indexCacheKey = options.indexCacheKey;
23
+ this.#concurrency = options.concurrency ?? 32;
24
+ this.#maxGapBytes = options.maxGapBytes ?? 0;
25
+ this.#providedIndex = options.index ?? null;
26
+ }
27
+ get indexCacheKey() {
28
+ return this.#indexCacheKey ?? this.#urlResolver.cacheKey;
29
+ }
30
+ async listEntries() {
31
+ const index = await this.#getIndex();
32
+ return Object.keys(index.entries).sort();
33
+ }
34
+ async getEntryBytes(path) {
35
+ const entries = await this.getEntriesBytes([path], { allowMissing: true });
36
+ return entries.get(path);
37
+ }
38
+ async getEntry(path) {
39
+ const bytes = await this.getEntryBytes(path);
40
+ if (bytes == null) {
41
+ return undefined;
42
+ }
43
+ return decodeZipEntryJson(bytes);
44
+ }
45
+ /**
46
+ * Reads several entries as decompressed bytes, coalescing their byte ranges.
47
+ *
48
+ * Prefer this over calling {@link MvfZipSource.getEntryBytes} per path: that issues one
49
+ * request per entry, while this reads them in as few requests as their layout allows.
50
+ * Consumers that need bytes rather than objects — a parser with its own decoder, or a
51
+ * worker the bytes are posted to — should use this instead of
52
+ * {@link MvfZipSource.getEntries}, which decodes and discards them.
53
+ */
54
+ async getEntriesBytes(paths, options = {}) {
55
+ const unique = uniquePaths(paths);
56
+ if (unique.length === 0) {
57
+ return new Map();
58
+ }
59
+ const index = await this.#getIndex();
60
+ return this.#urlResolver.withAuthRetry((url) => readZipEntries(url, unique, index, {
61
+ allowMissing: options.allowMissing ?? false,
62
+ concurrency: this.#concurrency,
63
+ fetchFn: this.#fetchFn,
64
+ maxGapBytes: this.#maxGapBytes,
65
+ }));
66
+ }
67
+ async getEntries(paths, options = {}) {
68
+ const bytesByPath = await this.getEntriesBytes(paths, options);
69
+ const files = {};
70
+ for (const [path, bytes] of bytesByPath) {
71
+ files[path] = decodeZipEntryJson(bytes);
72
+ }
73
+ return files;
74
+ }
75
+ invalidate() {
76
+ this.#urlResolver.invalidate();
77
+ // A provided index is dropped too: invalidate means "trust nothing I was given".
78
+ this.#providedIndex = null;
79
+ this.#index = null;
80
+ this.#indexCacheKeyUsed = undefined;
81
+ this.#pendingIndex = null;
82
+ }
83
+ async #getIndex() {
84
+ if (this.#providedIndex != null) {
85
+ return this.#providedIndex;
86
+ }
87
+ const cacheKey = this.indexCacheKey;
88
+ if (this.#index != null && this.#indexCacheKeyUsed === cacheKey) {
89
+ return this.#index;
90
+ }
91
+ if (this.#pendingIndex != null) {
92
+ return this.#pendingIndex;
93
+ }
94
+ const pending = this.#urlResolver
95
+ .withAuthRetry((url) => openRemoteZipIndex(url, this.#fetchFn))
96
+ .then((index) => {
97
+ this.#index = index;
98
+ this.#indexCacheKeyUsed = cacheKey;
99
+ return index;
100
+ })
101
+ // Clearing on failure too lets a later call retry instead of replaying the rejection.
102
+ // The identity check keeps a stale open from discarding a newer one after invalidate().
103
+ .finally(() => {
104
+ if (this.#pendingIndex === pending) {
105
+ this.#pendingIndex = null;
106
+ }
107
+ });
108
+ this.#pendingIndex = pending;
109
+ return pending;
110
+ }
111
+ }
112
+ //# sourceMappingURL=source.js.map
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Result of resolving a signed zip URL.
3
+ */
4
+ export interface ResolvedUrl {
5
+ url: string;
6
+ cacheKey?: string;
7
+ }
8
+ /**
9
+ * A plain URL string or an async resolver that returns a signed URL.
10
+ */
11
+ export type UrlResolver = string | (() => Promise<ResolvedUrl | string>);
12
+ export interface CachedUrlResolverOptions {
13
+ resolver: UrlResolver;
14
+ }
15
+ /**
16
+ * Normalizes a resolver result into a {@link ResolvedUrl}.
17
+ */
18
+ export declare function resolveUrl(resolver: UrlResolver): Promise<ResolvedUrl>;
19
+ /**
20
+ * Wraps a {@link UrlResolver} with URL/index cache invalidation on 401/403.
21
+ */
22
+ export declare class CachedUrlResolver {
23
+ #private;
24
+ constructor(options: CachedUrlResolverOptions);
25
+ get cacheKey(): string | undefined;
26
+ getUrl(forceRefresh?: boolean): Promise<string>;
27
+ invalidate(): void;
28
+ withAuthRetry<T>(operation: (url: string) => Promise<T>): Promise<T>;
29
+ }
30
+ /**
31
+ * Creates a cache key for zip index caching.
32
+ */
33
+ export declare function createIndexCacheKey(resolver: CachedUrlResolver): string | undefined;
34
+ //# sourceMappingURL=resolver.d.ts.map
@@ -0,0 +1,78 @@
1
+ import { HttpRangeError } from '../zip/range.js';
2
+ /**
3
+ * Normalizes a resolver result into a {@link ResolvedUrl}.
4
+ */
5
+ export async function resolveUrl(resolver) {
6
+ if (typeof resolver === 'string') {
7
+ return { url: resolver };
8
+ }
9
+ const result = await resolver();
10
+ if (typeof result === 'string') {
11
+ return { url: result };
12
+ }
13
+ return result;
14
+ }
15
+ /**
16
+ * Wraps a {@link UrlResolver} with URL/index cache invalidation on 401/403.
17
+ */
18
+ export class CachedUrlResolver {
19
+ #resolver;
20
+ #state = null;
21
+ #pending = null;
22
+ constructor(options) {
23
+ this.#resolver = options.resolver;
24
+ }
25
+ get cacheKey() {
26
+ return this.#state?.cacheKey;
27
+ }
28
+ async getUrl(forceRefresh = false) {
29
+ const state = await this.#ensureState(forceRefresh);
30
+ return state.url;
31
+ }
32
+ invalidate() {
33
+ this.#state = null;
34
+ this.#pending = null;
35
+ }
36
+ async withAuthRetry(operation) {
37
+ let refreshed = false;
38
+ while (true) {
39
+ const url = await this.getUrl(refreshed);
40
+ try {
41
+ return await operation(url);
42
+ }
43
+ catch (error) {
44
+ if (!refreshed && error instanceof HttpRangeError && (error.status === 401 || error.status === 403)) {
45
+ this.invalidate();
46
+ refreshed = true;
47
+ continue;
48
+ }
49
+ throw error;
50
+ }
51
+ }
52
+ }
53
+ async #ensureState(forceRefresh) {
54
+ if (!forceRefresh && this.#state != null) {
55
+ return this.#state;
56
+ }
57
+ if (!forceRefresh && this.#pending != null) {
58
+ return this.#pending;
59
+ }
60
+ this.#pending = resolveUrl(this.#resolver).then((resolved) => {
61
+ const next = {
62
+ url: resolved.url,
63
+ cacheKey: resolved.cacheKey,
64
+ };
65
+ this.#state = next;
66
+ this.#pending = null;
67
+ return next;
68
+ });
69
+ return this.#pending;
70
+ }
71
+ }
72
+ /**
73
+ * Creates a cache key for zip index caching.
74
+ */
75
+ export function createIndexCacheKey(resolver) {
76
+ return resolver.cacheKey;
77
+ }
78
+ //# sourceMappingURL=resolver.js.map
@@ -0,0 +1,32 @@
1
+ import type { MvfVersion } from './version.js';
2
+ /**
3
+ * A fetched MVF zip entry decoded as JSON.
4
+ */
5
+ export interface FetchedFile {
6
+ path: string;
7
+ version: MvfVersion;
8
+ contents: unknown;
9
+ }
10
+ /**
11
+ * Validates a single fetched MVF file.
12
+ *
13
+ * Implementations should throw when validation fails.
14
+ */
15
+ export type FileValidator = (file: FetchedFile) => void | Promise<void>;
16
+ /**
17
+ * Thrown when a fetched MVF file fails validation.
18
+ */
19
+ export declare class ValidationError extends Error {
20
+ readonly path: string;
21
+ readonly cause: unknown;
22
+ constructor(path: string, cause: unknown);
23
+ }
24
+ /**
25
+ * Invokes a validator and wraps failures in {@link ValidationError}.
26
+ */
27
+ export declare function validateFetchedFile(validator: FileValidator | undefined, file: FetchedFile): Promise<void>;
28
+ /**
29
+ * Validates multiple fetched files sequentially.
30
+ */
31
+ export declare function validateFetchedFiles(validator: FileValidator | undefined, version: MvfVersion, files: Record<string, unknown>): Promise<void>;
32
+ //# sourceMappingURL=validate.d.ts.map