{"version":3,"sources":["/Users/bdon/workspace/protomaps/PMTiles/js/dist/cjs/index.cjs","../../src/index.ts","../../src/adapters.ts"],"names":["leafletRasterLayer","__name","source","options","loaded","mimeType","cls","coord","done","el","controller","signal","header","arr","blob","imageUrl","e","key","tile","v3compat","v4","requestParameters","arg2","abortController","result","err","_Protocol","params","__async","pmtilesUrl","instance","PMTiles","data"],"mappings":"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","file":"/Users/bdon/workspace/protomaps/PMTiles/js/dist/cjs/index.cjs","sourcesContent":[null,"import { decompressSync } from \"fflate\";\nexport * from \"./adapters\";\n\n/** @hidden */\nexport interface BufferPosition {\n  buf: Uint8Array;\n  pos: number;\n}\n\nfunction toNum(low: number, high: number): number {\n  return (high >>> 0) * 0x100000000 + (low >>> 0);\n}\n\nfunction readVarintRemainder(l: number, p: BufferPosition): number {\n  const buf = p.buf;\n  let b = buf[p.pos++];\n  let h = (b & 0x70) >> 4;\n  if (b < 0x80) return toNum(l, h);\n  b = buf[p.pos++];\n  h |= (b & 0x7f) << 3;\n  if (b < 0x80) return toNum(l, h);\n  b = buf[p.pos++];\n  h |= (b & 0x7f) << 10;\n  if (b < 0x80) return toNum(l, h);\n  b = buf[p.pos++];\n  h |= (b & 0x7f) << 17;\n  if (b < 0x80) return toNum(l, h);\n  b = buf[p.pos++];\n  h |= (b & 0x7f) << 24;\n  if (b < 0x80) return toNum(l, h);\n  b = buf[p.pos++];\n  h |= (b & 0x01) << 31;\n  if (b < 0x80) return toNum(l, h);\n  throw new Error(\"Expected varint not more than 10 bytes\");\n}\n\n/** @hidden */\nexport function readVarint(p: BufferPosition): number {\n  const buf = p.buf;\n  let b = buf[p.pos++];\n  let val = b & 0x7f;\n  if (b < 0x80) return val;\n  b = buf[p.pos++];\n  val |= (b & 0x7f) << 7;\n  if (b < 0x80) return val;\n  b = buf[p.pos++];\n  val |= (b & 0x7f) << 14;\n  if (b < 0x80) return val;\n  b = buf[p.pos++];\n  val |= (b & 0x7f) << 21;\n  if (b < 0x80) return val;\n  b = buf[p.pos];\n  val |= (b & 0x0f) << 28;\n\n  return readVarintRemainder(val, p);\n}\n\nfunction rotate(\n  n: number,\n  x: number,\n  y: number,\n  rx: number,\n  ry: number\n): [number, number] {\n  if (ry === 0) {\n    if (rx !== 0) {\n      return [n - 1 - y, n - 1 - x];\n    }\n    return [y, x];\n  }\n  return [x, y];\n}\n\n/**\n * Convert Z,X,Y to a Hilbert TileID.\n */\nexport function zxyToTileId(z: number, x: number, y: number): number {\n  if (z > 26) {\n    throw new Error(\"Tile zoom level exceeds max safe number limit (26)\");\n  }\n  if (x >= 1 << z || y >= 1 << z) {\n    throw new Error(\"tile x/y outside zoom level bounds\");\n  }\n  let acc = ((1 << z) * (1 << z) - 1) / 3;\n  let a = z - 1;\n  let [tx, ty] = [x, y];\n  for (let s = 1 << a; s > 0; s >>= 1) {\n    const rx = tx & s;\n    const ry = ty & s;\n    acc += ((3 * rx) ^ ry) * (1 << a);\n    [tx, ty] = rotate(s, tx, ty, rx, ry);\n    a--;\n  }\n  return acc;\n}\n\nfunction tileIdToZ(i: number): number {\n  const c = 3 * i + 1;\n  if (c < 0x100000000) {\n    return 31 - Math.clz32(c);\n  }\n  return 63 - Math.clz32(c / 0x100000000);\n}\n\n/**\n * Convert a Hilbert TileID to Z,X,Y.\n */\nexport function tileIdToZxy(i: number): [number, number, number] {\n  const z = tileIdToZ(i) >> 1;\n  if (z > 26)\n    throw new Error(\"Tile zoom level exceeds max safe number limit (26)\");\n  const acc = ((1 << z) * (1 << z) - 1) / 3;\n\n  let t = i - acc;\n  let x = 0;\n  let y = 0;\n  const n = 1 << z;\n  for (let s = 1; s < n; s <<= 1) {\n    const rx = s & (t / 2);\n    const ry = s & (t ^ rx);\n    [x, y] = rotate(s, x, y, rx, ry);\n    t = t / 2;\n    x += rx;\n    y += ry;\n  }\n  return [z, x, y];\n}\n\n/**\n * PMTiles v3 directory entry.\n */\nexport interface Entry {\n  tileId: number;\n  offset: number;\n  length: number;\n  runLength: number;\n}\n\ninterface MetadataLike {\n  attribution?: string;\n  name?: string;\n  version?: string;\n  // biome-ignore lint: TileJSON spec\n  vector_layers?: string;\n  description?: string;\n}\n\n/**\n * Enum representing a compression algorithm used.\n * 0 = unknown compression, for if you must use a different or unspecified algorithm.\n * 1 = no compression.\n */\nexport enum Compression {\n  Unknown = 0,\n  None = 1,\n  Gzip = 2,\n  Brotli = 3,\n  Zstd = 4,\n}\n\n/**\n * Provide a decompression implementation that acts on `buf` and returns decompressed data.\n *\n * Should use the native DecompressionStream on browsers, zlib on node.\n * Should throw if the compression algorithm is not supported.\n */\nexport type DecompressFunc = (\n  buf: ArrayBuffer,\n  compression: Compression\n) => Promise<ArrayBuffer>;\n\nasync function defaultDecompress(\n  buf: ArrayBuffer,\n  compression: Compression\n): Promise<ArrayBuffer> {\n  if (compression === Compression.None || compression === Compression.Unknown) {\n    return buf;\n  }\n  if (compression === Compression.Gzip) {\n    // biome-ignore lint: needed to detect DecompressionStream in browser+node+cloudflare workers\n    if (typeof (globalThis as any).DecompressionStream === \"undefined\") {\n      return decompressSync(new Uint8Array(buf));\n    }\n    const stream = new Response(buf).body;\n    if (!stream) {\n      throw new Error(\"Failed to read response stream\");\n    }\n    const result: ReadableStream<Uint8Array> = stream.pipeThrough(\n      // biome-ignore lint: needed to detect DecompressionStream in browser+node+cloudflare workers\n      new (globalThis as any).DecompressionStream(\"gzip\")\n    );\n    return new Response(result).arrayBuffer();\n  }\n  throw new Error(\"Compression method not supported\");\n}\n\n/**\n * Describe the type of tiles stored in the archive.\n * 0 is unknown/other, 1 is \"MVT\" vector tiles.\n */\nexport enum TileType {\n  Unknown = 0,\n  Mvt = 1,\n  Png = 2,\n  Jpeg = 3,\n  Webp = 4,\n  Avif = 5,\n  Mlt = 6,\n}\n\nexport function tileTypeExt(t: TileType): string {\n  if (t === TileType.Mvt) return \".mvt\";\n  if (t === TileType.Png) return \".png\";\n  if (t === TileType.Jpeg) return \".jpg\";\n  if (t === TileType.Webp) return \".webp\";\n  if (t === TileType.Avif) return \".avif\";\n  if (t === TileType.Mlt) return \".mlt\";\n  return \"\";\n}\n\nconst HEADER_SIZE_BYTES = 127;\n\n/**\n * PMTiles v3 header storing basic archive-level information.\n */\nexport interface Header {\n  specVersion: number;\n  rootDirectoryOffset: number;\n  rootDirectoryLength: number;\n  jsonMetadataOffset: number;\n  jsonMetadataLength: number;\n  leafDirectoryOffset: number;\n  leafDirectoryLength?: number;\n  tileDataOffset: number;\n  tileDataLength?: number;\n  numAddressedTiles: number;\n  numTileEntries: number;\n  numTileContents: number;\n  clustered: boolean;\n  internalCompression: Compression;\n  tileCompression: Compression;\n  tileType: TileType;\n  minZoom: number;\n  maxZoom: number;\n  minLon: number;\n  minLat: number;\n  maxLon: number;\n  maxLat: number;\n  centerZoom: number;\n  centerLon: number;\n  centerLat: number;\n  etag?: string;\n}\n\n/**\n * Low-level function for looking up a TileID or leaf directory inside a directory.\n */\nexport function findTile(entries: Entry[], tileId: number): Entry | null {\n  let m = 0;\n  let n = entries.length - 1;\n  while (m <= n) {\n    const k = (n + m) >> 1;\n    const cmp = tileId - entries[k].tileId;\n    if (cmp > 0) {\n      m = k + 1;\n    } else if (cmp < 0) {\n      n = k - 1;\n    } else {\n      return entries[k];\n    }\n  }\n\n  // at this point, m > n\n  if (n >= 0) {\n    if (entries[n].runLength === 0) {\n      return entries[n];\n    }\n    if (tileId - entries[n].tileId < entries[n].runLength) {\n      return entries[n];\n    }\n  }\n  return null;\n}\n\nexport interface RangeResponse {\n  data: ArrayBuffer;\n  etag?: string;\n  expires?: string;\n  cacheControl?: string;\n}\n\n/**\n * Interface for retrieving an archive from remote or local storage.\n */\nexport interface Source {\n  getBytes: (\n    offset: number,\n    length: number,\n    signal?: AbortSignal,\n    etag?: string\n  ) => Promise<RangeResponse>;\n\n  /**\n   * Return a unique string key for the archive e.g. a URL.\n   */\n  getKey: () => string;\n}\n\n/**\n * Use the Browser's File API, which is different from the NodeJS file API.\n * see https://developer.mozilla.org/en-US/docs/Web/API/File_API\n */\nexport class FileSource implements Source {\n  file: File;\n\n  constructor(file: File) {\n    this.file = file;\n  }\n\n  getKey() {\n    return this.file.name;\n  }\n\n  async getBytes(offset: number, length: number): Promise<RangeResponse> {\n    const blob = this.file.slice(offset, offset + length);\n    const a = await blob.arrayBuffer();\n    return { data: a };\n  }\n}\n\n/**\n * Uses the browser Fetch API to make tile requests via HTTP.\n *\n * This method does not send conditional request headers If-Match because of CORS.\n * Instead, it detects ETag mismatches via the response ETag or the 416 response code.\n *\n * This also works around browser and storage-specific edge cases.\n */\nexport class FetchSource implements Source {\n  url: string;\n\n  /**\n   * A [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers) object, specfying custom [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers) set for all requests to the remote archive.\n   *\n   * This should be used instead of maplibre's [transformRequest](https://maplibre.org/maplibre-gl-js/docs/API/classes/Map/#example) for PMTiles archives.\n   */\n  customHeaders: Headers;\n  credentials: \"same-origin\" | \"include\" | undefined;\n  /** @hidden */\n  mustReload: boolean;\n  /** @hidden */\n  chromeWindowsNoCache: boolean;\n\n  constructor(\n    url: string,\n    customHeaders: Headers = new Headers(),\n    credentials: \"same-origin\" | \"include\" | undefined = undefined\n  ) {\n    this.url = url;\n    this.customHeaders = customHeaders;\n    this.credentials = credentials;\n    this.mustReload = false;\n    let userAgent = \"\";\n    if (\"navigator\" in globalThis) {\n      //biome-ignore lint: cf workers\n      userAgent = (globalThis as any).navigator?.userAgent ?? \"\";\n    }\n    const isWindows = userAgent.indexOf(\"Windows\") > -1;\n    const isChromiumBased = /Chrome|Chromium|Edg|OPR|Brave/.test(userAgent);\n    this.chromeWindowsNoCache = false;\n    if (isWindows && isChromiumBased) {\n      this.chromeWindowsNoCache = true;\n    }\n  }\n\n  getKey() {\n    return this.url;\n  }\n\n  /**\n   * Mutate the custom [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers) set for all requests to the remote archive.\n   */\n  setHeaders(customHeaders: Headers) {\n    this.customHeaders = customHeaders;\n  }\n\n  async getBytes(\n    offset: number,\n    length: number,\n    passedSignal?: AbortSignal,\n    etag?: string\n  ): Promise<RangeResponse> {\n    let controller: AbortController | undefined;\n    let signal: AbortSignal | undefined;\n    if (passedSignal) {\n      signal = passedSignal;\n    } else {\n      controller = new AbortController();\n      signal = controller.signal;\n    }\n\n    const requestHeaders = new Headers(this.customHeaders);\n    requestHeaders.set(\"range\", `bytes=${offset}-${offset + length - 1}`);\n\n    // we don't send if match because:\n    // * it disables browser caching completely (Chromium)\n    // * it requires a preflight request for every tile request\n    // * it requires CORS configuration becasue If-Match is not a CORs-safelisted header\n    // CORs configuration should expose ETag.\n    // if any etag mismatch is detected, we need to ignore the browser cache\n    let cache: \"no-store\" | \"reload\" | undefined;\n    if (this.mustReload) {\n      cache = \"reload\";\n    } else if (this.chromeWindowsNoCache) {\n      cache = \"no-store\";\n    }\n\n    let resp = await fetch(this.url, {\n      signal: signal,\n      cache: cache,\n      headers: requestHeaders,\n      credentials: this.credentials,\n    });\n\n    // handle edge case where the archive is < 16384 kb total.\n    if (offset === 0 && resp.status === 416) {\n      const contentRange = resp.headers.get(\"Content-Range\");\n      if (!contentRange || !contentRange.startsWith(\"bytes */\")) {\n        throw new Error(\"Missing content-length on 416 response\");\n      }\n      const actualLength = +contentRange.substr(8);\n      requestHeaders.set(\"range\", `bytes=0-${actualLength - 1}`);\n      resp = await fetch(this.url, {\n        signal: signal,\n        cache: \"reload\",\n        headers: requestHeaders,\n        credentials: this.credentials,\n      });\n    }\n\n    // if it's a weak etag, it's not useful for us, so ignore it.\n    let newEtag = resp.headers.get(\"Etag\");\n    if (newEtag?.startsWith(\"W/\")) {\n      newEtag = null;\n    }\n\n    // some storage systems are misbehaved (Cloudflare R2)\n    if (resp.status === 416 || (etag && newEtag && newEtag !== etag)) {\n      this.mustReload = true;\n      throw new EtagMismatch(\n        `Server returned non-matching ETag ${etag} after one retry. Check browser extensions and servers for issues that may affect correct ETag headers.`\n      );\n    }\n\n    if (resp.status >= 300) {\n      throw new Error(`Bad response code: ${resp.status}`);\n    }\n\n    // some well-behaved backends, e.g. DigitalOcean CDN, respond with 200 instead of 206\n    // but we also need to detect no support for Byte Serving which is returning the whole file\n    const contentLength = resp.headers.get(\"Content-Length\");\n    if (resp.status === 200 && (!contentLength || +contentLength > length)) {\n      if (controller) controller.abort();\n      throw new Error(\n        \"Server returned no content-length header or content-length exceeding request. Check that your storage backend supports HTTP Byte Serving.\"\n      );\n    }\n\n    const a = await resp.arrayBuffer();\n    return {\n      data: a,\n      etag: newEtag || undefined,\n      cacheControl: resp.headers.get(\"Cache-Control\") || undefined,\n      expires: resp.headers.get(\"Expires\") || undefined,\n    };\n  }\n}\n\n/** @hidden */\nexport function getUint64(v: DataView, offset: number): number {\n  const wh = v.getUint32(offset + 4, true);\n  const wl = v.getUint32(offset + 0, true);\n  return wh * 2 ** 32 + wl;\n}\n\n/**\n * Parse raw header bytes into a Header object.\n */\nexport function bytesToHeader(bytes: ArrayBuffer, etag?: string): Header {\n  const v = new DataView(bytes);\n  const specVersion = v.getUint8(7);\n  if (specVersion > 3) {\n    throw new Error(\n      `Archive is spec version ${specVersion} but this library supports up to spec version 3`\n    );\n  }\n\n  return {\n    specVersion: specVersion,\n    rootDirectoryOffset: getUint64(v, 8),\n    rootDirectoryLength: getUint64(v, 16),\n    jsonMetadataOffset: getUint64(v, 24),\n    jsonMetadataLength: getUint64(v, 32),\n    leafDirectoryOffset: getUint64(v, 40),\n    leafDirectoryLength: getUint64(v, 48),\n    tileDataOffset: getUint64(v, 56),\n    tileDataLength: getUint64(v, 64),\n    numAddressedTiles: getUint64(v, 72),\n    numTileEntries: getUint64(v, 80),\n    numTileContents: getUint64(v, 88),\n    clustered: v.getUint8(96) === 1,\n    internalCompression: v.getUint8(97),\n    tileCompression: v.getUint8(98),\n    tileType: v.getUint8(99),\n    minZoom: v.getUint8(100),\n    maxZoom: v.getUint8(101),\n    minLon: v.getInt32(102, true) / 10000000,\n    minLat: v.getInt32(106, true) / 10000000,\n    maxLon: v.getInt32(110, true) / 10000000,\n    maxLat: v.getInt32(114, true) / 10000000,\n    centerZoom: v.getUint8(118),\n    centerLon: v.getInt32(119, true) / 10000000,\n    centerLat: v.getInt32(123, true) / 10000000,\n    etag: etag,\n  };\n}\n\nfunction deserializeIndex(buffer: ArrayBuffer): Entry[] {\n  const p = { buf: new Uint8Array(buffer), pos: 0 };\n  const numEntries = readVarint(p);\n\n  const entries: Entry[] = [];\n\n  let lastId = 0;\n  for (let i = 0; i < numEntries; i++) {\n    const v = readVarint(p);\n    entries.push({ tileId: lastId + v, offset: 0, length: 0, runLength: 1 });\n    lastId += v;\n  }\n\n  for (let i = 0; i < numEntries; i++) {\n    entries[i].runLength = readVarint(p);\n  }\n\n  for (let i = 0; i < numEntries; i++) {\n    entries[i].length = readVarint(p);\n  }\n\n  for (let i = 0; i < numEntries; i++) {\n    const v = readVarint(p);\n    if (v === 0 && i > 0) {\n      entries[i].offset = entries[i - 1].offset + entries[i - 1].length;\n    } else {\n      entries[i].offset = v - 1;\n    }\n  }\n\n  return entries;\n}\n\n/**\n * Error thrown when a response for PMTiles over HTTP does not match previous, cached parts of the archive.\n * The default PMTiles implementation will catch this error once internally and retry a request.\n */\nexport class EtagMismatch extends Error {}\n\n/**\n * Interface for caches of parts (headers, directories) of a PMTiles archive.\n */\nexport interface Cache {\n  getHeader: (source: Source) => Promise<Header>;\n  getDirectory: (\n    source: Source,\n    offset: number,\n    length: number,\n    header: Header\n  ) => Promise<Entry[]>;\n  invalidate: (source: Source) => Promise<void>;\n}\n\nasync function getHeaderAndRoot(\n  source: Source,\n  decompress: DecompressFunc\n): Promise<[Header, [string, number, Entry[] | ArrayBuffer]?]> {\n  const resp = await source.getBytes(0, 16384);\n\n  const v = new DataView(resp.data);\n  if (v.getUint16(0, true) !== 0x4d50) {\n    throw new Error(\"Wrong magic number for PMTiles archive\");\n  }\n\n  const headerData = resp.data.slice(0, HEADER_SIZE_BYTES);\n\n  const header = bytesToHeader(headerData, resp.etag);\n\n  // optimistically set the root directory\n  // TODO check root bounds\n  const rootDirData = resp.data.slice(\n    header.rootDirectoryOffset,\n    header.rootDirectoryOffset + header.rootDirectoryLength\n  );\n  const dirKey = `${source.getKey()}|${header.etag || \"\"}|${\n    header.rootDirectoryOffset\n  }|${header.rootDirectoryLength}`;\n\n  const rootDir = deserializeIndex(\n    await decompress(rootDirData, header.internalCompression)\n  );\n  return [header, [dirKey, rootDir.length, rootDir]];\n}\n\nasync function getDirectory(\n  source: Source,\n  decompress: DecompressFunc,\n  offset: number,\n  length: number,\n  header: Header\n): Promise<Entry[]> {\n  const resp = await source.getBytes(offset, length, undefined, header.etag);\n  const data = await decompress(resp.data, header.internalCompression);\n  const directory = deserializeIndex(data);\n  if (directory.length === 0) {\n    throw new Error(\"Empty directory is invalid\");\n  }\n\n  return directory;\n}\n\ninterface ResolvedValue {\n  lastUsed: number;\n  data: Header | Entry[] | ArrayBuffer;\n}\n\n/**\n * A cache for parts of a PMTiles archive where promises are never shared between requests.\n *\n * Runtimes such as Cloudflare Workers cannot share promises between different requests.\n *\n * Only caches headers and directories, not individual tile contents.\n */\nexport class ResolvedValueCache {\n  cache: Map<string, ResolvedValue>;\n  maxCacheEntries: number;\n  counter: number;\n  decompress: DecompressFunc;\n\n  constructor(\n    maxCacheEntries = 100,\n    prefetch = true, // deprecated\n    decompress: DecompressFunc = defaultDecompress\n  ) {\n    this.cache = new Map<string, ResolvedValue>();\n    this.maxCacheEntries = maxCacheEntries;\n    this.counter = 1;\n    this.decompress = decompress;\n  }\n\n  async getHeader(source: Source): Promise<Header> {\n    const cacheKey = source.getKey();\n    const cacheValue = this.cache.get(cacheKey);\n    if (cacheValue) {\n      cacheValue.lastUsed = this.counter++;\n      const data = cacheValue.data;\n      return data as Header;\n    }\n\n    const res = await getHeaderAndRoot(source, this.decompress);\n    if (res[1]) {\n      this.cache.set(res[1][0], {\n        lastUsed: this.counter++,\n        data: res[1][2],\n      });\n    }\n\n    this.cache.set(cacheKey, {\n      lastUsed: this.counter++,\n      data: res[0],\n    });\n    this.prune();\n    return res[0];\n  }\n\n  async getDirectory(\n    source: Source,\n    offset: number,\n    length: number,\n    header: Header\n  ): Promise<Entry[]> {\n    const cacheKey = `${source.getKey()}|${\n      header.etag || \"\"\n    }|${offset}|${length}`;\n    const cacheValue = this.cache.get(cacheKey);\n    if (cacheValue) {\n      cacheValue.lastUsed = this.counter++;\n      const data = cacheValue.data;\n      return data as Entry[];\n    }\n\n    const directory = await getDirectory(\n      source,\n      this.decompress,\n      offset,\n      length,\n      header\n    );\n    this.cache.set(cacheKey, {\n      lastUsed: this.counter++,\n      data: directory,\n    });\n    this.prune();\n    return directory;\n  }\n\n  prune() {\n    if (this.cache.size > this.maxCacheEntries) {\n      let minUsed = Infinity;\n      let minKey = undefined;\n      this.cache.forEach((cacheValue: ResolvedValue, key: string) => {\n        if (cacheValue.lastUsed < minUsed) {\n          minUsed = cacheValue.lastUsed;\n          minKey = key;\n        }\n      });\n      if (minKey) {\n        this.cache.delete(minKey);\n      }\n    }\n  }\n\n  async invalidate(source: Source) {\n    this.cache.delete(source.getKey());\n  }\n}\n\ninterface SharedPromiseCacheValue {\n  lastUsed: number;\n  data: Promise<Header | Entry[] | ArrayBuffer>;\n}\n\n/**\n * A cache for parts of a PMTiles archive where promises can be shared between requests.\n *\n * Only caches headers and directories, not individual tile contents.\n */\nexport class SharedPromiseCache {\n  cache: Map<string, SharedPromiseCacheValue>;\n  invalidations: Map<string, Promise<void>>;\n  maxCacheEntries: number;\n  counter: number;\n  decompress: DecompressFunc;\n\n  constructor(\n    maxCacheEntries = 100,\n    prefetch = true, // deprecated\n    decompress: DecompressFunc = defaultDecompress\n  ) {\n    this.cache = new Map<string, SharedPromiseCacheValue>();\n    this.invalidations = new Map<string, Promise<void>>();\n    this.maxCacheEntries = maxCacheEntries;\n    this.counter = 1;\n    this.decompress = decompress;\n  }\n\n  async getHeader(source: Source): Promise<Header> {\n    const cacheKey = source.getKey();\n    const cacheValue = this.cache.get(cacheKey);\n    if (cacheValue) {\n      cacheValue.lastUsed = this.counter++;\n      const data = await cacheValue.data;\n      return data as Header;\n    }\n\n    const p = new Promise<Header>((resolve, reject) => {\n      getHeaderAndRoot(source, this.decompress)\n        .then((res) => {\n          if (res[1]) {\n            this.cache.set(res[1][0], {\n              lastUsed: this.counter++,\n              data: Promise.resolve(res[1][2]),\n            });\n          }\n          resolve(res[0]);\n          this.prune();\n        })\n        .catch((e) => {\n          reject(e);\n        });\n    });\n    this.cache.set(cacheKey, { lastUsed: this.counter++, data: p });\n    return p;\n  }\n\n  async getDirectory(\n    source: Source,\n    offset: number,\n    length: number,\n    header: Header\n  ): Promise<Entry[]> {\n    const cacheKey = `${source.getKey()}|${\n      header.etag || \"\"\n    }|${offset}|${length}`;\n    const cacheValue = this.cache.get(cacheKey);\n    if (cacheValue) {\n      cacheValue.lastUsed = this.counter++;\n      const data = await cacheValue.data;\n      return data as Entry[];\n    }\n\n    const p = new Promise<Entry[]>((resolve, reject) => {\n      getDirectory(source, this.decompress, offset, length, header)\n        .then((directory) => {\n          resolve(directory);\n          this.prune();\n        })\n        .catch((e) => {\n          reject(e);\n        });\n    });\n    this.cache.set(cacheKey, { lastUsed: this.counter++, data: p });\n    return p;\n  }\n\n  prune() {\n    if (this.cache.size >= this.maxCacheEntries) {\n      let minUsed = Infinity;\n      let minKey = undefined;\n      this.cache.forEach((cacheValue: SharedPromiseCacheValue, key: string) => {\n        if (cacheValue.lastUsed < minUsed) {\n          minUsed = cacheValue.lastUsed;\n          minKey = key;\n        }\n      });\n      if (minKey) {\n        this.cache.delete(minKey);\n      }\n    }\n  }\n\n  async invalidate(source: Source) {\n    const key = source.getKey();\n    if (this.invalidations.get(key)) {\n      return await this.invalidations.get(key);\n    }\n    this.cache.delete(source.getKey());\n    const p = new Promise<void>((resolve, reject) => {\n      this.getHeader(source)\n        .then((h) => {\n          resolve();\n          this.invalidations.delete(key);\n        })\n        .catch((e) => {\n          reject(e);\n        });\n    });\n    this.invalidations.set(key, p);\n  }\n}\n\n/**\n * Main class encapsulating PMTiles decoding logic.\n *\n * if `source` is a string, creates a FetchSource using that string as the URL to a remote PMTiles.\n * if no `cache` is passed, use a SharedPromiseCache.\n * if no `decompress` is passed, default to the browser DecompressionStream API with a fallback to `fflate`.\n */\n// biome-ignore lint: that's just how its capitalized\nexport class PMTiles {\n  source: Source;\n  cache: Cache;\n  decompress: DecompressFunc;\n\n  constructor(\n    source: Source | string,\n    cache?: Cache,\n    decompress?: DecompressFunc\n  ) {\n    if (typeof source === \"string\") {\n      this.source = new FetchSource(source);\n    } else {\n      this.source = source;\n    }\n    if (decompress) {\n      this.decompress = decompress;\n    } else {\n      this.decompress = defaultDecompress;\n    }\n    if (cache) {\n      this.cache = cache;\n    } else {\n      this.cache = new SharedPromiseCache();\n    }\n  }\n\n  /**\n   * Return the header of the archive,\n   * including information such as tile type, min/max zoom, bounds, and summary statistics.\n   */\n  async getHeader() {\n    return await this.cache.getHeader(this.source);\n  }\n\n  /** @hidden */\n  async getZxyAttempt(\n    z: number,\n    x: number,\n    y: number,\n    signal?: AbortSignal\n  ): Promise<RangeResponse | undefined> {\n    const tileId = zxyToTileId(z, x, y);\n    const header = await this.cache.getHeader(this.source);\n\n    if (z < header.minZoom || z > header.maxZoom) {\n      return undefined;\n    }\n\n    let dO = header.rootDirectoryOffset;\n    let dL = header.rootDirectoryLength;\n    for (let depth = 0; depth <= 3; depth++) {\n      const directory = await this.cache.getDirectory(\n        this.source,\n        dO,\n        dL,\n        header\n      );\n      const entry = findTile(directory, tileId);\n      if (entry) {\n        if (entry.runLength > 0) {\n          const resp = await this.source.getBytes(\n            header.tileDataOffset + entry.offset,\n            entry.length,\n            signal,\n            header.etag\n          );\n          return {\n            data: await this.decompress(resp.data, header.tileCompression),\n            cacheControl: resp.cacheControl,\n            expires: resp.expires,\n          };\n        }\n        dO = header.leafDirectoryOffset + entry.offset;\n        dL = entry.length;\n      } else {\n        // TODO: We should in fact return a valid RangeResponse\n        // with empty data, but filled in cache control / expires headers\n        return undefined;\n      }\n    }\n    throw new Error(\"Maximum directory depth exceeded\");\n  }\n\n  /**\n   * Primary method to get a single tile's bytes from an archive.\n   *\n   * Returns undefined if the tile does not exist in the archive.\n   */\n  async getZxy(\n    z: number,\n    x: number,\n    y: number,\n    signal?: AbortSignal\n  ): Promise<RangeResponse | undefined> {\n    try {\n      return await this.getZxyAttempt(z, x, y, signal);\n    } catch (e) {\n      if (e instanceof EtagMismatch) {\n        this.cache.invalidate(this.source);\n        return await this.getZxyAttempt(z, x, y, signal);\n      }\n      throw e;\n    }\n  }\n\n  /** @hidden */\n  async getMetadataAttempt(): Promise<unknown> {\n    const header = await this.cache.getHeader(this.source);\n\n    const resp = await this.source.getBytes(\n      header.jsonMetadataOffset,\n      header.jsonMetadataLength,\n      undefined,\n      header.etag\n    );\n    const decompressed = await this.decompress(\n      resp.data,\n      header.internalCompression\n    );\n    const dec = new TextDecoder(\"utf-8\");\n    return JSON.parse(dec.decode(decompressed));\n  }\n\n  /**\n   * Return the arbitrary JSON metadata of the archive.\n   */\n  async getMetadata(): Promise<unknown> {\n    try {\n      return await this.getMetadataAttempt();\n    } catch (e) {\n      if (e instanceof EtagMismatch) {\n        this.cache.invalidate(this.source);\n        return await this.getMetadataAttempt();\n      }\n      throw e;\n    }\n  }\n\n  /**\n   * Construct a [TileJSON](https://github.com/mapbox/tilejson-spec) object.\n   *\n   * baseTilesUrl is the desired tiles URL, excluding the suffix `/{z}/{x}/{y}.{ext}`.\n   * For example, if the desired URL is `http://example.com/tileset/{z}/{x}/{y}.mvt`,\n   * the baseTilesUrl should be `https://example.com/tileset`.\n   */\n  async getTileJson(baseTilesUrl: string): Promise<unknown> {\n    const header = await this.getHeader();\n    const metadata = (await this.getMetadata()) as MetadataLike;\n    const ext = tileTypeExt(header.tileType);\n\n    return {\n      tilejson: \"3.0.0\",\n      scheme: \"xyz\",\n      tiles: [`${baseTilesUrl}/{z}/{x}/{y}${ext}`],\n      // biome-ignore lint: TileJSON spec\n      vector_layers: metadata.vector_layers,\n      attribution: metadata.attribution,\n      description: metadata.description,\n      name: metadata.name,\n      version: metadata.version,\n      bounds: [header.minLon, header.minLat, header.maxLon, header.maxLat],\n      center: [header.centerLon, header.centerLat, header.centerZoom],\n      minzoom: header.minZoom,\n      maxzoom: header.maxZoom,\n    };\n  }\n}\n","// biome-ignore lint: needed for Leaflet + IIFE to work\ndeclare const L: any;\n// biome-ignore lint: needed for window.URL to disambiguate from cloudflare workers\ndeclare const window: any;\ndeclare const document: DocumentLike;\n\nimport type { Coords } from \"leaflet\";\nimport { PMTiles, TileType } from \"./index\";\n\ninterface DocumentLike {\n  // biome-ignore lint: we don't want to bring in the entire document type\n  createElement: (s: string) => any;\n}\n\n// biome-ignore lint: we don't want to bring in the entire document type\ntype DoneCallback = (error?: Error, tile?: any) => void;\n\n/**\n * Add a raster PMTiles as a layer to a Leaflet map.\n *\n * For vector tiles see https://github.com/protomaps/protomaps-leaflet\n */\nexport const leafletRasterLayer = (source: PMTiles, options: unknown) => {\n  let loaded = false;\n  let mimeType = \"\";\n  const cls = L.GridLayer.extend({\n    createTile: (coord: Coords, done: DoneCallback) => {\n      const el = document.createElement(\"img\");\n      const controller = new AbortController();\n      const signal = controller.signal;\n      el.cancel = () => {\n        controller.abort();\n      };\n      if (!loaded) {\n        source.getHeader().then((header) => {\n          if (\n            header.tileType === TileType.Mvt ||\n            header.tileType === TileType.Mlt\n          ) {\n            console.error(\n              \"Error: archive contains vector tiles, but leafletRasterLayer is for displaying raster tiles. See https://github.com/protomaps/PMTiles/tree/main/js for details.\"\n            );\n          } else if (header.tileType === 2) {\n            mimeType = \"image/png\";\n          } else if (header.tileType === 3) {\n            mimeType = \"image/jpeg\";\n          } else if (header.tileType === 4) {\n            mimeType = \"image/webp\";\n          } else if (header.tileType === 5) {\n            mimeType = \"image/avif\";\n          }\n        });\n        loaded = true;\n      }\n      source\n        .getZxy(coord.z, coord.x, coord.y, signal)\n        .then((arr) => {\n          if (arr) {\n            const blob = new Blob([arr.data], { type: mimeType });\n            const imageUrl = window.URL.createObjectURL(blob);\n            el.src = imageUrl;\n          } else {\n            el.style.display = \"none\";\n          }\n          el.cancel = undefined;\n          done(undefined, el);\n        })\n        .catch((e) => {\n          if (e.name !== \"AbortError\") {\n            throw e;\n          }\n        });\n      return el;\n    },\n\n    _removeTile: function (key: string) {\n      const tile = this._tiles[key];\n      if (!tile) {\n        return;\n      }\n\n      if (tile.el.cancel) tile.el.cancel();\n      if (tile.el.src) window.URL.revokeObjectURL(tile.el.src);\n\n      tile.el.width = 0;\n      tile.el.height = 0;\n      tile.el.deleted = true;\n      L.DomUtil.remove(tile.el);\n      delete this._tiles[key];\n      this.fire(\"tileunload\", {\n        tile: tile.el,\n        coords: this._keyToTileCoords(key),\n      });\n    },\n  });\n  return new cls(options);\n};\n\ntype GetResourceResponse<T> = ExpiryData & {\n  data: T;\n};\ntype AddProtocolAction = (\n  requestParameters: RequestParameters,\n  abortController: AbortController\n) => Promise<GetResourceResponse<unknown>>;\n\ntype ExpiryData = {\n  cacheControl?: string | null;\n  expires?: string | null; // MapLibre can be a Date object\n};\n\n// copied from MapLibre /util/ajax.ts\ntype RequestParameters = {\n  url: string;\n  headers?: unknown;\n  method?: \"GET\" | \"POST\" | \"PUT\";\n  body?: string;\n  type?: \"string\" | \"json\" | \"arrayBuffer\" | \"image\";\n  credentials?: \"same-origin\" | \"include\";\n  collectResourceTiming?: boolean;\n};\n\n// for legacy maplibre-3 interop\ntype ResponseCallbackV3 = (\n  error?: Error | undefined,\n  data?: unknown | undefined,\n  cacheControl?: string | undefined,\n  expires?: string | undefined\n) => void;\n\ntype V3OrV4Protocol = <\n  T extends AbortController | ResponseCallbackV3,\n  R = T extends AbortController\n    ? Promise<GetResourceResponse<unknown>>\n    : { cancel: () => void },\n>(\n  requestParameters: RequestParameters,\n  arg2: T\n) => R;\n\nconst v3compat =\n  (v4: AddProtocolAction): V3OrV4Protocol =>\n  (requestParameters, arg2) => {\n    if (arg2 instanceof AbortController) {\n      // biome-ignore lint: overloading return type not handled by compiler\n      return v4(requestParameters, arg2) as any;\n    }\n    const abortController = new AbortController();\n    v4(requestParameters, abortController)\n      .then(\n        (result) => {\n          return arg2(\n            undefined,\n            result.data,\n            result.cacheControl || \"\",\n            result.expires || \"\"\n          );\n        },\n        (err) => {\n          return arg2(err);\n        }\n      )\n      .catch((e) => {\n        return arg2(e);\n      });\n    return { cancel: () => abortController.abort() };\n  };\n\n/**\n * MapLibre GL JS protocol. Must be added once globally.\n */\nexport class Protocol {\n  /** @hidden */\n  tiles: Map<string, PMTiles>;\n  metadata: boolean;\n  errorOnMissingTile: boolean;\n\n  /**\n   * Initialize the MapLibre PMTiles protocol.\n   *\n   * * metadata: also load the metadata section of the PMTiles. required for some \"inspect\" functionality\n   * and to automatically populate the map attribution. Requires an extra HTTP request.\n   * * errorOnMissingTile: When a vector MVT tile is missing from the archive, raise an error instead of\n   * returning the empty array. Not recommended. This is only to reproduce the behavior of ZXY tile APIs\n   * which some applications depend on when overzooming.\n   */\n  constructor(options?: { metadata?: boolean; errorOnMissingTile?: boolean }) {\n    this.tiles = new Map<string, PMTiles>();\n    this.metadata = options?.metadata || false;\n    this.errorOnMissingTile = options?.errorOnMissingTile || false;\n  }\n\n  /**\n   * Add a {@link PMTiles} instance to the global protocol instance.\n   *\n   * For remote fetch sources, references in MapLibre styles like pmtiles://http://...\n   * will resolve to the same instance if the URLs match.\n   */\n  add(p: PMTiles) {\n    this.tiles.set(p.source.getKey(), p);\n  }\n\n  /**\n   * Fetch a {@link PMTiles} instance by URL, for remote PMTiles instances.\n   */\n  get(url: string) {\n    return this.tiles.get(url);\n  }\n\n  /** @hidden */\n  tilev4 = async (\n    params: RequestParameters,\n    abortController: AbortController\n  ) => {\n    if (params.type === \"json\") {\n      const pmtilesUrl = params.url.substr(10);\n      let instance = this.tiles.get(pmtilesUrl);\n      if (!instance) {\n        instance = new PMTiles(pmtilesUrl);\n        this.tiles.set(pmtilesUrl, instance);\n      }\n\n      if (this.metadata) {\n        const data = await instance.getTileJson(params.url);\n        abortController.signal.throwIfAborted();\n        return { data };\n      }\n\n      const h = await instance.getHeader();\n      abortController.signal.throwIfAborted();\n\n      if (h.minLon >= h.maxLon || h.minLat >= h.maxLat) {\n        console.error(\n          `Bounds of PMTiles archive ${h.minLon},${h.minLat},${h.maxLon},${h.maxLat} are not valid.`\n        );\n      }\n\n      return {\n        data: {\n          tiles: [`${params.url}/{z}/{x}/{y}`],\n          minzoom: h.minZoom,\n          maxzoom: h.maxZoom,\n          bounds: [h.minLon, h.minLat, h.maxLon, h.maxLat],\n        },\n      };\n    }\n    const re = new RegExp(/pmtiles:\\/\\/(.+)\\/(\\d+)\\/(\\d+)\\/(\\d+)/);\n    const result = params.url.match(re);\n    if (!result) {\n      throw new Error(\"Invalid PMTiles protocol URL\");\n    }\n    const pmtilesUrl = result[1];\n\n    let instance = this.tiles.get(pmtilesUrl);\n    if (!instance) {\n      instance = new PMTiles(pmtilesUrl);\n      this.tiles.set(pmtilesUrl, instance);\n    }\n    const z = result[2];\n    const x = result[3];\n    const y = result[4];\n\n    const resp = await instance?.getZxy(+z, +x, +y, abortController.signal);\n    abortController.signal.throwIfAborted();\n    if (resp) {\n      return {\n        data: new Uint8Array(resp.data),\n        cacheControl: resp.cacheControl,\n        expires: resp.expires,\n      };\n    }\n    const header = await instance.getHeader();\n    if (header.tileType === TileType.Mvt || header.tileType === TileType.Mlt) {\n      if (this.errorOnMissingTile) {\n        throw new Error(\"Tile not found.\");\n      }\n      return { data: new Uint8Array() };\n    }\n    return { data: null };\n  };\n\n  tile = v3compat(this.tilev4);\n}\n"]}