From f8b1806065d6786fca76aa5fb8f33e4001820be2 Mon Sep 17 00:00:00 2001 From: Harald Fernengel Date: Wed, 16 Sep 2026 18:36:25 +0200 Subject: [PATCH 01/14] Return parent tile key for aggregatedData request For some maps, it's essential to know the actual tile key of the aggregated data as that's unfortunately not part of the payload. Also refactor the logic for aggregated data - it was mostly wrong. Start iterating the subQuadKeys until we have a hit, otherwise take the closest parent. That should guarantee the closest ancestor even if it's more than 4 levels up the tree. Relates-To: DATASDK-108 Signed-off-by: Oleksii Zubko --- .../lib/client/VersionedLayerClient.ts | 36 ++++- .../lib/client/VolatileLayerClient.ts | 3 +- .../lib/utils/getTile.ts | 137 +++++++++++------- .../test/unit/getTile.test.ts | 117 +++++++++++++-- 4 files changed, 225 insertions(+), 68 deletions(-) diff --git a/@here/olp-sdk-dataservice-read/lib/client/VersionedLayerClient.ts b/@here/olp-sdk-dataservice-read/lib/client/VersionedLayerClient.ts index 433973ba..e1f127c0 100644 --- a/@here/olp-sdk-dataservice-read/lib/client/VersionedLayerClient.ts +++ b/@here/olp-sdk-dataservice-read/lib/client/VersionedLayerClient.ts @@ -29,7 +29,7 @@ import { } from "@here/olp-sdk-core"; import { BlobApi, MetadataApi, QueryApi } from "@here/olp-sdk-dataservice-api"; import { MetadataCacheRepository } from "../cache/MetadataCacheRepository"; -import { getTile } from "../utils/getTile"; +import { getTile, TileResponse } from "../utils/getTile"; import { DataRequest } from "./DataRequest"; import { PartitionsRequest } from "./PartitionsRequest"; import { QuadKeyPartitionsRequest } from "./QuadKeyPartitionsRequest"; @@ -102,7 +102,36 @@ export class VersionedLayerClient { * * @return Tile data (if it exists) or the nearest parent tile data */ - async getAggregatedData(request: TileRequest, abortSignal?: AbortSignal) { + getAggregatedData( + request: TileRequest, + abortSignal?: AbortSignal + ): Promise; + + /** + * @brief Fetches data of a tile or its closest ancestor. + * Use this API for tile-tree structures where children tile data is aggregated and stored in parent tiles. + * + * @param request The `TileRequest` instance that contains a complete set + * of request parameters. + * @param abortSignal A signal object that allows you to communicate with a request (such as the `fetch` request) + * and, if required, abort it using the `AbortController` object. + * @param options An optional object that can include the `includeTileKey` flag to indicate whether the parent tile key should be included in the response. + * + * For more information, see the [`AbortController` documentation](https://developer.mozilla.org/en-US/docs/Web/API/AbortController). + * + * @return Tile data (if it exists) or the nearest parent tile data including the parent tile key + */ + getAggregatedData( + request: TileRequest, + abortSignal: AbortSignal | undefined, + options: { includeTileKey: true } + ): Promise; + + async getAggregatedData( + request: TileRequest, + abortSignal?: AbortSignal, + options?: { includeTileKey?: boolean } + ): Promise { let catalogVersion = this.version; if (catalogVersion === undefined) { @@ -119,7 +148,8 @@ export class VersionedLayerClient { catalogVersion }; - return getTile(request, params, abortSignal); + const tileResponse = await getTile(request, params, abortSignal); + return options?.includeTileKey ? tileResponse : tileResponse.response; } /** diff --git a/@here/olp-sdk-dataservice-read/lib/client/VolatileLayerClient.ts b/@here/olp-sdk-dataservice-read/lib/client/VolatileLayerClient.ts index 79bbad2e..d8021cce 100644 --- a/@here/olp-sdk-dataservice-read/lib/client/VolatileLayerClient.ts +++ b/@here/olp-sdk-dataservice-read/lib/client/VolatileLayerClient.ts @@ -106,7 +106,8 @@ export class VolatileLayerClient { settings: this.settings }; - return getTile(request, params, abortSignal); + const tileResponse = await getTile(request, params, abortSignal); + return tileResponse.response; } /** diff --git a/@here/olp-sdk-dataservice-read/lib/utils/getTile.ts b/@here/olp-sdk-dataservice-read/lib/utils/getTile.ts index 4a2ec48c..d61b3d17 100644 --- a/@here/olp-sdk-dataservice-read/lib/utils/getTile.ts +++ b/@here/olp-sdk-dataservice-read/lib/utils/getTile.ts @@ -26,7 +26,10 @@ import { ApiName } from "@here/olp-sdk-core"; import { BlobApi } from "@here/olp-sdk-dataservice-api"; -import { Index as QuadTreeIndex } from "@here/olp-sdk-dataservice-api/lib/query-api"; +import { + ParentQuad, + Index as QuadTreeIndex +} from "@here/olp-sdk-dataservice-api/lib/query-api"; import { QuadTreeIndexCacheRepository } from "../cache/QuadTreeIndexCacheRepository"; import { QuadTreeIndexDepth, @@ -44,6 +47,11 @@ export interface GetTileParams { layerType: "versioned" | "volatile"; } +export interface TileResponse { + response: Response; + parentTileKey: TileKey | undefined; +} + /** * Fetches asynchronously data from a tile or from its nearest ancestor if not found. * @@ -82,7 +90,7 @@ export async function getTile( request: TileRequest, params: TileRequestParams, abortSignal?: AbortSignal -): Promise { +): Promise { let catalogVersion: number | undefined; const quadKey = request.getTileKey(); @@ -105,91 +113,112 @@ export async function getTile( abortSignal ); - const delta = 4; + const depth = 4; const requestedTileKey = TileKey.fromRowColumnLevel( quadKey.row, quadKey.column, quadKey.level ); + const quadTreeIndexRoot = requestedTileKey.changedLevelBy(-depth); - let quadTreeIndex = null; + let quadTreeIndex: QuadTreeIndex | undefined; if (request.getFetchOption() !== FetchOptions.OnlineOnly) { const cache = new QuadTreeIndexCacheRepository(params.settings.cache); - - for (let i = 1; i <= delta; i++) { - const parentInCache = requestedTileKey.changedLevelBy(-i); - const cachedTree = cache.get({ - hrn: params.catalogHrn.toString(), - layerId: params.layerId, - depth: delta, - root: parentInCache, - version: catalogVersion - }); - - if (cachedTree) { - quadTreeIndex = cachedTree; - } - } + quadTreeIndex = cache.get({ + hrn: params.catalogHrn.toString(), + layerId: params.layerId, + depth, + root: quadTreeIndexRoot, + version: catalogVersion + }); } - const parentTileKey = requestedTileKey.changedLevelBy(-delta); - if (!quadTreeIndex) { quadTreeIndex = await fetchQuadTreeIndex({ ...params, catalogVersion, - depth: delta, + depth, fetchOptions: request.getFetchOption(), - tileKey: parentTileKey, + tileKey: quadTreeIndexRoot, abortSignal, billingTag: request.getBillingTag() }); } - if (!quadTreeIndex.subQuads || !quadTreeIndex.subQuads.length) { - return Promise.resolve( - new Response(null, { + const subQuads = quadTreeIndex.subQuads ?? []; + const parentQuads = quadTreeIndex.parentQuads ?? []; + + if (!subQuads.length && !parentQuads.length) { + return Promise.resolve({ + response: new Response(null, { status: 204, statusText: "No Content" - }) + }), + parentTileKey: undefined + }); + } + + const closestParent = findClosestParent( + requestedTileKey, + quadTreeIndex, + quadTreeIndexRoot + ); + if (!closestParent) { + return Promise.reject( + new Error( + `Error getting blob for Tile: ${requestedTileKey.toHereTile()}` + ) ); } - // Return the data for the requested QuadKey or for the closest parent - const subQuads = quadTreeIndex.subQuads; + return { + response: await BlobApi.getBlob(blobRequestBuilder, { + dataHandle: closestParent.parentQuad.dataHandle, + layerId: params.layerId, + billingTag: request.getBillingTag() + }), + parentTileKey: closestParent.tileKey + }; +} - let currentTileKey = requestedTileKey; - let currentDelta = delta; - for ( - let level = currentTileKey.level; - level >= parentTileKey.level; - --level - ) { - const metadata = subQuads.find( - (item) => - item.subQuadKey === currentTileKey.getSubHereTile(currentDelta) +/** + * Helper function to get the closest parent quad for a given tile key. + * @hidden + */ +function findClosestParent( + tileKey: TileKey, + quadTreeIndex: QuadTreeIndex, + quadTreeIndexRoot: TileKey +) { + const subQuads = quadTreeIndex.subQuads ?? []; + const parentQuads = quadTreeIndex.parentQuads ?? []; + + // First, iterate through the sub quad trees, starting from lowest level up to the quad tree index root + let currentTileKey = tileKey; + while (currentTileKey.level >= quadTreeIndexRoot.level) { + const currentDelta = currentTileKey.level - quadTreeIndexRoot.level; + const subHereTile = currentTileKey.getSubHereTile(currentDelta); + const subQuad = subQuads.find( + (item) => item.subQuadKey === subHereTile ); - if (metadata) { - return BlobApi.getBlob(blobRequestBuilder, { - dataHandle: metadata.dataHandle, - layerId: params.layerId, - billingTag: request.getBillingTag() - }); + if (subQuad) { + return { parentQuad: subQuad, tileKey: currentTileKey }; } - try { - currentTileKey = currentTileKey.parent(); - currentDelta = parentTileKey.level - currentTileKey.level; - } catch (error) { - continue; - } + currentTileKey = currentTileKey.parent(); } - return Promise.reject( - new Error(`Error getting blob for Tile: ${JSON.stringify(quadKey)}`) - ); + // not found? Find the closest parent quad + return parentQuads.reduce< + { parentQuad: ParentQuad; tileKey: TileKey } | undefined + >((closest, parentQuad) => { + const tileKey = TileKey.fromHereTile(parentQuad.partition); + return !closest || tileKey.level > closest.tileKey.level + ? { parentQuad, tileKey } + : closest; + }, undefined); } /** diff --git a/@here/olp-sdk-dataservice-read/test/unit/getTile.test.ts b/@here/olp-sdk-dataservice-read/test/unit/getTile.test.ts index c1582c24..ad40f99d 100644 --- a/@here/olp-sdk-dataservice-read/test/unit/getTile.test.ts +++ b/@here/olp-sdk-dataservice-read/test/unit/getTile.test.ts @@ -89,8 +89,8 @@ describe("getTile", function () { } ); - expect(response.status).eqls(204); - expect(response.statusText).eqls("No Content"); + expect(response.response.status).eqls(204); + expect(response.response.statusText).eqls("No Content"); }); it("Should return the blob of the requested tile", async function () { @@ -129,12 +129,115 @@ describe("getTile", function () { } ); - expect(response).eqls(mockedBlob); + expect(response.response).eqls(mockedBlob); + expect( + response.parentTileKey?.equals( + core.TileKey.fromRowColumnLevel(818, 2021, 11) + ) + ).toBe(true); expect(getBlobStub.mock.calls[0][1].dataHandle).eqls( "mocked-data-handle" ); }); + it("Should return the blob of the closest parent tile from sub quads", async function () { + const mockedQuadKeyTreeData = { + subQuads: [ + { + subQuadKey: "17", + version: 309, + dataHandle: "farther-parent-data-handle" + }, + { + subQuadKey: "70", + version: 309, + dataHandle: "closest-parent-data-handle" + } + ], + parentQuads: [] + }; + const mockedBlob = new Response("mocked-parent-blob"); + + quadTreeIndexStub.mockImplementation((): Promise => + Promise.resolve(mockedQuadKeyTreeData) + ); + const getBlobStub = vi + .spyOn(BlobApi, "getBlob") + .mockReturnValue(Promise.resolve(mockedBlob)); + + const response = await getTile( + new TileRequest() + .withTileKey({ row: 818, column: 2021, level: 11 }) + .withFetchOption(FetchOptions.OnlineOnly), + { + settings: olpClientSettingsStub as any, + catalogHrn: core.HRN.fromString("hrn:here:data:::mocked-hrn"), + layerId: "mocked-layer-id", + layerType: "versioned", + catalogVersion: 123 + } + ); + + expect(response.response).eqls(mockedBlob); + expect( + response.parentTileKey?.equals( + core.TileKey.fromRowColumnLevel(409, 1010, 10) + ) + ).toBe(true); + expect(getBlobStub.mock.calls[0][1].dataHandle).eqls( + "closest-parent-data-handle" + ); + }); + + it("Should return the blob of the closest parent tile from parent quads", async function () { + const mockedQuadKeyTreeData = { + subQuads: [], + parentQuads: [ + { + partition: "1525", + version: 309, + dataHandle: "farther-parent-data-handle" + }, + { + partition: "6103", + version: 309, + dataHandle: "closest-parent-data-handle" + } + ] + }; + const mockedBlob = new Response("mocked-parent-blob"); + + quadTreeIndexStub.mockImplementation((): Promise => + Promise.resolve(mockedQuadKeyTreeData) + ); + const getBlobStub = vi + .spyOn(BlobApi, "getBlob") + .mockReturnValue(Promise.resolve(mockedBlob)); + + const response = await getTile( + new TileRequest() + .withTileKey({ row: 818, column: 2021, level: 11 }) + .withFetchOption(FetchOptions.OnlineOnly), + { + settings: olpClientSettingsStub as any, + catalogHrn: core.HRN.fromString("hrn:here:data:::mocked-hrn"), + layerId: "mocked-layer-id", + layerType: "versioned", + catalogVersion: 123 + } + ); + + expect(response.response).eqls(mockedBlob); + expect( + response.parentTileKey?.equals( + core.TileKey.fromRowColumnLevel(25, 63, 6) + ) + ).toBe(true); + expect(getBlobStub.mock.calls[0][1].dataHandle).eqls( + "closest-parent-data-handle" + ); + }); + it("Should reject if no quad of the tree covers the tile", async function () { const mockedQuadKeyTreeData = { subQuads: [ @@ -168,13 +271,7 @@ describe("getTile", function () { catalogVersion: 123 } ) - ).rejects.toThrow( - `Error getting blob for Tile: ${JSON.stringify({ - row: 818, - column: 2021, - level: 11 - })}` - ); + ).rejects.toThrow("Error getting blob for Tile: 6250009"); }); it("Should throw an error if not tile key", async function () { From 7068ff76813a98d422fdbf6a3fc660177a86fa45 Mon Sep 17 00:00:00 2001 From: Oleksii Zubko Date: Mon, 21 Sep 2026 16:50:46 +0300 Subject: [PATCH 02/14] Add tests for aggregated tile key lookup Cover VersionedLayerClient.getAggregatedData()'s new includeTileKey overload: the default call keeps returning a plain Response, and passing { includeTileKey: true } returns the TileResponse with the resolved parentTileKey. Also add a regression test for requesting an aggregated tile at a level not deeper than the quad tree index depth (4). In that case the quad tree index root is clamped to the global root tile, and findClosestParent() currently throws "Cannot get the parent of the root tile key" while walking up the sub quad chain, instead of falling back to parentQuads. This test currently fails; it is fixed in the next commit. Relates-To: DATASDK-108 Signed-off-by: Oleksii Zubko --- .../test/unit/VersionedLayerClient.test.ts | 84 +++++++++++++++++++ .../test/unit/getTile.test.ts | 48 +++++++++++ 2 files changed, 132 insertions(+) diff --git a/@here/olp-sdk-dataservice-read/test/unit/VersionedLayerClient.test.ts b/@here/olp-sdk-dataservice-read/test/unit/VersionedLayerClient.test.ts index 8f6dcea5..d7a40e3d 100644 --- a/@here/olp-sdk-dataservice-read/test/unit/VersionedLayerClient.test.ts +++ b/@here/olp-sdk-dataservice-read/test/unit/VersionedLayerClient.test.ts @@ -875,6 +875,90 @@ describe("VersionedLayerClient", function () { await versionedLayerClient.getPartitions(partitionsRequest); expect(partitions).equals(mockedPartitions); }); + + it("Method getAggregatedData should return a plain Response by default", async function () { + // The sub quad key of row 818, column 2021, level 11 within the quad + // tree that is rooted four levels above it. + const requestedSubQuadKey = "281"; + const mockedQuadKeyTreeData: Index = { + subQuads: [ + { + subQuadKey: requestedSubQuadKey, + version: 5, + dataHandle: "mocked-aggregated-data-handle" + } + ], + parentQuads: [] + }; + const mockedBlobData = new Response("mocked-aggregated-blob"); + + getQuadTreeIndexStub.mockImplementation( + (builder: any, params: any): Promise => { + return Promise.resolve(mockedQuadKeyTreeData); + } + ); + getBlobStub.mockImplementation( + (builder: any, params: any): Promise => { + return Promise.resolve(mockedBlobData); + } + ); + + const tileRequest = new dataServiceRead.TileRequest() + .withTileKey({ row: 818, column: 2021, level: 11 }) + .withFetchOption(core.FetchOptions.OnlineOnly); + + const response = + await versionedLayerClient.getAggregatedData(tileRequest); + + assert.instanceOf(response, Response); + expect(response).eqls(mockedBlobData); + assert.isUndefined((response as any).parentTileKey); + }); + + it("Method getAggregatedData should return the parent tile key when includeTileKey is true", async function () { + // The sub quad key of row 818, column 2021, level 11 within the quad + // tree that is rooted four levels above it. + const requestedSubQuadKey = "281"; + const mockedQuadKeyTreeData: Index = { + subQuads: [ + { + subQuadKey: requestedSubQuadKey, + version: 5, + dataHandle: "mocked-aggregated-data-handle" + } + ], + parentQuads: [] + }; + const mockedBlobData = new Response("mocked-aggregated-blob"); + + getQuadTreeIndexStub.mockImplementation( + (builder: any, params: any): Promise => { + return Promise.resolve(mockedQuadKeyTreeData); + } + ); + getBlobStub.mockImplementation( + (builder: any, params: any): Promise => { + return Promise.resolve(mockedBlobData); + } + ); + + const requestedTileKey = core.TileKey.fromRowColumnLevel(818, 2021, 11); + const tileRequest = new dataServiceRead.TileRequest() + .withTileKey(requestedTileKey) + .withFetchOption(core.FetchOptions.OnlineOnly); + + const response = await versionedLayerClient.getAggregatedData( + tileRequest, + undefined, + { includeTileKey: true } + ); + + expect(response.response).eqls(mockedBlobData); + expect(response.parentTileKey?.equals(requestedTileKey)).toBe(true); + expect(getBlobStub.mock.calls[0][1].dataHandle).eqls( + "mocked-aggregated-data-handle" + ); + }); }); describe("VersionedLayerClient with locked version 0 in constructor", function () { diff --git a/@here/olp-sdk-dataservice-read/test/unit/getTile.test.ts b/@here/olp-sdk-dataservice-read/test/unit/getTile.test.ts index ad40f99d..0605fb6f 100644 --- a/@here/olp-sdk-dataservice-read/test/unit/getTile.test.ts +++ b/@here/olp-sdk-dataservice-read/test/unit/getTile.test.ts @@ -274,6 +274,54 @@ describe("getTile", function () { ).rejects.toThrow("Error getting blob for Tile: 6250009"); }); + it("Should return the blob of the closest parent tile when the requested tile is at or below the quad tree index depth", async function () { + // The requested tile is at level 2, which is not deeper than the + // quad tree index depth (4). This clamps the root of the quad tree + // index to the global root tile (level 0), so walking up from the + // requested tile reaches level 0 without ever leaving the tree. + const mockedQuadKeyTreeData = { + subQuads: [], + parentQuads: [ + { + partition: "1", + version: 309, + dataHandle: "root-data-handle" + } + ] + }; + const mockedBlob = new Response("mocked-parent-blob"); + + quadTreeIndexStub.mockImplementation((): Promise => + Promise.resolve(mockedQuadKeyTreeData) + ); + const getBlobStub = vi + .spyOn(BlobApi, "getBlob") + .mockReturnValue(Promise.resolve(mockedBlob)); + + const response = await getTile( + new TileRequest() + .withTileKey({ row: 1, column: 1, level: 2 }) + .withFetchOption(FetchOptions.OnlineOnly), + { + settings: olpClientSettingsStub as any, + catalogHrn: core.HRN.fromString("hrn:here:data:::mocked-hrn"), + layerId: "mocked-layer-id", + layerType: "versioned", + catalogVersion: 123 + } + ); + + expect(response.response).eqls(mockedBlob); + expect( + response.parentTileKey?.equals( + core.TileKey.fromRowColumnLevel(0, 0, 0) + ) + ).toBe(true); + expect(getBlobStub.mock.calls[0][1].dataHandle).eqls( + "root-data-handle" + ); + }); + it("Should throw an error if not tile key", async function () { const tile = await getTile(request, { settings: olpClientSettingsStub as any, From c47df4a4724ada26ea950dae92ffae8e421407d2 Mon Sep 17 00:00:00 2001 From: Oleksii Zubko Date: Mon, 21 Sep 2026 16:51:26 +0300 Subject: [PATCH 03/14] Fix findClosestParent() root-level crash When the requested tile's level is not deeper than the quad tree index depth (4), TileKey.changedLevelBy() clamps quadTreeIndexRoot to the global root tile (level 0). findClosestParent() then walks the sub quad chain up to and including that root, but unconditionally called currentTileKey.parent() afterwards even once currentTileKey was already the root - and TileKey.parent() throws on the root instead of returning a tile. The exception pre-empted the parentQuads fallback that this same commit's logic depends on to guarantee the closest ancestor. Stop the walk once currentTileKey reaches quadTreeIndexRoot instead of requesting its parent, so the lookup falls through to parentQuads as intended. Relates-To: DATASDK-108 Signed-off-by: Oleksii Zubko --- @here/olp-sdk-dataservice-read/lib/utils/getTile.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/@here/olp-sdk-dataservice-read/lib/utils/getTile.ts b/@here/olp-sdk-dataservice-read/lib/utils/getTile.ts index d61b3d17..746614a8 100644 --- a/@here/olp-sdk-dataservice-read/lib/utils/getTile.ts +++ b/@here/olp-sdk-dataservice-read/lib/utils/getTile.ts @@ -207,6 +207,13 @@ function findClosestParent( return { parentQuad: subQuad, tileKey: currentTileKey }; } + // Stop before requesting the parent of the quad tree index root - + // that root can be the global root tile (level 0), whose parent() + // throws rather than returning another tile. + if (currentTileKey.level === quadTreeIndexRoot.level) { + break; + } + currentTileKey = currentTileKey.parent(); } From 7c0e0b6f537a2af00b52429c0207c6be593ab1ee Mon Sep 17 00:00:00 2001 From: Oleksii Zubko Date: Mon, 21 Sep 2026 17:32:07 +0300 Subject: [PATCH 04/14] Fix getTile() integration test for TileResponse getTile() now returns TileResponse ({ response, parentTileKey }) instead of a plain Response. VersionedLayerClient.ts, VolatileLayerClient.ts and the unit test already accounted for this, but this integration test calls getTile() directly and still called .blob() on the result, so it only failed once a build actually reflected the new getTile.ts (this repo's root-level integration and api-breaks suites resolve @here/olp-sdk-dataservice-read via its compiled output, not the TypeScript source, so a stale build made the break invisible locally until a fresh npm run build surfaced it). Read the blob through the new .response field instead. Relates-To: DATASDK-108 Signed-off-by: Oleksii Zubko --- tests/integration/olp-sdk-dataservice-read/getTile.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/olp-sdk-dataservice-read/getTile.test.ts b/tests/integration/olp-sdk-dataservice-read/getTile.test.ts index 7ba62ae7..893e37bd 100644 --- a/tests/integration/olp-sdk-dataservice-read/getTile.test.ts +++ b/tests/integration/olp-sdk-dataservice-read/getTile.test.ts @@ -138,8 +138,8 @@ describe("getTile", () => { assert.isDefined(response); assert.isDefined(response2); - expect((await response.blob()).size).to.be.equals(100); - expect((await response2.blob()).size).to.be.equals(300); + expect((await response.response.blob()).size).to.be.equals(100); + expect((await response2.response.blob()).size).to.be.equals(300); /** * Should be 5 calls: From 48628fef8e1984fff7ac20e13e786479df9af3f5 Mon Sep 17 00:00:00 2001 From: Oleksii Zubko Date: Mon, 21 Sep 2026 17:41:10 +0300 Subject: [PATCH 05/14] Overload getTile() to avoid a breaking change getTile() is re-exported from the package root, so any consumer could already be calling it directly and relying on the plain Response it used to return. Changing its return type to TileResponse, as this branch previously did, silently breaks that caller on the next patch release: no test in this repo guards getTile()'s public shape (there is no api-breaks test for it), which is how this slipped through. Give the free function the same two-overload treatment already used on VersionedLayerClient.getAggregatedData(): the three-argument call keeps returning Promise unchanged, and a new fourth argument, { includeTileKey: true }, opts in to Promise. The shared logic moves into an unexported fetchTile() that always resolves the full TileResponse; both overloads and both Client classes are built on top of it. VersionedLayerClient.getAggregatedData() now asks fetchTile() for the full TileResponse via that fourth argument, since it needs parentTileKey to honor its own includeTileKey option. VolatileLayerClient.getAggregatedData() goes back to forwarding its arguments straight to getTile(), since it never needed the tile key. The two pre-existing getTile() tests ("204 response" and "blob of the requested tile") go back to their original, unmodified form, since the three-argument call is unchanged again. Their includeTileKey coverage moves into new, separate tests instead of being folded into them. Relates-To: DATASDK-108 Signed-off-by: Oleksii Zubko --- .../lib/client/VersionedLayerClient.ts | 4 +- .../lib/client/VolatileLayerClient.ts | 8 +- .../lib/utils/getTile.ts | 36 ++++++++ .../test/unit/getTile.test.ts | 88 ++++++++++++++++++- .../olp-sdk-dataservice-read/getTile.test.ts | 4 +- 5 files changed, 131 insertions(+), 9 deletions(-) diff --git a/@here/olp-sdk-dataservice-read/lib/client/VersionedLayerClient.ts b/@here/olp-sdk-dataservice-read/lib/client/VersionedLayerClient.ts index e1f127c0..4dbcea86 100644 --- a/@here/olp-sdk-dataservice-read/lib/client/VersionedLayerClient.ts +++ b/@here/olp-sdk-dataservice-read/lib/client/VersionedLayerClient.ts @@ -148,7 +148,9 @@ export class VersionedLayerClient { catalogVersion }; - const tileResponse = await getTile(request, params, abortSignal); + const tileResponse = await getTile(request, params, abortSignal, { + includeTileKey: true + }); return options?.includeTileKey ? tileResponse : tileResponse.response; } diff --git a/@here/olp-sdk-dataservice-read/lib/client/VolatileLayerClient.ts b/@here/olp-sdk-dataservice-read/lib/client/VolatileLayerClient.ts index d8021cce..bc8a6093 100644 --- a/@here/olp-sdk-dataservice-read/lib/client/VolatileLayerClient.ts +++ b/@here/olp-sdk-dataservice-read/lib/client/VolatileLayerClient.ts @@ -98,7 +98,10 @@ export class VolatileLayerClient { * * @return Tile data (if it exists) or the nearest parent tile data */ - async getAggregatedData(request: TileRequest, abortSignal?: AbortSignal) { + async getAggregatedData( + request: TileRequest, + abortSignal?: AbortSignal + ): Promise { const params: TileRequestParams = { catalogHrn: HRN.fromString(this.hrn), layerId: this.layerId, @@ -106,8 +109,7 @@ export class VolatileLayerClient { settings: this.settings }; - const tileResponse = await getTile(request, params, abortSignal); - return tileResponse.response; + return getTile(request, params, abortSignal); } /** diff --git a/@here/olp-sdk-dataservice-read/lib/utils/getTile.ts b/@here/olp-sdk-dataservice-read/lib/utils/getTile.ts index 746614a8..0c7701cb 100644 --- a/@here/olp-sdk-dataservice-read/lib/utils/getTile.ts +++ b/@here/olp-sdk-dataservice-read/lib/utils/getTile.ts @@ -86,7 +86,43 @@ export interface TileResponse { * * @returns The blob of the requested tile or the blob of the closest parent Tile. */ +export function getTile( + request: TileRequest, + params: TileRequestParams, + abortSignal?: AbortSignal +): Promise; + +/** + * Same as the three-argument [[getTile]], but also resolves the tile key of + * the parent whose data was actually returned. + * + * @param options Pass `{ includeTileKey: true }` to receive a [[TileResponse]] + * instead of a plain `Response`. + * + * @returns The blob of the requested tile or the blob of the closest parent + * tile, together with that tile's key. + */ +export function getTile( + request: TileRequest, + params: TileRequestParams, + abortSignal: AbortSignal | undefined, + options: { includeTileKey: true } +): Promise; + export async function getTile( + request: TileRequest, + params: TileRequestParams, + abortSignal?: AbortSignal, + options?: { includeTileKey?: boolean } +): Promise { + const tileResponse = await fetchTile(request, params, abortSignal); + return options?.includeTileKey ? tileResponse : tileResponse.response; +} + +/** + * @hidden + */ +async function fetchTile( request: TileRequest, params: TileRequestParams, abortSignal?: AbortSignal diff --git a/@here/olp-sdk-dataservice-read/test/unit/getTile.test.ts b/@here/olp-sdk-dataservice-read/test/unit/getTile.test.ts index 0605fb6f..3a4c35e6 100644 --- a/@here/olp-sdk-dataservice-read/test/unit/getTile.test.ts +++ b/@here/olp-sdk-dataservice-read/test/unit/getTile.test.ts @@ -89,8 +89,40 @@ describe("getTile", function () { } ); + expect(response.status).eqls(204); + expect(response.statusText).eqls("No Content"); + }); + + it("Should return a 204 TileResponse when includeTileKey is true", async function () { + const mockedQuadKeyTreeData = { + subQuads: [], + parentQuads: [] + }; + + quadTreeIndexStub.mockImplementation( + (builder: any, params: any): Promise => { + return Promise.resolve(mockedQuadKeyTreeData); + } + ); + + const response = await getTile( + new TileRequest() + .withTileKey({ row: 0, column: 0, level: 0 }) + .withFetchOption(FetchOptions.OnlineOnly), + { + settings: olpClientSettingsStub as any, + catalogHrn: core.HRN.fromString("hrn:here:data:::mocked-hrn"), + layerId: "mocked-layer-id", + layerType: "versioned", + catalogVersion: 123 + }, + undefined, + { includeTileKey: true } + ); + expect(response.response.status).eqls(204); expect(response.response.statusText).eqls("No Content"); + assert.isUndefined(response.parentTileKey); }); it("Should return the blob of the requested tile", async function () { @@ -129,6 +161,50 @@ describe("getTile", function () { } ); + expect(response).eqls(mockedBlob); + expect(getBlobStub.mock.calls[0][1].dataHandle).eqls( + "mocked-data-handle" + ); + }); + + it("Should resolve the requested tile's own key when includeTileKey is true", async function () { + // The sub quad key of row 818, column 2021, level 11 within the quad + // tree that is rooted four levels above it. + const requestedSubQuadKey = "281"; + const mockedQuadKeyTreeData = { + subQuads: [ + { + subQuadKey: requestedSubQuadKey, + version: 309, + dataHandle: "mocked-data-handle" + } + ], + parentQuads: [] + }; + const mockedBlob = new Response("mocked-blob"); + + quadTreeIndexStub.mockImplementation((): Promise => + Promise.resolve(mockedQuadKeyTreeData) + ); + const getBlobStub = vi + .spyOn(BlobApi, "getBlob") + .mockReturnValue(Promise.resolve(mockedBlob)); + + const response = await getTile( + new TileRequest() + .withTileKey({ row: 818, column: 2021, level: 11 }) + .withFetchOption(FetchOptions.OnlineOnly), + { + settings: olpClientSettingsStub as any, + catalogHrn: core.HRN.fromString("hrn:here:data:::mocked-hrn"), + layerId: "mocked-layer-id", + layerType: "versioned", + catalogVersion: 123 + }, + undefined, + { includeTileKey: true } + ); + expect(response.response).eqls(mockedBlob); expect( response.parentTileKey?.equals( @@ -175,7 +251,9 @@ describe("getTile", function () { layerId: "mocked-layer-id", layerType: "versioned", catalogVersion: 123 - } + }, + undefined, + { includeTileKey: true } ); expect(response.response).eqls(mockedBlob); @@ -224,7 +302,9 @@ describe("getTile", function () { layerId: "mocked-layer-id", layerType: "versioned", catalogVersion: 123 - } + }, + undefined, + { includeTileKey: true } ); expect(response.response).eqls(mockedBlob); @@ -308,7 +388,9 @@ describe("getTile", function () { layerId: "mocked-layer-id", layerType: "versioned", catalogVersion: 123 - } + }, + undefined, + { includeTileKey: true } ); expect(response.response).eqls(mockedBlob); diff --git a/tests/integration/olp-sdk-dataservice-read/getTile.test.ts b/tests/integration/olp-sdk-dataservice-read/getTile.test.ts index 893e37bd..7ba62ae7 100644 --- a/tests/integration/olp-sdk-dataservice-read/getTile.test.ts +++ b/tests/integration/olp-sdk-dataservice-read/getTile.test.ts @@ -138,8 +138,8 @@ describe("getTile", () => { assert.isDefined(response); assert.isDefined(response2); - expect((await response.response.blob()).size).to.be.equals(100); - expect((await response2.response.blob()).size).to.be.equals(300); + expect((await response.blob()).size).to.be.equals(100); + expect((await response2.blob()).size).to.be.equals(300); /** * Should be 5 calls: From da7374e8a1d80884196ca71922e67ebefb2e2bca Mon Sep 17 00:00:00 2001 From: Oleksii Zubko Date: Mon, 21 Sep 2026 17:43:43 +0300 Subject: [PATCH 06/14] Add api-breaks coverage for getTile() getTile() is exported from the package root, but no test in tests/integration/api-breaks/ covered it, which is how its return type silently changed from Response to TileResponse earlier on this branch without any test catching it. Guard the three-argument call (a plain Response, including that an AbortSignal is still accepted as the third argument) and the new includeTileKey opt-in (a TileResponse) against a future incompatible change. Relates-To: DATASDK-108 Signed-off-by: Oleksii Zubko --- tests/integration/api-breaks/getTile.test.ts | 143 +++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 tests/integration/api-breaks/getTile.test.ts diff --git a/tests/integration/api-breaks/getTile.test.ts b/tests/integration/api-breaks/getTile.test.ts new file mode 100644 index 00000000..a722f0f3 --- /dev/null +++ b/tests/integration/api-breaks/getTile.test.ts @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2020-2026 HERE Europe B.V. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ + +import { + afterEach, + assert, + beforeEach, + describe, + expect, + it, + vi +} from "vitest"; +import { + getTile, + TileRequest, + TileResponse +} from "@here/olp-sdk-dataservice-read"; +import { BlobApi, QueryApi } from "@here/olp-sdk-dataservice-api"; +import { + FetchOptions, + HRN, + OlpClientSettings, + RequestFactory, + TileKey +} from "@here/olp-sdk-core"; + +// getTile() is re-exported from the package root, so it is public API even +// though it is mostly used internally by VersionedLayerClient and +// VolatileLayerClient. These tests guard its call signature and return +// shape against an accidental breaking change, such as the three-argument +// call silently starting to resolve something other than a plain Response. +describe("getTile", function () { + const settings = new OlpClientSettings({ + environment: "here", + getToken: () => Promise.resolve("mocked-token") + }); + const catalogHrn = HRN.fromString("hrn:here:data:::mocked-hrn"); + // The sub quad key of row 818, column 2021, level 11 within the quad + // tree that is rooted four levels above it. + const requestedSubQuadKey = "281"; + + beforeEach(function () { + vi.spyOn(RequestFactory, "getBaseUrl").mockImplementation(() => + Promise.resolve("http://fake-base.url") + ); + vi.spyOn(QueryApi, "quadTreeIndex").mockImplementation( + (): Promise => + Promise.resolve({ + subQuads: [ + { + subQuadKey: requestedSubQuadKey, + version: 1, + dataHandle: "mocked-data-handle" + } + ], + parentQuads: [] + }) + ); + vi.spyOn(BlobApi, "getBlob").mockReturnValue( + Promise.resolve(new Response("mocked-blob")) + ); + }); + + afterEach(function () { + vi.restoreAllMocks(); + }); + + function tileRequest(): TileRequest { + return new TileRequest() + .withTileKey({ row: 818, column: 2021, level: 11 }) + .withFetchOption(FetchOptions.OnlineOnly); + } + + it("Should resolve a plain Response with the three-argument call", async function () { + const response = await getTile(tileRequest(), { + settings, + catalogHrn, + layerId: "mocked-layer-id", + layerType: "versioned", + catalogVersion: 123 + }); + + assert.instanceOf(response, Response); + assert.isUndefined((response as any).parentTileKey); + expect((await response.blob()).size).to.be.greaterThan(0); + }); + + it("Should still accept an abortSignal as the third argument", async function () { + const abortController = new AbortController(); + + const response = await getTile( + tileRequest(), + { + settings, + catalogHrn, + layerId: "mocked-layer-id", + layerType: "versioned", + catalogVersion: 123 + }, + abortController.signal + ); + + assert.instanceOf(response, Response); + }); + + it("Should resolve a TileResponse when includeTileKey is true", async function () { + const response: TileResponse = await getTile( + tileRequest(), + { + settings, + catalogHrn, + layerId: "mocked-layer-id", + layerType: "versioned", + catalogVersion: 123 + }, + undefined, + { includeTileKey: true } + ); + + assert.instanceOf(response.response, Response); + expect( + response.parentTileKey?.equals( + TileKey.fromRowColumnLevel(818, 2021, 11) + ) + ).to.be.equal(true); + }); +}); From c36e7286529f09659cca60035cf89975c364a73d Mon Sep 17 00:00:00 2001 From: Oleksii Zubko Date: Mon, 21 Sep 2026 18:17:02 +0300 Subject: [PATCH 07/14] Close getTile() coverage gaps flagged by Codecov Codecov's patch coverage check flagged fetchTile() and findClosestParent() in getTile.ts: 1 fully missing line and 5 partial branches, all inside code this branch added or rewrote. - The cached-quad-tree-index path (request.getFetchOption() !== FetchOptions.OnlineOnly) had no test at all, so cache.get() never ran. - The `?? []` fallbacks for an omitted subQuads/parentQuads field, in both fetchTile() and findClosestParent(), were only ever exercised with an explicit empty array, never with the field missing. - findClosestParent()'s parentQuads reduce always saw candidates in increasing depth order, so the branch that keeps the current closest match instead of replacing it never ran. Add one test per gap: a cache hit that skips QueryApi.quadTreeIndex, a missing subQuads field, a missing parentQuads field, and a parentQuads list with the deepest match first. Relates-To: DATASDK-108 Signed-off-by: Oleksii Zubko --- .../test/unit/getTile.test.ts | 194 ++++++++++++++++++ 1 file changed, 194 insertions(+) diff --git a/@here/olp-sdk-dataservice-read/test/unit/getTile.test.ts b/@here/olp-sdk-dataservice-read/test/unit/getTile.test.ts index 3a4c35e6..aa428f36 100644 --- a/@here/olp-sdk-dataservice-read/test/unit/getTile.test.ts +++ b/@here/olp-sdk-dataservice-read/test/unit/getTile.test.ts @@ -30,6 +30,7 @@ import { } from "vitest"; import { createStubInstance } from "./stub-instance"; import { TileRequest, getTile } from "@here/olp-sdk-dataservice-read/lib"; +import { QuadTreeIndexCacheRepository } from "../../lib/cache/QuadTreeIndexCacheRepository"; import * as core from "@here/olp-sdk-core"; import { BlobApi, QueryApi } from "@here/olp-sdk-dataservice-api"; import { FetchOptions } from "@here/olp-sdk-core"; @@ -404,6 +405,199 @@ describe("getTile", function () { ); }); + it("Should keep the already-found deepest parent quad", async function () { + const mockedQuadKeyTreeData = { + subQuads: [], + parentQuads: [ + { + partition: "6103", + version: 309, + dataHandle: "closest-parent-data-handle" + }, + { + partition: "1525", + version: 309, + dataHandle: "should-be-ignored-data-handle" + } + ] + }; + const mockedBlob = new Response("mocked-parent-blob"); + + quadTreeIndexStub.mockImplementation((): Promise => + Promise.resolve(mockedQuadKeyTreeData) + ); + const getBlobStub = vi + .spyOn(BlobApi, "getBlob") + .mockReturnValue(Promise.resolve(mockedBlob)); + + const response = await getTile( + new TileRequest() + .withTileKey({ row: 818, column: 2021, level: 11 }) + .withFetchOption(FetchOptions.OnlineOnly), + { + settings: olpClientSettingsStub as any, + catalogHrn: core.HRN.fromString("hrn:here:data:::mocked-hrn"), + layerId: "mocked-layer-id", + layerType: "versioned", + catalogVersion: 123 + }, + undefined, + { includeTileKey: true } + ); + + expect(response.response).eqls(mockedBlob); + expect( + response.parentTileKey?.equals( + core.TileKey.fromRowColumnLevel(25, 63, 6) + ) + ).toBe(true); + expect(getBlobStub.mock.calls[0][1].dataHandle).eqls( + "closest-parent-data-handle" + ); + }); + + it("Should use the cached quad tree index instead of fetching it", async function () { + const settings = new core.OlpClientSettings({ + environment: "here", + getToken: () => Promise.resolve("mocked-token") + }); + const requestedTileKey = core.TileKey.fromRowColumnLevel(818, 2021, 11); + + new QuadTreeIndexCacheRepository(settings.cache).put({ + hrn: "hrn:here:data:::mocked-hrn", + layerId: "mocked-layer-id", + depth: 4, + root: requestedTileKey.changedLevelBy(-4), + version: 123, + tree: { + subQuads: [ + { + subQuadKey: "281", + version: 309, + dataHandle: "cached-data-handle" + } + ], + parentQuads: [] + } + }); + + const mockedBlob = new Response("cached-blob"); + const getBlobStub = vi + .spyOn(BlobApi, "getBlob") + .mockReturnValue(Promise.resolve(mockedBlob)); + + const response = await getTile( + new TileRequest().withTileKey(requestedTileKey), + { + settings, + catalogHrn: core.HRN.fromString("hrn:here:data:::mocked-hrn"), + layerId: "mocked-layer-id", + layerType: "versioned", + catalogVersion: 123 + }, + undefined, + { includeTileKey: true } + ); + + expect(quadTreeIndexStub).not.toHaveBeenCalled(); + expect(response.response).eqls(mockedBlob); + expect(response.parentTileKey?.equals(requestedTileKey)).toBe(true); + expect(getBlobStub.mock.calls[0][1].dataHandle).eqls( + "cached-data-handle" + ); + }); + + it("Should treat a missing subQuads field as an empty list", async function () { + const mockedQuadKeyTreeData = { + parentQuads: [ + { + partition: "6103", + version: 309, + dataHandle: "closest-parent-data-handle" + } + ] + }; + const mockedBlob = new Response("mocked-parent-blob"); + + quadTreeIndexStub.mockImplementation((): Promise => + Promise.resolve(mockedQuadKeyTreeData) + ); + const getBlobStub = vi + .spyOn(BlobApi, "getBlob") + .mockReturnValue(Promise.resolve(mockedBlob)); + + const response = await getTile( + new TileRequest() + .withTileKey({ row: 818, column: 2021, level: 11 }) + .withFetchOption(FetchOptions.OnlineOnly), + { + settings: olpClientSettingsStub as any, + catalogHrn: core.HRN.fromString("hrn:here:data:::mocked-hrn"), + layerId: "mocked-layer-id", + layerType: "versioned", + catalogVersion: 123 + }, + undefined, + { includeTileKey: true } + ); + + expect(response.response).eqls(mockedBlob); + expect( + response.parentTileKey?.equals( + core.TileKey.fromRowColumnLevel(25, 63, 6) + ) + ).toBe(true); + expect(getBlobStub.mock.calls[0][1].dataHandle).eqls( + "closest-parent-data-handle" + ); + }); + + it("Should treat a missing parentQuads field as an empty list", async function () { + const requestedSubQuadKey = "281"; + const mockedQuadKeyTreeData = { + subQuads: [ + { + subQuadKey: requestedSubQuadKey, + version: 309, + dataHandle: "mocked-data-handle" + } + ] + }; + const mockedBlob = new Response("mocked-blob"); + + quadTreeIndexStub.mockImplementation((): Promise => + Promise.resolve(mockedQuadKeyTreeData) + ); + const getBlobStub = vi + .spyOn(BlobApi, "getBlob") + .mockReturnValue(Promise.resolve(mockedBlob)); + + const response = await getTile( + new TileRequest() + .withTileKey({ row: 818, column: 2021, level: 11 }) + .withFetchOption(FetchOptions.OnlineOnly), + { + settings: olpClientSettingsStub as any, + catalogHrn: core.HRN.fromString("hrn:here:data:::mocked-hrn"), + layerId: "mocked-layer-id", + layerType: "versioned", + catalogVersion: 123 + }, + undefined, + { includeTileKey: true } + ); + + expect(response.response).eqls(mockedBlob); + expect( + response.parentTileKey?.equals( + core.TileKey.fromRowColumnLevel(818, 2021, 11) + ) + ).toBe(true); + expect(getBlobStub.mock.calls[0][1].dataHandle).eqls( + "mocked-data-handle" + ); + }); + it("Should throw an error if not tile key", async function () { const tile = await getTile(request, { settings: olpClientSettingsStub as any, From 00968bfe412905ed44708c55ac2e082ffead7643 Mon Sep 17 00:00:00 2001 From: Oleksii Zubko Date: Mon, 21 Sep 2026 18:18:55 +0300 Subject: [PATCH 08/14] Bump @here/olp-sdk-dataservice-read to 3.0.1 This branch adds the aggregated-tile-key feature and its follow-up fixes (crash fix, non-breaking overload, coverage), so the package needs a patch release once it lands. Relates-To: DATASDK-108 Signed-off-by: Oleksii Zubko --- @here/olp-sdk-dataservice-read/package.json | 2 +- package-lock.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/@here/olp-sdk-dataservice-read/package.json b/@here/olp-sdk-dataservice-read/package.json index b1849373..645c8bd2 100644 --- a/@here/olp-sdk-dataservice-read/package.json +++ b/@here/olp-sdk-dataservice-read/package.json @@ -1,6 +1,6 @@ { "name": "@here/olp-sdk-dataservice-read", - "version": "3.0.0", + "version": "3.0.1", "description": "Wrapper around a subset of the HERE Open Location Platform Data REST API related to reading data from OLP catalogs", "main": "index.js", "browser": "index.web.js", diff --git a/package-lock.json b/package-lock.json index f6607ef9..146c7f2b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,7 +68,7 @@ } }, "@here/olp-sdk-dataservice-read": { - "version": "3.0.0", + "version": "3.0.1", "license": "Apache-2.0", "dependencies": { "@here/olp-sdk-core": "^3.0.0", From b60b04e0bd03541bd0f63c43ff553e4ecc7c9aa8 Mon Sep 17 00:00:00 2001 From: Oleksii Zubko Date: Mon, 21 Sep 2026 18:22:46 +0300 Subject: [PATCH 09/14] Fix copyright year in getTile api-breaks test The file was added this year, so it does not need the 2020 start year copied from older test files in the same directory. Relates-To: DATASDK-108 Signed-off-by: Oleksii Zubko --- tests/integration/api-breaks/getTile.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/api-breaks/getTile.test.ts b/tests/integration/api-breaks/getTile.test.ts index a722f0f3..39a7ab05 100644 --- a/tests/integration/api-breaks/getTile.test.ts +++ b/tests/integration/api-breaks/getTile.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2026 HERE Europe B.V. + * Copyright (C) 2026 HERE Europe B.V. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From 095346441c8507ed183bd747037758df312fa9f2 Mon Sep 17 00:00:00 2001 From: Oleksii Zubko Date: Mon, 21 Sep 2026 19:11:30 +0300 Subject: [PATCH 10/14] Cache quad tree indexes that have only parents fetchQuadTreeIndex() gated its cache.put() on a truthy subQuads field, which predates the parentQuads fallback this branch added. A response that carries only parentQuads - exactly the case that fallback exists for - was therefore never cached, so every getAggregatedData() call for that root re-fetched the index over the network. Gate on either field being present instead, and add a regression test that fails without the fix. Relates-To: DATASDK-108 Signed-off-by: Oleksii Zubko --- .../lib/utils/getTile.ts | 3 +- .../test/unit/getTile.test.ts | 37 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/@here/olp-sdk-dataservice-read/lib/utils/getTile.ts b/@here/olp-sdk-dataservice-read/lib/utils/getTile.ts index 0c7701cb..8b7e48ea 100644 --- a/@here/olp-sdk-dataservice-read/lib/utils/getTile.ts +++ b/@here/olp-sdk-dataservice-read/lib/utils/getTile.ts @@ -301,7 +301,8 @@ export async function fetchQuadTreeIndex( if ( params.fetchOptions !== FetchOptions.OnlineOnly && - quadTreeIndex.subQuads + (quadTreeIndex.subQuads !== undefined || + quadTreeIndex.parentQuads !== undefined) ) { cache.put({ hrn: params.catalogHrn.toString(), diff --git a/@here/olp-sdk-dataservice-read/test/unit/getTile.test.ts b/@here/olp-sdk-dataservice-read/test/unit/getTile.test.ts index aa428f36..0f4283b7 100644 --- a/@here/olp-sdk-dataservice-read/test/unit/getTile.test.ts +++ b/@here/olp-sdk-dataservice-read/test/unit/getTile.test.ts @@ -507,6 +507,43 @@ describe("getTile", function () { ); }); + it("Should cache a quad tree index that has only parent quads", async function () { + const settings = new core.OlpClientSettings({ + environment: "here", + getToken: () => Promise.resolve("mocked-token") + }); + const mockedQuadKeyTreeData = { + parentQuads: [ + { + partition: "6103", + version: 309, + dataHandle: "closest-parent-data-handle" + } + ] + }; + + quadTreeIndexStub.mockImplementation((): Promise => + Promise.resolve(mockedQuadKeyTreeData) + ); + vi.spyOn(BlobApi, "getBlob").mockReturnValue( + Promise.resolve(new Response("mocked-parent-blob")) + ); + + const params = { + settings, + catalogHrn: core.HRN.fromString("hrn:here:data:::mocked-hrn"), + layerId: "mocked-layer-id", + layerType: "versioned" as const, + catalogVersion: 123 + }; + const tileKey = { row: 818, column: 2021, level: 11 }; + + await getTile(new TileRequest().withTileKey(tileKey), params); + await getTile(new TileRequest().withTileKey(tileKey), params); + + expect(quadTreeIndexStub).toHaveBeenCalledTimes(1); + }); + it("Should treat a missing subQuads field as an empty list", async function () { const mockedQuadKeyTreeData = { parentQuads: [ From c385014070d759a08701be7e8c8470a8d0d7d98a Mon Sep 17 00:00:00 2001 From: Oleksii Zubko Date: Mon, 21 Sep 2026 19:12:15 +0300 Subject: [PATCH 11/14] Type-check and run api-breaks guards in CI The api-breaks suite guards the exported surface with subclasses such as VersionedLayerClientTest, which only fail as compile errors. Vitest transpiles without type checking, and the CI script never invoked the suite at all, so those guards never ran anywhere. Add getAggregatedData to the guard, since it is the method this branch changed, run tsc over tests/ before the suite, and call the suite from the CI script. Relates-To: DATASDK-108 Signed-off-by: Oleksii Zubko --- package.json | 2 +- scripts/linux/psv/build_test_psv.sh | 3 +++ .../api-breaks/VersionedLayerClient.test.ts | 24 ++++++++++++++++++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index fa143e1e..cf8d21ff 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "http-server-testing-bundles": "node scripts/static-server.js tests/integration/bundles/umd/static 8080", "test-published-bundles": "vitest run --config ./vitest.bundles-published.config.ts", "test-generated-bundles": "vitest run --config ./vitest.bundles-generated.config.ts", - "api-breaks-test": "vitest run --config ./vitest.api-breaks.config.ts", + "api-breaks-test": "tsc --noEmit -p tsconfig.json && vitest run --config ./vitest.api-breaks.config.ts", "integration-test": "vitest run --config ./vitest.integration.config.ts", "functional-test": "vitest run --config ./vitest.functional.config.ts", "coverage": "npm run coverage --workspaces --if-present", diff --git a/scripts/linux/psv/build_test_psv.sh b/scripts/linux/psv/build_test_psv.sh index e00cea3a..5d49e969 100755 --- a/scripts/linux/psv/build_test_psv.sh +++ b/scripts/linux/psv/build_test_psv.sh @@ -40,6 +40,9 @@ npm run test # Integration tests npm run integration-test +# Public API surface guards (type-checks tests/ before running) +npm run api-breaks-test + # Functional tests npm run functional-test diff --git a/tests/integration/api-breaks/VersionedLayerClient.test.ts b/tests/integration/api-breaks/VersionedLayerClient.test.ts index d7d487f0..ee75892f 100644 --- a/tests/integration/api-breaks/VersionedLayerClient.test.ts +++ b/tests/integration/api-breaks/VersionedLayerClient.test.ts @@ -33,7 +33,9 @@ import { VersionedLayerClientParams, QuadKeyPartitionsRequest, DataRequest, - PartitionsRequest + PartitionsRequest, + TileRequest, + TileResponse } from "@here/olp-sdk-dataservice-read"; import { QueryApi, MetadataApi } from "@here/olp-sdk-dataservice-api"; import { HRN, OlpClientSettings } from "@here/olp-sdk-core"; @@ -97,6 +99,24 @@ describe("VersionedLayerClient", function () { > { return Promise.resolve({}); } + + getAggregatedData( + request: TileRequest, + abortSignal?: AbortSignal + ): Promise; + getAggregatedData( + request: TileRequest, + abortSignal: AbortSignal | undefined, + options: { includeTileKey: true } + ): Promise; + + async getAggregatedData( + request: TileRequest, + abortSignal?: AbortSignal, + options?: { includeTileKey?: boolean } + ): Promise { + return Promise.resolve(new Response()); + } } beforeEach(function () { @@ -118,6 +138,7 @@ describe("VersionedLayerClient", function () { assert.isFunction(layerClient.getData); assert.isFunction(layerClient.getPartitions); + assert.isFunction(layerClient.getAggregatedData); }); it("Shoud be initialized with VersionedLayerClientParams", async function () { @@ -134,6 +155,7 @@ describe("VersionedLayerClient", function () { assert.isFunction(layerClient.getData); assert.isFunction(layerClient.getPartitions); + assert.isFunction(layerClient.getAggregatedData); }); it("getPartitions method with QuadKeyPartitionsRequest", async function () { From 6fd8cccfddd6505992b7555ebbd2b619f6779e1d Mon Sep 17 00:00:00 2001 From: Oleksii Zubko Date: Mon, 21 Sep 2026 19:12:35 +0300 Subject: [PATCH 12/14] Accept a false or run-time includeTileKey flag The includeTileKey overload typed its options object as { includeTileKey: true }, so TypeScript callers could pass the literal true and nothing else: { includeTileKey: false } and a boolean held in a variable were both compile errors, even though the implementation and the JSDoc describe it as a flag. Add two overloads to getTile() and to VersionedLayerClient.getAggregatedData(): one for an omitted or literal false flag, resolving a plain Response, and one for a run-time boolean, resolving the union that the caller narrows. The union overload comes last so the literal forms keep their precise return types. Relates-To: DATASDK-108 Signed-off-by: Oleksii Zubko --- .../lib/client/VersionedLayerClient.ts | 29 ++++++++++ .../lib/utils/getTile.ts | 26 +++++++++ .../test/unit/getTile.test.ts | 54 +++++++++++++++++++ .../api-breaks/VersionedLayerClient.test.ts | 10 ++++ 4 files changed, 119 insertions(+) diff --git a/@here/olp-sdk-dataservice-read/lib/client/VersionedLayerClient.ts b/@here/olp-sdk-dataservice-read/lib/client/VersionedLayerClient.ts index 4dbcea86..a60df299 100644 --- a/@here/olp-sdk-dataservice-read/lib/client/VersionedLayerClient.ts +++ b/@here/olp-sdk-dataservice-read/lib/client/VersionedLayerClient.ts @@ -127,6 +127,35 @@ export class VersionedLayerClient { options: { includeTileKey: true } ): Promise; + /** + * @brief Fetches data of a tile or its closest ancestor. + * + * Same as the two-argument `getAggregatedData`, for callers that pass an + * options object with `includeTileKey` disabled or omitted. + * + * @return Tile data (if it exists) or the nearest parent tile data + */ + getAggregatedData( + request: TileRequest, + abortSignal: AbortSignal | undefined, + options: { includeTileKey?: false } + ): Promise; + + /** + * @brief Fetches data of a tile or its closest ancestor. + * + * Same as the two-argument `getAggregatedData`, for an `includeTileKey` + * flag whose value is only known at run time. Narrow the result before + * using it. + * + * @return A `TileResponse` if the flag was `true`, otherwise the tile data + */ + getAggregatedData( + request: TileRequest, + abortSignal: AbortSignal | undefined, + options: { includeTileKey?: boolean } + ): Promise; + async getAggregatedData( request: TileRequest, abortSignal?: AbortSignal, diff --git a/@here/olp-sdk-dataservice-read/lib/utils/getTile.ts b/@here/olp-sdk-dataservice-read/lib/utils/getTile.ts index 8b7e48ea..8950bab5 100644 --- a/@here/olp-sdk-dataservice-read/lib/utils/getTile.ts +++ b/@here/olp-sdk-dataservice-read/lib/utils/getTile.ts @@ -109,6 +109,32 @@ export function getTile( options: { includeTileKey: true } ): Promise; +/** + * Same as the three-argument [[getTile]], for callers that pass an options + * object with `includeTileKey` disabled or omitted. + * + * @returns The blob of the requested tile or the blob of the closest parent Tile. + */ +export function getTile( + request: TileRequest, + params: TileRequestParams, + abortSignal: AbortSignal | undefined, + options: { includeTileKey?: false } +): Promise; + +/** + * Same as the three-argument [[getTile]], for an `includeTileKey` flag whose + * value is only known at run time. Narrow the result before using it. + * + * @returns A [[TileResponse]] if the flag was `true`, otherwise the plain blob. + */ +export function getTile( + request: TileRequest, + params: TileRequestParams, + abortSignal: AbortSignal | undefined, + options: { includeTileKey?: boolean } +): Promise; + export async function getTile( request: TileRequest, params: TileRequestParams, diff --git a/@here/olp-sdk-dataservice-read/test/unit/getTile.test.ts b/@here/olp-sdk-dataservice-read/test/unit/getTile.test.ts index 0f4283b7..14d71191 100644 --- a/@here/olp-sdk-dataservice-read/test/unit/getTile.test.ts +++ b/@here/olp-sdk-dataservice-read/test/unit/getTile.test.ts @@ -168,6 +168,60 @@ describe("getTile", function () { ); }); + it("Should return a plain Response when includeTileKey is false or a run-time flag", async function () { + const mockedQuadKeyTreeData = { + subQuads: [ + { + subQuadKey: "281", + version: 309, + dataHandle: "mocked-data-handle" + } + ], + parentQuads: [] + }; + const mockedBlob = new Response("mocked-blob"); + + quadTreeIndexStub.mockImplementation((): Promise => + Promise.resolve(mockedQuadKeyTreeData) + ); + vi.spyOn(BlobApi, "getBlob").mockReturnValue( + Promise.resolve(mockedBlob) + ); + + const params = { + settings: olpClientSettingsStub as any, + catalogHrn: core.HRN.fromString("hrn:here:data:::mocked-hrn"), + layerId: "mocked-layer-id", + layerType: "versioned" as const, + catalogVersion: 123 + }; + const buildRequest = () => + new TileRequest() + .withTileKey({ row: 818, column: 2021, level: 11 }) + .withFetchOption(FetchOptions.OnlineOnly); + + const explicitlyFalse = await getTile( + buildRequest(), + params, + undefined, + { + includeTileKey: false + } + ); + expect(explicitlyFalse).eqls(mockedBlob); + + const omitted = await getTile(buildRequest(), params, undefined, {}); + expect(omitted).eqls(mockedBlob); + + // A flag only known at run time resolves to the union overload, so the + // caller has to narrow it. + const runtimeFlag: boolean = false; + const dynamic = await getTile(buildRequest(), params, undefined, { + includeTileKey: runtimeFlag + }); + assert.instanceOf(dynamic, Response); + }); + it("Should resolve the requested tile's own key when includeTileKey is true", async function () { // The sub quad key of row 818, column 2021, level 11 within the quad // tree that is rooted four levels above it. diff --git a/tests/integration/api-breaks/VersionedLayerClient.test.ts b/tests/integration/api-breaks/VersionedLayerClient.test.ts index ee75892f..3a19edef 100644 --- a/tests/integration/api-breaks/VersionedLayerClient.test.ts +++ b/tests/integration/api-breaks/VersionedLayerClient.test.ts @@ -109,6 +109,16 @@ describe("VersionedLayerClient", function () { abortSignal: AbortSignal | undefined, options: { includeTileKey: true } ): Promise; + getAggregatedData( + request: TileRequest, + abortSignal: AbortSignal | undefined, + options: { includeTileKey?: false } + ): Promise; + getAggregatedData( + request: TileRequest, + abortSignal: AbortSignal | undefined, + options: { includeTileKey?: boolean } + ): Promise; async getAggregatedData( request: TileRequest, From c9acb5d7f441fcad391dba579592e40f540283f2 Mon Sep 17 00:00:00 2001 From: Oleksii Zubko Date: Mon, 21 Sep 2026 19:12:51 +0300 Subject: [PATCH 13/14] Release @here/olp-sdk-dataservice-read 3.1.0 The earlier bump on this branch went to 3.0.1, but the branch adds exported API - TileResponse, the getTile() and getAggregatedData() overloads - and changes runtime behavior, so a patch release understates it. Consumers on a pinned-patch policy would pick the behavior change up as a safe update. Go to 3.1.0 instead and add the missing CHANGELOG entry, spelling out both behavior changes: parentQuads data now replaces the 204 that an empty sub tree used to produce, for volatile layers too, and the rejection message now carries a HERE tile key. Relates-To: DATASDK-108 Signed-off-by: Oleksii Zubko --- @here/olp-sdk-dataservice-read/package.json | 2 +- CHANGELOG.md | 9 +++++++++ package-lock.json | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/@here/olp-sdk-dataservice-read/package.json b/@here/olp-sdk-dataservice-read/package.json index 645c8bd2..abd6b9d8 100644 --- a/@here/olp-sdk-dataservice-read/package.json +++ b/@here/olp-sdk-dataservice-read/package.json @@ -1,6 +1,6 @@ { "name": "@here/olp-sdk-dataservice-read", - "version": "3.0.1", + "version": "3.1.0", "description": "Wrapper around a subset of the HERE Open Location Platform Data REST API related to reading data from OLP catalogs", "main": "index.js", "browser": "index.web.js", diff --git a/CHANGELOG.md b/CHANGELOG.md index b321b113..e61754cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## v3.1.0 (22/09/2026) + +**olp-sdk-dataservice-read** + +- Added the `includeTileKey` option to `getTile()` and `VersionedLayerClient::getAggregatedData()`. Pass `{ includeTileKey: true }` to receive a `TileResponse` that carries the key of the tile whose data was actually returned, instead of a plain `Response`. +- **Behavior change:** `getTile()` now falls back to the `parentQuads` of the quad tree index when the requested tile has no data in its own sub tree. Previously such a tile resolved a `204 No Content` response; it now resolves the data of the closest ancestor. This affects both `VersionedLayerClient::getAggregatedData()` and `VolatileLayerClient::getAggregatedData()`. Callers that branch on `response.status === 204` to detect missing data should be reviewed. +- **Behavior change:** the rejection message of `getTile()` now identifies the tile by its HERE tile key (`Error getting blob for Tile: 6250009`) instead of a serialized quad key (`Error getting blob for Tile: {"row":818,"column":2021,"level":11}`). +- Fixed a crash in the closest-parent lookup when the quad tree index root is the global root tile (level 0). + ## v3.0.0 (2026) - Discontinued support of Node.js versions less than 24.x. diff --git a/package-lock.json b/package-lock.json index 146c7f2b..4fa0d631 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,7 +68,7 @@ } }, "@here/olp-sdk-dataservice-read": { - "version": "3.0.1", + "version": "3.1.0", "license": "Apache-2.0", "dependencies": { "@here/olp-sdk-core": "^3.0.0", From 1878d7627b0cebc3df88fe1151bdb17c7d10990f Mon Sep 17 00:00:00 2001 From: Oleksii Zubko Date: Mon, 21 Sep 2026 19:25:37 +0300 Subject: [PATCH 14/14] Bump Vitest and coverage-v8 to 4.1.11 Patch update of the test runner and its V8 coverage provider. Relates-To: MINOR Signed-off-by: Oleksii Zubko --- package-lock.json | 402 +++++++++++++++++++--------------------------- package.json | 4 +- 2 files changed, 166 insertions(+), 240 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4fa0d631..cf68fb87 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "devDependencies": { "@eslint/js": "^10.0.1", "@types/node": "^24.0.0", - "@vitest/coverage-v8": "^4.1.10", + "@vitest/coverage-v8": "^4.1.11", "eslint": "^10.8.0", "eslint-config-prettier": "^10.1.8", "fetch-mock": "^12.6.0", @@ -27,7 +27,7 @@ "typedoc": "^0.28.20", "typescript": "~5.8.3", "typescript-eslint": "^8.65.0", - "vitest": "^4.1.10", + "vitest": "^4.1.11", "webpack": "^5.109.1", "webpack-cli": "^7.2.1" }, @@ -168,40 +168,6 @@ "node": ">=14.17.0" } }, - "node_modules/@emnapi/core": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", - "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.2", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", - "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", - "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.10.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", @@ -484,33 +450,14 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", - "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.3" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } - }, "node_modules/@oxc-project/types": { - "version": "0.139.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", - "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", + "version": "0.150.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.150.0.tgz", + "integrity": "sha512-rDS5/31E9HfPl/CIzGrn0DOlvBbXFseQ5URJ9sYMfstbKLD/c6Gm9vmRzRGDdAXyOIL4zmO37lc9RIwYqVruZw==", "dev": true, "license": "MIT", "funding": { - "url": "https://github.com/sponsors/Boshen" + "url": "https://github.com/sponsors/oxc-project" } }, "node_modules/@puppeteer/browsers": { @@ -542,10 +489,27 @@ } } }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.9.tgz", + "integrity": "sha512-tNISae1QEf/vkb3xkRcjV5SEdzPE97We5IVaa2Z8jSszQPZ8U60B/YCYpw4QI7VidYsBtKavczXf+DyDs9WGxw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", - "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.9.tgz", + "integrity": "sha512-YC8YsI30o606GTZi0VyzYlsDKFP8W61i/QzayHDkLbNEz/IShqAmTa+hsJRj13xTHA0H+6fk4b2UmGn+Q/cMlg==", "cpu": [ "arm64" ], @@ -560,9 +524,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", - "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.9.tgz", + "integrity": "sha512-IwhlH3qK5urrY8hZiEgGkHKEFN901p/p2bjxCxJlr4GyNnF7wYpUvK+Y43uaRYuC4hpfjzbR3SJC3arX1jGvmw==", "cpu": [ "arm64" ], @@ -577,9 +541,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", - "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.9.tgz", + "integrity": "sha512-XxpJfVzFh+jilRxIXUqcfYAYcunIc/XEzIizsOL1fcJee5Sf7H3mH8WlLmfHfluz5amqR88QQo9izKtmMlavAw==", "cpu": [ "x64" ], @@ -594,9 +558,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", - "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.9.tgz", + "integrity": "sha512-kSfvhmgeWyfkbT3p/1s5vSgboogoah2zkm9fX2zjg2hHxSV7T4KhMWRUUaRk4OXNqoD3QAUeRqLcs1aZOK4U1g==", "cpu": [ "x64" ], @@ -611,9 +575,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", - "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.9.tgz", + "integrity": "sha512-1RVzG17pxqbTfYLC352JlLt6kKLG+6Hr30n8DlIJqsnV5luUDd2Qdx9Ayw1Cabfyb1K9k0jXEZ7evxkRoT+uiw==", "cpu": [ "arm" ], @@ -628,9 +592,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", - "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.9.tgz", + "integrity": "sha512-BXqPvZ2drqVD+/Z8UpKwcs4Mp7grM+eGFku4CAEKrEtcbAsUpzREphK1sogCRZGreVPiMkiiBtw0n3TPteuqvw==", "cpu": [ "arm64" ], @@ -648,9 +612,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", - "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.9.tgz", + "integrity": "sha512-11vWvo8YDwLzukt27J3aYDWU+gg2P7J+ZOmiJ0hkF5BXZDW7pVya7r40MXDy6ya0i9KamoENSVKIugvJNgFXIA==", "cpu": [ "arm64" ], @@ -668,9 +632,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", - "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.9.tgz", + "integrity": "sha512-a1tijMkdwsIARtc0F39ApURROkf3NwqinI6TOiSSWCTR7dT96dffNvMUtDHnq64wKNTIZOIlzKrFvvFUznJiyw==", "cpu": [ "ppc64" ], @@ -688,9 +652,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", - "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.9.tgz", + "integrity": "sha512-x6SQNdAvv4c3hWqTMaWuawzMX9myaCs/yEmlGsxJzkdClnHW7FbrjQuSiRDhuSYzEYoEMhsaJy9qHG/XNemJPQ==", "cpu": [ "s390x" ], @@ -708,9 +672,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", - "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.9.tgz", + "integrity": "sha512-9s0AZ8BFK5/n7B/TBoa2yJE3gI3KURrbXcPBlsAsvjU4VeJKgE90y1YtNxyEUIcHPQkg6/yfF3qihUrcM/Kf0Q==", "cpu": [ "x64" ], @@ -728,9 +692,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", - "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.9.tgz", + "integrity": "sha512-P7VWAmV+WdJluH7ovnRGoiv2i8To7GAZ+kGzfGup635cyL7SyYl3lSUaA3Gp5THf0n/Co5EyEqb2zbqq+nMOHQ==", "cpu": [ "x64" ], @@ -748,9 +712,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", - "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.9.tgz", + "integrity": "sha512-1qixtsE4BK8h+yS3BfmZ09UhA7O/N4IACva6YBr7EBvCJraByTuRcgOTaiA62Tm0vey3UcKXLOaoGHtYmNGEVg==", "cpu": [ "arm64" ], @@ -764,29 +728,10 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", - "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.11.1", - "@emnapi/runtime": "1.11.1", - "@napi-rs/wasm-runtime": "^1.1.6" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", - "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.9.tgz", + "integrity": "sha512-ok8IQjcEPs1AKZfuEUznVBrJw+gK4soq+bx8b1X2XoMqVClarc1q5JDmVtWXY1xfr6ZuHTAsPXHTgTrqKTZeww==", "cpu": [ "arm64" ], @@ -801,9 +746,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", - "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.9.tgz", + "integrity": "sha512-Ip2mXoU0hM0boq3Rf+ekuT653OROSo6aSYcPT1VHE4q52KvyxgFkQgrgb/IEsxOuvQ2fZZbs8khJAyCEPM24/g==", "cpu": [ "x64" ], @@ -880,17 +825,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", - "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@types/chai": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", @@ -1208,14 +1142,14 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.10.tgz", - "integrity": "sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.11.tgz", + "integrity": "sha512-8MVGEFnJIcdGjcbfKmeq8z0pZHH0JlVtoVZH9Q/qwUp6wyFnEJUBMrw9DCaj+ra3vShGmhavjalMIhPNxZAUcw==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.10", + "@vitest/utils": "4.1.11", "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", @@ -1229,8 +1163,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.1.10", - "vitest": "4.1.10" + "@vitest/browser": "4.1.11", + "vitest": "4.1.11" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -1239,16 +1173,16 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", - "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz", + "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.10", - "@vitest/utils": "4.1.10", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -1257,13 +1191,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", - "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz", + "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.10", + "@vitest/spy": "4.1.11", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -1284,9 +1218,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", - "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz", + "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==", "dev": true, "license": "MIT", "dependencies": { @@ -1297,13 +1231,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", - "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz", + "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.10", + "@vitest/utils": "4.1.11", "pathe": "^2.0.3" }, "funding": { @@ -1311,14 +1245,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", - "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz", + "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.10", - "@vitest/utils": "4.1.10", + "@vitest/pretty-format": "4.1.11", + "@vitest/utils": "4.1.11", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -1327,9 +1261,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", - "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz", + "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==", "dev": true, "license": "MIT", "funding": { @@ -1337,13 +1271,13 @@ } }, "node_modules/@vitest/utils": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", - "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz", + "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.10", + "@vitest/pretty-format": "4.1.11", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -1691,9 +1625,9 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz", - "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2376,9 +2310,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", - "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", + "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", "dev": true, "funding": [ { @@ -3491,9 +3425,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.16", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", - "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", "dev": true, "funding": [ { @@ -3553,9 +3487,9 @@ } }, "node_modules/obug": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", - "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.2.1.tgz", + "integrity": "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==", "dev": true, "funding": [ "https://github.com/sponsors/sxzz", @@ -3685,9 +3619,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", - "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", "engines": { @@ -3774,9 +3708,9 @@ "license": "MIT" }, "node_modules/postcss": { - "version": "8.5.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz", - "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==", + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", "dev": true, "funding": [ { @@ -3794,7 +3728,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.16", + "nanoid": "^3.3.18", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -3980,13 +3914,13 @@ } }, "node_modules/rolldown": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", - "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.9.tgz", + "integrity": "sha512-hx/Pv0N1haXRb11qkfnK5MXB/iqr7i0yjWQqmO9uHqZpBgQSqzc8UsSnEpalsh+j1I8qQ2CkXAkJC8Br3dKSlg==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.139.0", + "@oxc-project/types": "=0.150.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -3996,21 +3930,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.1.5", - "@rolldown/binding-darwin-arm64": "1.1.5", - "@rolldown/binding-darwin-x64": "1.1.5", - "@rolldown/binding-freebsd-x64": "1.1.5", - "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", - "@rolldown/binding-linux-arm64-gnu": "1.1.5", - "@rolldown/binding-linux-arm64-musl": "1.1.5", - "@rolldown/binding-linux-ppc64-gnu": "1.1.5", - "@rolldown/binding-linux-s390x-gnu": "1.1.5", - "@rolldown/binding-linux-x64-gnu": "1.1.5", - "@rolldown/binding-linux-x64-musl": "1.1.5", - "@rolldown/binding-openharmony-arm64": "1.1.5", - "@rolldown/binding-wasm32-wasi": "1.1.5", - "@rolldown/binding-win32-arm64-msvc": "1.1.5", - "@rolldown/binding-win32-x64-msvc": "1.1.5" + "@rolldown/binding-android-arm-eabi": "1.2.9", + "@rolldown/binding-android-arm64": "1.2.9", + "@rolldown/binding-darwin-arm64": "1.2.9", + "@rolldown/binding-darwin-x64": "1.2.9", + "@rolldown/binding-freebsd-x64": "1.2.9", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.9", + "@rolldown/binding-linux-arm64-gnu": "1.2.9", + "@rolldown/binding-linux-arm64-musl": "1.2.9", + "@rolldown/binding-linux-ppc64-gnu": "1.2.9", + "@rolldown/binding-linux-s390x-gnu": "1.2.9", + "@rolldown/binding-linux-x64-gnu": "1.2.9", + "@rolldown/binding-linux-x64-musl": "1.2.9", + "@rolldown/binding-openharmony-arm64": "1.2.9", + "@rolldown/binding-win32-arm64-msvc": "1.2.9", + "@rolldown/binding-win32-x64-msvc": "1.2.9" } }, "node_modules/schema-utils": { @@ -4320,9 +4254,9 @@ } }, "node_modules/tinyrainbow": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", + "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", "dev": true, "license": "MIT", "engines": { @@ -4342,14 +4276,6 @@ "typescript": ">=4.8.4" } }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, "node_modules/type": { "version": "2.7.3", "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", @@ -4519,16 +4445,16 @@ } }, "node_modules/vite": { - "version": "8.1.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz", - "integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.0.tgz", + "integrity": "sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==", "dev": true, "license": "MIT", "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.5", - "postcss": "^8.5.17", - "rolldown": "~1.1.5", + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.28", + "rolldown": "~1.2.6", "tinyglobby": "^0.2.17" }, "bin": { @@ -4545,7 +4471,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.3.0", + "@vitejs/devtools": "^0.7.1", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -4597,19 +4523,19 @@ } }, "node_modules/vitest": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", - "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.11.tgz", + "integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.10", - "@vitest/mocker": "4.1.10", - "@vitest/pretty-format": "4.1.10", - "@vitest/runner": "4.1.10", - "@vitest/snapshot": "4.1.10", - "@vitest/spy": "4.1.10", - "@vitest/utils": "4.1.10", + "@vitest/expect": "4.1.11", + "@vitest/mocker": "4.1.11", + "@vitest/pretty-format": "4.1.11", + "@vitest/runner": "4.1.11", + "@vitest/snapshot": "4.1.11", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -4637,12 +4563,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.10", - "@vitest/browser-preview": "4.1.10", - "@vitest/browser-webdriverio": "4.1.10", - "@vitest/coverage-istanbul": "4.1.10", - "@vitest/coverage-v8": "4.1.10", - "@vitest/ui": "4.1.10", + "@vitest/browser-playwright": "4.1.11", + "@vitest/browser-preview": "4.1.11", + "@vitest/browser-webdriverio": "4.1.11", + "@vitest/coverage-istanbul": "4.1.11", + "@vitest/coverage-v8": "4.1.11", + "@vitest/ui": "4.1.11", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" diff --git a/package.json b/package.json index cf8d21ff..851c910d 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "devDependencies": { "@eslint/js": "^10.0.1", "@types/node": "^24.0.0", - "@vitest/coverage-v8": "^4.1.10", + "@vitest/coverage-v8": "^4.1.11", "eslint": "^10.8.0", "eslint-config-prettier": "^10.1.8", "fetch-mock": "^12.6.0", @@ -65,7 +65,7 @@ "typedoc": "^0.28.20", "typescript": "~5.8.3", "typescript-eslint": "^8.65.0", - "vitest": "^4.1.10", + "vitest": "^4.1.11", "webpack": "^5.109.1", "webpack-cli": "^7.2.1" },