Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,35 @@ const [finalized, metadata] = await client.loadDataset({

```

### ERA5 land datasets

ERA5 and ERA5-Land datasets are separate dataset IDs within the ECMWF ERA5 collection. Use `listAvailableDatasets()` to inspect the exact names before loading.

```typescript
// Non-land ERA5 total precipitation
const [precipitation, precipitationMetadata] = await client.loadDataset({
request: {
organization: "ecmwf",
collection: "era5",
dataset: "precipitation_total",
variant: "finalized"
}
});

// ERA5-Land total precipitation
const [landPrecipitation, landPrecipitationMetadata] = await client.loadDataset({
request: {
organization: "ecmwf",
collection: "era5",
dataset: "precipitation_total_land",
variant: "finalized"
}
});

// ERA5-Land wind datasets follow the same pattern:
// dataset: "wind_u_10m_land" or dataset: "wind_v_10m_land"
```

### Selecting while loading

```typescript
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@dclimate/dclimate-client-js",
"version": "0.5.8",
"version": "0.5.9",
"description": "JavaScript client for dClimate datasets using jaxray and IPFS stores",
"type": "module",
"main": "./dist/node/index.js",
Expand Down
35 changes: 32 additions & 3 deletions src/stac/stac-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,33 @@ export interface ResolvedCidFromServer {
variant: string;
}

function datasetIdFromItemId(
itemId: string,
collection: string
): string | undefined {
const prefix = `${collection}-`;
const remainder = itemId.startsWith(prefix) ? itemId.slice(prefix.length) : itemId;
const [dataset] = remainder.split("-");
return dataset || undefined;
}

function featureMatchesDataset(
feature: StacServerItem,
collection: string,
dataset: string
): boolean {
if (feature.collection && feature.collection !== collection) {
return false;
}

const datasetId = getStringProperty(feature.properties, "dclimate:dataset_id");
if (datasetId) {
return datasetId === dataset;
}

return datasetIdFromItemId(feature.id, collection) === dataset;
}

/**
* Resolve dataset CID via STAC server /search API.
*
Expand Down Expand Up @@ -75,9 +102,11 @@ export async function resolveCidFromStacServer(
const data: StacServerSearchResponse = await response.json();
const features = data.features || [];

// Filter to matching dataset (item ID pattern: {collection}-{dataset}-{variant})
const prefix = `${collection}-${dataset}`;
const matches = features.filter((f) => f.id.startsWith(prefix));
// Filter to the exact dataset. A prefix match would conflate datasets such
// as precipitation_total and precipitation_total_land.
const matches = features.filter((f) =>
featureMatchesDataset(f, collection, dataset)
);

if (matches.length === 0) {
throw new Error(`No items found for ${collection}/${dataset}`);
Expand Down
132 changes: 131 additions & 1 deletion tests/stac-server.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, expect, it, beforeAll } from "vitest";
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import {
resolveCidFromStacServer,
resolveDatasetCidFromStacServer,
Expand Down Expand Up @@ -79,6 +79,136 @@ describe("STAC Server", () => {
});
});

describe("exact dataset matching", () => {
afterEach(() => {
vi.unstubAllGlobals();
});

function mockSearchResponse(features: unknown[]) {
vi.stubGlobal(
"fetch",
vi.fn(async () => ({
ok: true,
json: async () => ({
type: "FeatureCollection",
features,
}),
text: async () => "",
}))
);
}

it("does not resolve a base ERA5 dataset to a *_land prefix collision", async () => {
mockSearchResponse([
{
type: "Feature",
id: "ecmwf_era5-precipitation_total_land-finalized",
collection: "ecmwf_era5",
properties: {
"dclimate:dataset_id": "precipitation_total_land",
"dclimate:variant": "finalized",
},
assets: {
data: {
href: "ipfs://bafy-era5-land-precip-finalized",
},
},
},
{
type: "Feature",
id: "ecmwf_era5-precipitation_total-finalized",
collection: "ecmwf_era5",
properties: {
"dclimate:dataset_id": "precipitation_total",
"dclimate:variant": "finalized",
},
assets: {
data: {
href: "ipfs://bafy-era5-precip-finalized",
},
},
},
]);

const result = await resolveCidFromStacServer(
"ecmwf_era5",
"precipitation_total",
"finalized",
"https://example.test"
);

expect(result.cid).toBe("bafy-era5-precip-finalized");
});

it("rejects a request when only a prefix-related land dataset exists", async () => {
mockSearchResponse([
{
type: "Feature",
id: "ecmwf_era5-wind_u_10m_land-finalized",
collection: "ecmwf_era5",
properties: {
"dclimate:dataset_id": "wind_u_10m_land",
"dclimate:variant": "finalized",
},
assets: {
data: {
href: "ipfs://bafy-era5-land-wind-u",
},
},
},
]);

await expect(
resolveCidFromStacServer(
"ecmwf_era5",
"wind_u_10m",
"finalized",
"https://example.test"
)
).rejects.toThrow(/No items found/);
});

it("keeps legacy item-id fallback exact", async () => {
mockSearchResponse([
{
type: "Feature",
id: "ecmwf_era5-temperature_2m_land-finalized",
collection: "ecmwf_era5",
properties: {
"dclimate:variant": "finalized",
},
assets: {
data: {
href: "ipfs://bafy-era5-land-t2m",
},
},
},
{
type: "Feature",
id: "ecmwf_era5-temperature_2m-finalized",
collection: "ecmwf_era5",
properties: {
"dclimate:variant": "finalized",
},
assets: {
data: {
href: "ipfs://bafy-era5-t2m",
},
},
},
]);

const result = await resolveCidFromStacServer(
"ecmwf_era5",
"temperature_2m",
"finalized",
"https://example.test"
);

expect(result.cid).toBe("bafy-era5-t2m");
});
});

describe("resolveCidFromStacServer", () => {
it("returns CID as string without ipfs:// prefix", async () => {
if (!serverAvailable || !availableDataset) {
Expand Down
Loading