-
Notifications
You must be signed in to change notification settings - Fork 1
resolve vcc:// virtual chunk locations against repo config #16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -108,18 +108,22 @@ export class ReadSession { | |
| private nextFetchClientId = 1; | ||
| private rangeCoalescerIds?: WeakMap<RangeCoalescingFn, number>; | ||
| private nextRangeCoalescerId = 1; | ||
| /** VCC name → url_prefix map for resolving `vcc://` chunk locations. */ | ||
| private virtualChunkContainers: Map<string, string>; | ||
|
|
||
| private constructor( | ||
| storage: Storage, | ||
| snapshot: Snapshot, | ||
| specVersion: SpecVersion, | ||
| maxManifestCacheSize: number = 100, | ||
| virtualChunkContainers?: Map<string, string>, | ||
| ) { | ||
| this.storage = storage; | ||
| this.snapshot = snapshot; | ||
| this.specVersion = specVersion; | ||
| this.manifestCache = new LRUCache(maxManifestCacheSize); | ||
| this.manifestLoader = singleFlight(this.manifestCache); | ||
| this.virtualChunkContainers = virtualChunkContainers ?? new Map(); | ||
| } | ||
|
|
||
| private getFetchClientKey(fetchClient: FetchClient | undefined): string { | ||
|
|
@@ -250,7 +254,10 @@ export class ReadSession { | |
| static async open( | ||
| storage: Storage, | ||
| snapshotId: Uint8Array, | ||
| options?: RequestOptions & { maxManifestCacheSize?: number }, | ||
| options?: RequestOptions & { | ||
| maxManifestCacheSize?: number; | ||
| virtualChunkContainers?: Map<string, string>; | ||
| }, | ||
| ): Promise<ReadSession> { | ||
| const { snapshot, specVersion } = await ReadSession.loadSnapshot( | ||
| storage, | ||
|
|
@@ -262,6 +269,7 @@ export class ReadSession { | |
| snapshot, | ||
| specVersion, | ||
| options?.maxManifestCacheSize, | ||
| options?.virtualChunkContainers, | ||
| ); | ||
| } | ||
|
|
||
|
|
@@ -628,9 +636,13 @@ export class ReadSession { | |
|
|
||
| case "virtual": { | ||
| // Virtual chunks reference external URLs | ||
| // Translate cloud storage URLs to HTTPS endpoints | ||
| const httpUrl = translateToHttpUrl( | ||
| // Expand any vcc://name/path → absolute URL, then translate s3:// etc. → HTTPS | ||
| const absoluteLocation = expandVccUrl( | ||
| payload.location, | ||
| this.virtualChunkContainers, | ||
| ); | ||
| const httpUrl = translateToHttpUrl( | ||
| absoluteLocation, | ||
| options?.azureAccount, | ||
| ); | ||
|
|
||
|
|
@@ -712,8 +724,12 @@ export class ReadSession { | |
| const absoluteStart = payload.offset + rangeStart; | ||
| const expectedSize = rangeEnd - rangeStart; | ||
|
|
||
| const httpUrl = translateToHttpUrl( | ||
| const absoluteLocation = expandVccUrl( | ||
| payload.location, | ||
| this.virtualChunkContainers, | ||
| ); | ||
| const httpUrl = translateToHttpUrl( | ||
| absoluteLocation, | ||
| options?.azureAccount, | ||
| ); | ||
|
|
||
|
|
@@ -803,6 +819,58 @@ function compareUtf8Bytes(a: string, b: string): number { | |
| return bytesA.length - bytesB.length; | ||
| } | ||
|
|
||
| /** URL scheme for relative Virtual Chunk Container references. */ | ||
| const VCC_SCHEME = "vcc://"; | ||
|
|
||
| /** | ||
| * Expand a `vcc://name/relative/path` chunk location to an absolute URL using | ||
| * the repo's Virtual Chunk Container map. Pass-through for any location that | ||
| * doesn't start with `vcc://`. | ||
| * | ||
| * When the map is empty (e.g. a `ReadSession` constructed without a | ||
| * `Repository`), vcc:// locations pass through unchanged so a caller's | ||
| * `fetchClient` can still handle them. When the map is non-empty but the | ||
| * referenced name is missing, we throw — that signals a real config / | ||
| * manifest mismatch the caller should surface. | ||
| * | ||
| * Persisted repo configs may contain legacy/migrated prefixes without a | ||
| * trailing slash. Normalize those before joining so `vcc://name/path` | ||
| * resolves under the configured container root instead of being appended to | ||
| * the last prefix segment. | ||
| * | ||
| * @throws Error when the URL is malformed or the name is unknown despite a | ||
| * populated container map. | ||
| */ | ||
| export function expandVccUrl( | ||
| location: string, | ||
| containers: Map<string, string>, | ||
| ): string { | ||
| if (!location.startsWith(VCC_SCHEME)) return location; | ||
|
|
||
| const rest = location.slice(VCC_SCHEME.length); | ||
| const slash = rest.indexOf("/"); | ||
| if (slash === -1) { | ||
| throw new Error( | ||
| `Invalid vcc:// URL "${location}": missing "/" after container name`, | ||
| ); | ||
| } | ||
|
|
||
| const name = rest.slice(0, slash); | ||
| const relativePath = rest.slice(slash + 1); | ||
| const urlPrefix = containers.get(name); | ||
| if (urlPrefix === undefined) { | ||
| if (containers.size === 0) return location; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Returning the original |
||
| throw new Error( | ||
| `Unknown virtual chunk container "${name}" referenced by ${location}`, | ||
| ); | ||
| } | ||
|
|
||
| const normalizedPrefix = urlPrefix.endsWith("/") | ||
| ? urlPrefix | ||
| : `${urlPrefix}/`; | ||
| return normalizedPrefix + relativePath; | ||
| } | ||
|
|
||
| /** | ||
| * Translate cloud storage URLs to HTTP(S) endpoints for public buckets. | ||
| * | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The use of
ArrayBuffer.prototype.slice()creates a copy of the underlying buffer. While the repository configuration is typically small, this allocation can be avoided by using aUint8Arrayview directly if theflexbufferslibrary supports it. Ifflexbuffers.toObjectstrictly requires anArrayBufferthat contains only the relevant data, this approach is correct, but it's worth verifying if a view can be used to improve efficiency.