diff --git a/README.md b/README.md index 7289ed4..413c6d7 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,55 @@ const slice = await point.timeRange({ console.log(await slice.toRecords("precipitation")); ``` +### Station data usage + +Gridded Zarr datasets come from `loadDataset`. Point-observation **station** +datasets (GHCND and friends) live under `client.stations`, and read the same way: +degrees, ISO timestamps, chained selections. + +```typescript +const stations = await client.stations.load({ cid: "bafyr4i..." }); + +// Every station, with position and coverage window. +for (const s of stations.stations) { + console.log(s.stationId, s.latitude, s.longitude, s.start, s.end); +} + +// Stations within 50 km of a point, over one week. +const records = await stations + .circle(40.75, -73.99, 50) + .timeRange({ start: "2023-01-01", end: "2023-01-07" }) + .toRecords("TMAX"); +``` + +Selections return new instances, so a partial selection can be branched: + +```typescript +const week = stations.timeRange({ start: "2023-01-01", end: "2023-01-07" }); +const nyc = await week.select("USW00094728").rows(); +const lax = await week.select("USW00023174").rows(); +``` + +Two things differ from `GeoTemporalDataset`, because the data model differs: + +- **`nearest(lat, lon, { maxKm })` instead of `point()`.** A grid always has a + cell under any coordinate; stations are irregular, so the nearest one may be + far away. Pass `maxKm` to make that a hard bound rather than a surprise. +- **`where(...)` has no gridded counterpart.** Row-level predicates are pushed + down to fragment statistics, so most fragments are skipped without being read: + +```typescript +const hotDays = await stations + .nearest(29.98, -95.36) + .timeRange({ start: "2025-01-01", end: "2025-12-31" }) + .where({ element: "TMAX", op: "gt", value: 350 }) // tenths of °C, so 35 °C + .rows(); +``` + +Reads go over the IPFS HTTP gateway, so no local daemon is required and the same +code runs in a browser. Resolution is by CID for now; STAC catalog support will +follow. + ### Siren REST API usage Use Siren methods by configuring `siren` in the client options. diff --git a/package-lock.json b/package-lock.json index cae5a03..dacbe98 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,10 @@ "version": "0.6.0", "license": "MIT", "dependencies": { + "@dclimate/dparquet": "file:../dparquet", "@dclimate/jaxray": "^0.7.0", - "@opentelemetry/api": "^1.9.1" + "@opentelemetry/api": "^1.9.1", + "multiformats": "^14.0.4" }, "devDependencies": { "@eslint/js": "10.0.1", @@ -21,6 +23,36 @@ "vitest": "^4.1.0" } }, + "../dparquet": { + "name": "@dclimate/dparquet", + "version": "0.0.0", + "dependencies": { + "@dclimate/ipld-index": "file:../ipld-index", + "@helia/unixfs": "8.0.3", + "@ipld/dag-cbor": "^10.0.1", + "@noble/hashes": "^2.0.1", + "helia": "7.0.5", + "hyparquet": "^1.26.2", + "hyparquet-compressors": "^1.1.1", + "hyparquet-writer": "^0.16.1", + "multiformats": "^14.0.4", + "zod": "^4.1.0" + }, + "devDependencies": { + "@types/node": "^22.15.0", + "kubo-rpc-client": "7.1.0", + "oxlint": "1.72.0", + "typescript": "^5.9.0", + "vitest": "^3.2.0" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@dclimate/dparquet": { + "resolved": "../dparquet", + "link": true + }, "node_modules/@dclimate/jaxray": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/@dclimate/jaxray/-/jaxray-0.7.0.tgz", @@ -37,6 +69,12 @@ "zarrita": "^0.5.4" } }, + "node_modules/@dclimate/jaxray/node_modules/multiformats": { + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", + "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", + "license": "Apache-2.0 OR MIT" + }, "node_modules/@emnapi/core": { "version": "1.11.2", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.2.tgz", @@ -282,6 +320,12 @@ "npm": ">=7.0.0" } }, + "node_modules/@ipld/dag-cbor/node_modules/multiformats": { + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", + "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", + "license": "Apache-2.0 OR MIT" + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -1969,9 +2013,9 @@ "license": "MIT" }, "node_modules/multiformats": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", - "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", + "version": "14.0.5", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.5.tgz", + "integrity": "sha512-vbIm83F2yZ1pWJGS0yl0ysracIvv56LtbrIyiIQHoLdYDJOMoLfVFsXhh9DUH4SFdkdkFhucyWniihsNzVEjkQ==", "license": "Apache-2.0 OR MIT" }, "node_modules/nanoid": { @@ -2428,6 +2472,12 @@ "multiformats": "^13.0.0" } }, + "node_modules/uint8arrays/node_modules/multiformats": { + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", + "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", + "license": "Apache-2.0 OR MIT" + }, "node_modules/unzipit": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/unzipit/-/unzipit-1.4.3.tgz", diff --git a/package.json b/package.json index 77b4778..0b752ac 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,8 @@ "test:ci": "vitest run", "test:coverage": "vitest run --coverage", "prepare": "npm run build", - "prepublishOnly": "npm run build" + "prepublishOnly": "npm run build", + "inspect:stations": "tsx scripts/inspect-stations.ts" }, "keywords": [ "dclimate", @@ -52,7 +53,9 @@ "homepage": "https://github.com/dClimate/dclimate-client-js#readme", "dependencies": { "@dclimate/jaxray": "^0.7.0", - "@opentelemetry/api": "^1.9.1" + "@opentelemetry/api": "^1.9.1", + "@dclimate/dparquet": "file:../dparquet", + "multiformats": "^14.0.4" }, "devDependencies": { "@eslint/js": "10.0.1", diff --git a/scripts/inspect-stations.ts b/scripts/inspect-stations.ts new file mode 100644 index 0000000..d57fa24 --- /dev/null +++ b/scripts/inspect-stations.ts @@ -0,0 +1,218 @@ +/** + * Load and inspect a station dataset through `client.stations`. + * + * The station counterpart to `inspect-dataset.ts`: that one walks a gridded + * Zarr dataset, this one walks point observations. Both go through the same + * client, which is the thing worth seeing -- station data is not a separate + * SDK, just a second namespace. + * + * Usage: + * npx tsx scripts/inspect-stations.ts + * npx tsx scripts/inspect-stations.ts --station USW00094728 --element TMAX + * npx tsx scripts/inspect-stations.ts --near 40.78,-73.97 --from 2025-07-01 --to 2025-07-08 + * + * Options: + * --gateway IPFS HTTP gateway (default http://127.0.0.1:8080) + * --station Restrict to one station id (repeatable) + * --near Use the single nearest station to a point instead + * --element Restrict to one element, e.g. TMAX, TMIN, PRCP (repeatable) + * --from ISO date, inclusive + * --to ISO date, inclusive + * --limit Rows to print (default 10; 0 prints all) + * --plan Show what would be fetched, then stop + * + * Requires an IPFS gateway that can serve the dataset's blocks. A local Kubo + * daemon (`ipfs daemon`) is the usual answer; any gateway works. + */ + +import { DClimateClient } from "../src/index.js"; +import type { StationDataset, StationInfo } from "@dclimate/dparquet/reader"; + +const DEFAULT_GATEWAY = "http://127.0.0.1:8080"; + +interface Args { + cid: string; + gateway: string; + stations: string[]; + near: [number, number] | null; + elements: string[]; + from: string | null; + to: string | null; + limit: number; + plan: boolean; +} + +const USAGE = `Usage: + npx tsx scripts/inspect-stations.ts [options] + +Options: + --gateway IPFS HTTP gateway (default ${DEFAULT_GATEWAY}) + --station Restrict to one station id (repeatable) + --near Use the single nearest station to a point + --element Restrict to one element, e.g. TMAX (repeatable) + --from ISO date, inclusive + --to ISO date, inclusive + --limit Rows to print (default 10; 0 prints all) + --plan Show what would be fetched, then stop`; + +function parseArgs(argv: string[]): Args { + const args: Args = { + cid: "", + gateway: process.env.IPFS_GATEWAY_URL ?? DEFAULT_GATEWAY, + stations: [], + near: null, + elements: [], + from: null, + to: null, + limit: 10, + plan: false, + }; + + for (let i = 0; i < argv.length; i += 1) { + const arg = argv[i]!; + // A flag whose value is missing is a typo, not a request for the default: + // silently falling back would produce a plausible-looking wrong answer. + const value = (): string => { + const next = argv[i + 1]; + if (next === undefined || next.startsWith("--")) { + throw new Error(`${arg} needs a value`); + } + i += 1; + return next; + }; + + switch (arg) { + case "--gateway": args.gateway = value(); break; + case "--station": args.stations.push(value()); break; + case "--element": args.elements.push(value().toUpperCase()); break; + case "--from": args.from = value(); break; + case "--to": args.to = value(); break; + case "--limit": args.limit = Number(value()); break; + case "--plan": args.plan = true; break; + case "--near": { + const parts = value().split(","); + const lat = Number(parts[0]); + const lon = Number(parts[1]); + if (parts.length !== 2 || !Number.isFinite(lat) || !Number.isFinite(lon)) { + throw new Error("--near expects , e.g. --near 40.78,-73.97"); + } + args.near = [lat, lon]; + break; + } + default: + if (arg.startsWith("--")) throw new Error(`Unknown option: ${arg}`); + if (args.cid) throw new Error(`Unexpected argument: ${arg}`); + args.cid = arg; + } + } + + if (!args.cid) throw new Error("A root CID is required"); + if (!Number.isFinite(args.limit) || args.limit < 0) { + throw new Error("--limit must be a non-negative number"); + } + return args; +} + +const day = (date: Date): string => date.toISOString().slice(0, 10); + +function describeStation(station: StationInfo): string { + const where = + station.latitude === null || station.longitude === null + ? "no position" + : `${station.latitude.toFixed(4)}, ${station.longitude.toFixed(4)}`; + return `${station.stationId} ${where} ${day(station.start)} .. ${day(station.end)}`; +} + +function applySelection(dataset: StationDataset, args: Args): StationDataset { + let selected = dataset; + + if (args.near) { + const [lat, lon] = args.near; + selected = selected.nearest(lat, lon); + console.log(`\nNearest station to ${lat}, ${lon}: ${selected.toQuery().stations?.[0]}`); + } else if (args.stations.length > 0) { + selected = selected.select(...args.stations); + } + + if (args.elements.length > 0) selected = selected.elements(...args.elements); + + if (args.from || args.to) { + // Either bound alone is meaningful, so the missing side widens to the + // dataset's own extent rather than forcing the caller to pass both. + const covered = dataset.stations; + const earliest = Math.min(...covered.map((s) => s.start.getTime())); + const latest = Math.max(...covered.map((s) => s.end.getTime())); + selected = selected.timeRange({ + start: args.from ?? new Date(earliest), + end: args.to ?? new Date(latest), + }); + } + + return selected; +} + +async function main(): Promise { + const argv = process.argv.slice(2); + if (argv.length === 0 || argv.includes("--help") || argv.includes("-h")) { + console.log(USAGE); + process.exit(argv.length === 0 ? 1 : 0); + } + + const args = parseArgs(argv); + const client = new DClimateClient({ gatewayUrl: args.gateway, stacServerUrl: null }); + + console.log(`Loading station dataset ${args.cid}`); + console.log(`Gateway: ${args.gateway}\n`); + + const dataset = await client.stations.load({ cid: args.cid }); + + console.log(`Stations (${dataset.stations.length}):`); + for (const station of dataset.stations) console.log(` ${describeStation(station)}`); + + const selected = applySelection(dataset, args); + console.log(`\nQuery (wire units): ${JSON.stringify(selected.toQuery())}`); + + const plan = await selected.plan(); + const bytes = plan.fragments.reduce((sum, f) => sum + f.byteLength, 0); + const rows = plan.fragments.reduce((sum, f) => sum + f.rowCount, 0); + console.log( + `Plan: ${plan.fragments.length} fragment(s), ` + + `${plan.stations.length} station(s), ` + + `${rows} row(s), ${(bytes / 1024).toFixed(1)} KiB` + ); + // The number that shows predicate pushdown doing something: fragments ruled + // out by column statistics are never fetched at all. + console.log(` ${plan.stats.fragmentsPruned} fragment(s) pruned by statistics`); + + if (args.plan) return; + + const started = Date.now(); + const element = args.elements.length === 1 ? args.elements[0] : undefined; + const records = await selected.toRecords(element); + const elapsed = Date.now() - started; + + console.log(`\n${records.length} record(s) in ${elapsed} ms`); + + const shown = args.limit === 0 ? records : records.slice(0, args.limit); + for (const record of shown) { + const value = + element === undefined + ? JSON.stringify(record.values) + : String(record.value ?? "—"); + console.log(` ${day(record.time as Date)} ${record.stationId} ${value}`); + } + if (shown.length < records.length) { + console.log(` ... ${records.length - shown.length} more (--limit 0 for all)`); + } + + // Stored in NOAA's own scaling rather than converted, so the archive's exact + // integers survive. Saying so beats letting a reader assume whole °C. + if (element) { + console.log(`\nValues are integers in tenths (TMAX 317 = 31.7 °C).`); + } +} + +main().catch((error: unknown) => { + console.error(`\nError: ${error instanceof Error ? error.message : String(error)}`); + process.exit(1); +}); diff --git a/src/client.ts b/src/client.ts index 45c1ad8..f0b37d4 100644 --- a/src/client.ts +++ b/src/client.ts @@ -28,6 +28,7 @@ import { DEFAULT_STAC_SERVER_URL, } from "./stac/stac-server.js"; import { SirenClient } from "./siren/siren-client.js"; +import { StationsClient } from "./stations/stations-client.js"; function normalizeZarrGroup(group?: string): string | undefined { const normalized = group?.replace(/^\/+/, "").replace(/\/+$/, ""); @@ -45,6 +46,7 @@ export class DClimateClient { private stacCatalogTimestamp?: number; private stacCacheTtl: number = 3600000; // 1 hour private sirenClient?: SirenClient; + private stationsClient?: StationsClient; constructor(options: ClientOptions = {}) { this.gatewayUrl = options.gatewayUrl ?? DEFAULT_IPFS_GATEWAY; @@ -104,6 +106,21 @@ export class DClimateClient { return this.sirenClient; } + /** + * Access station (point-observation) datasets: + * `client.stations.load({ cid })` + * + * Unlike `client.siren`, this needs no configuration -- it reads over the + * client's own IPFS gateway -- so it is constructed on first use rather than + * requiring an option and throwing when absent. + */ + get stations(): StationsClient { + if (!this.stationsClient) { + this.stationsClient = new StationsClient({ gatewayUrl: this.gatewayUrl }); + } + return this.stationsClient; + } + async loadDataset({ request, options = { diff --git a/src/index.ts b/src/index.ts index 728e0bf..95ab80f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -40,3 +40,13 @@ export { type SirenRegion, type SirenRegionsResponse, } from "./siren/index.js"; +export { + StationsClient, + type LoadStationsRequest, + type StationsClientOptions, + type NearestOptions, + type StationDataset, + type StationInfo, + type TimeInput, + type TimeRangeInput, +} from "./stations/index.js"; diff --git a/src/stations/index.ts b/src/stations/index.ts new file mode 100644 index 0000000..9ddc248 --- /dev/null +++ b/src/stations/index.ts @@ -0,0 +1,12 @@ +export { + StationsClient, + type LoadStationsRequest, + type StationsClientOptions, +} from "./stations-client.js"; +export type { + NearestOptions, + StationDataset, + StationInfo, + TimeInput, + TimeRangeInput, +} from "@dclimate/dparquet/reader"; diff --git a/src/stations/stations-client.ts b/src/stations/stations-client.ts new file mode 100644 index 0000000..168236e --- /dev/null +++ b/src/stations/stations-client.ts @@ -0,0 +1,64 @@ +import { CID } from "multiformats/cid"; +import { + GatewayRangeSource, + StationDataset, +} from "@dclimate/dparquet/reader"; +import { DatasetNotFoundError } from "../errors.js"; + +/** + * Station (point-observation) datasets, as opposed to the gridded Zarr datasets + * `loadDataset` serves. + * + * The two are different enough underneath -- irregular stations with per-station + * time coverage, versus a regular lat/lon/time grid -- that sharing one loader + * would help nobody. What they do share is how a caller wants to *ask*: degrees, + * ISO timestamps, chained selections. `StationDataset` provides that surface, so + * this namespace stays thin: resolve a root, hand back the dataset. + * + * Resolution is by CID only for now. There is no STAC equivalent for station + * data yet; when there is, `load` grows a `{ collection, dataset }` form + * alongside the CID and the rest of this file is unaffected. + */ +export interface StationsClientOptions { + gatewayUrl: string; + fetch?: typeof globalThis.fetch; +} + +export interface LoadStationsRequest { + /** Root CID of a published station dataset. */ + cid: string; + /** Override the client's gateway for this dataset only. */ + gatewayUrl?: string; +} + +export class StationsClient { + constructor(private readonly options: StationsClientOptions) {} + + /** + * Open a station dataset by root CID. + * + * Reads go through the IPFS HTTP gateway, so this needs no local daemon and + * works unchanged in a browser. + */ + async load(request: LoadStationsRequest): Promise { + if (!request.cid) { + throw new DatasetNotFoundError( + "A station dataset CID is required. Catalog resolution is not available yet." + ); + } + + let root: CID; + try { + root = CID.parse(request.cid); + } catch (cause) { + const detail = cause instanceof Error ? `: ${cause.message}` : ""; + throw new DatasetNotFoundError(`Not a valid CID: ${request.cid}${detail}`); + } + + const source = new GatewayRangeSource({ + gatewayUrl: request.gatewayUrl ?? this.options.gatewayUrl, + ...(this.options.fetch ? { fetch: this.options.fetch } : {}), + }); + return StationDataset.open(source, root); + } +} diff --git a/tests/stations.test.ts b/tests/stations.test.ts new file mode 100644 index 0000000..14258a2 --- /dev/null +++ b/tests/stations.test.ts @@ -0,0 +1,82 @@ +import { describe, expect, it } from "vitest"; +import { DClimateClient } from "../src/index.js"; +import { StationsClient } from "../src/stations/stations-client.js"; +import { DatasetNotFoundError } from "../src/errors.js"; + +const REAL_CID = + "bafyr4igxoe2toq5t3afp4embl3p2v4rgdfndymlhjiuizca3zwlvrmhcam"; + +describe("client.stations", () => { + it("is available without configuration, unlike siren", () => { + // Siren needs credentials and throws when unconfigured; station reads only + // need the gateway the client already has, so requiring an option would be + // ceremony with nothing behind it. + const client = new DClimateClient({ gatewayUrl: "http://127.0.0.1:8080" }); + expect(client.stations).toBeInstanceOf(StationsClient); + }); + + it("reuses one instance across accesses", () => { + const client = new DClimateClient({ gatewayUrl: "http://127.0.0.1:8080" }); + expect(client.stations).toBe(client.stations); + }); + + it("rejects a malformed CID before touching the network", async () => { + const client = new DClimateClient({ gatewayUrl: "http://127.0.0.1:8080" }); + let fetched = 0; + const stations = new StationsClient({ + gatewayUrl: "http://127.0.0.1:8080", + fetch: (async () => { + fetched += 1; + return new Response(null, { status: 500 }); + }) as typeof fetch, + }); + + await expect(stations.load({ cid: "obviously-not-a-cid" })).rejects.toThrow( + DatasetNotFoundError + ); + // The point of the assertion: a typo'd CID should fail locally and + // instantly, not after a gateway round trip. + expect(fetched).toBe(0); + expect(client.stations).toBeDefined(); + }); + + it("requires a CID until catalog resolution exists", async () => { + const client = new DClimateClient({ gatewayUrl: "http://127.0.0.1:8080" }); + await expect( + client.stations.load({ cid: "" }) + ).rejects.toThrow(DatasetNotFoundError); + }); + + it("routes reads through the gateway it was given", async () => { + const requested: string[] = []; + const stations = new StationsClient({ + gatewayUrl: "https://gateway.example", + fetch: (async (input: string | URL | Request) => { + requested.push(String(input instanceof Request ? input.url : input)); + // A 404 ends the load; the assertion is about where it went, not + // whether the dataset resolves. + return new Response(null, { status: 404 }); + }) as typeof fetch, + }); + + await expect(stations.load({ cid: REAL_CID })).rejects.toThrow(); + expect(requested[0]).toContain("https://gateway.example/ipfs/"); + expect(requested[0]).toContain(REAL_CID); + }); + + it("lets a per-request gateway override the client default", async () => { + const requested: string[] = []; + const stations = new StationsClient({ + gatewayUrl: "https://default.example", + fetch: (async (input: string | URL | Request) => { + requested.push(String(input instanceof Request ? input.url : input)); + return new Response(null, { status: 404 }); + }) as typeof fetch, + }); + + await expect( + stations.load({ cid: REAL_CID, gatewayUrl: "https://override.example" }) + ).rejects.toThrow(); + expect(requested[0]).toContain("https://override.example/"); + }); +}); diff --git a/yarn.lock b/yarn.lock index 270b630..94e481d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,21 @@ # yarn lockfile v1 +"@dclimate/dparquet@file:../dparquet": + version "0.0.0" + resolved "file:../dparquet" + dependencies: + "@dclimate/ipld-index" "file:../ipld-index" + "@helia/unixfs" "8.0.3" + "@ipld/dag-cbor" "^10.0.1" + "@noble/hashes" "^2.0.1" + helia "7.0.5" + hyparquet "^1.26.2" + hyparquet-compressors "^1.1.1" + hyparquet-writer "^0.16.1" + multiformats "^14.0.4" + zod "^4.1.0" + "@dclimate/jaxray@^0.7.0": version "0.7.0" resolved "https://registry.npmjs.org/@dclimate/jaxray/-/jaxray-0.7.0.tgz" @@ -113,13 +128,6 @@ resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz" integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== -"@napi-rs/wasm-runtime@^1.1.1": - version "1.1.6" - resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz#ed33806d0f9be98dc76d0c3d4fd872fda701b5d5" - integrity sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg== - dependencies: - "@tybys/wasm-util" "^0.10.3" - "@noble/ciphers@^2.0.1": version "2.1.1" resolved "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.1.1.tgz" @@ -130,7 +138,7 @@ resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz" integrity sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw== -"@opentelemetry/api@^1.9.1": +"@opentelemetry/api@^1.9.0", "@opentelemetry/api@^1.9.1": version "1.9.1" resolved "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz" integrity sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q== @@ -145,83 +153,11 @@ resolved "https://registry.npmjs.org/@oxc-project/types/-/types-0.115.0.tgz" integrity sha512-4n91DKnebUS4yjUHl2g3/b2T+IUdCfmoZGhmwsovZCDaJSs+QkVAM+0AqqTxHSsHfeiMuueT75cZaZcT/m0pSw== -"@rolldown/binding-android-arm64@1.0.0-rc.9": - version "1.0.0-rc.9" - resolved "https://registry.yarnpkg.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.9.tgz#4bbd28868564948c2bf04b3ca117a6828f95626c" - integrity sha512-lcJL0bN5hpgJfSIz/8PIf02irmyL43P+j1pTCfbD1DbLkmGRuFIA4DD3B3ZOvGqG0XiVvRznbKtN0COQVaKUTg== - "@rolldown/binding-darwin-arm64@1.0.0-rc.9": version "1.0.0-rc.9" resolved "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.9.tgz" integrity sha512-J7Zk3kLYFsLtuH6U+F4pS2sYVzac0qkjcO5QxHS7OS7yZu2LRs+IXo+uvJ/mvpyUljDJ3LROZPoQfgBIpCMhdQ== -"@rolldown/binding-darwin-x64@1.0.0-rc.9": - version "1.0.0-rc.9" - resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.9.tgz#747b698878b6f44d817f87e9e3cb197b16076d2a" - integrity sha512-iwtmmghy8nhfRGeNAIltcNXzD0QMNaaA5U/NyZc1Ia4bxrzFByNMDoppoC+hl7cDiUq5/1CnFthpT9n+UtfFyg== - -"@rolldown/binding-freebsd-x64@1.0.0-rc.9": - version "1.0.0-rc.9" - resolved "https://registry.yarnpkg.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.9.tgz#35c29d7c83aa75429c74d7d1ee9c7d3e61f4552c" - integrity sha512-DLFYI78SCiZr5VvdEplsVC2Vx53lnA4/Ga5C65iyldMVaErr86aiqCoNBLl92PXPfDtUYjUh+xFFor40ueNs4Q== - -"@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.9": - version "1.0.0-rc.9" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.9.tgz#36d2bcbcf07f17f18fb2df727a62f16e5295c816" - integrity sha512-CsjTmTwd0Hri6iTw/DRMK7kOZ7FwAkrO4h8YWKoX/kcj833e4coqo2wzIFywtch/8Eb5enQ/lwLM7w6JX1W5RQ== - -"@rolldown/binding-linux-arm64-gnu@1.0.0-rc.9": - version "1.0.0-rc.9" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.9.tgz#5b03c11f2b661a275f2d7628e4f456783e1b9f63" - integrity sha512-2x9O2JbSPxpxMDhP9Z74mahAStibTlrBMW0520+epJH5sac7/LwZW5Bmg/E6CXuEF53JJFW509uP+lSedaUNxg== - -"@rolldown/binding-linux-arm64-musl@1.0.0-rc.9": - version "1.0.0-rc.9" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.9.tgz#d3cbd1b1760d34b5789af89f4bcc09a1446d3eb5" - integrity sha512-JA1QRW31ogheAIRhIg9tjMfsYbglXXYGNPLdPEYrwFxdbkQCAzvpSCSHCDWNl4hTtrol8WeboCSEpjdZK8qrCg== - -"@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.9": - version "1.0.0-rc.9" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.9.tgz#8e971e7f066b2c0876e20c9f6174d645f31efb84" - integrity sha512-aOKU9dJheda8Kj8Y3w9gnt9QFOO+qKPAl8SWd7JPHP+Cu0EuDAE5wokQubLzIDQWg2myXq2XhTpOVS07qqvT+w== - -"@rolldown/binding-linux-s390x-gnu@1.0.0-rc.9": - version "1.0.0-rc.9" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.9.tgz#e7283523780741f07a4441c7c8af5b2550faadf2" - integrity sha512-OalO94fqj7IWRn3VdXWty75jC5dk4C197AWEuMhIpvVv2lw9fiPhud0+bW2ctCxb3YoBZor71QHbY+9/WToadA== - -"@rolldown/binding-linux-x64-gnu@1.0.0-rc.9": - version "1.0.0-rc.9" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.9.tgz#da2302e079bb5f3a98edf75608621e94f1fb550e" - integrity sha512-cVEl1vZtBsBZna3YMjGXNvnYYrOJ7RzuWvZU0ffvJUexWkukMaDuGhUXn0rjnV0ptzGVkvc+vW9Yqy6h8YX4pg== - -"@rolldown/binding-linux-x64-musl@1.0.0-rc.9": - version "1.0.0-rc.9" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.9.tgz#3f27a620d56b93644fd1b6fad58fc2dbe93d5d71" - integrity sha512-UzYnKCIIc4heAKgI4PZ3dfBGUZefGCJ1TPDuLHoCzgrMYPb5Rv6TLFuYtyM4rWyHM7hymNdsg5ik2C+UD9VDbA== - -"@rolldown/binding-openharmony-arm64@1.0.0-rc.9": - version "1.0.0-rc.9" - resolved "https://registry.yarnpkg.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.9.tgz#9c307777157d029aaf8db1a09221b9275dbe5547" - integrity sha512-+6zoiF+RRyf5cdlFQP7nm58mq7+/2PFaY2DNQeD4B87N36JzfF/l9mdBkkmTvSYcYPE8tMh/o3cRlsx1ldLfog== - -"@rolldown/binding-wasm32-wasi@1.0.0-rc.9": - version "1.0.0-rc.9" - resolved "https://registry.yarnpkg.com/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.9.tgz#c3a82bef0ddd644efa74c050c26223f29f55039c" - integrity sha512-rgFN6sA/dyebil3YTlL2evvi/M+ivhfnyxec7AccTpRPccno/rPoNlqybEZQBkcbZu8Hy+eqNJCqfBR8P7Pg8g== - dependencies: - "@napi-rs/wasm-runtime" "^1.1.1" - -"@rolldown/binding-win32-arm64-msvc@1.0.0-rc.9": - version "1.0.0-rc.9" - resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.9.tgz#27e23cbd53b7095d0b66191ef999327b4684a6cf" - integrity sha512-lHVNUG/8nlF1IQk1C0Ci574qKYyty2goMiPlRqkC5R+3LkXDkL5Dhx8ytbxq35m+pkHVIvIxviD+TWLdfeuadA== - -"@rolldown/binding-win32-x64-msvc@1.0.0-rc.9": - version "1.0.0-rc.9" - resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.9.tgz#96046309142b398c9c2a9a0a052e7355535e69c8" - integrity sha512-G0oA4+w1iY5AGi5HcDTxWsoxF509hrFIPB2rduV5aDqS9FtDg1CAfa7V34qImbjfhIcA8C+RekocJZA96EarwQ== - "@rolldown/pluginutils@1.0.0-rc.9": version "1.0.0-rc.9" resolved "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.9.tgz" @@ -232,13 +168,6 @@ resolved "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz" integrity sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w== -"@tybys/wasm-util@^0.10.3": - version "0.10.3" - resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.10.3.tgz#015cba9e9dd47ce14d03d2a8c5d547bfb169665d" - integrity sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg== - dependencies: - tslib "^2.4.0" - "@types/chai@^5.2.2": version "5.2.3" resolved "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz" @@ -286,7 +215,7 @@ natural-compare "^1.4.0" ts-api-utils "^2.5.0" -"@typescript-eslint/parser@8.61.0": +"@typescript-eslint/parser@^8.61.0", "@typescript-eslint/parser@8.61.0": version "8.61.0" resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.61.0.tgz" integrity sha512-5B7PfA2e1NQGCnDHd/0lW7W3gvp3d59Ryw54FYO8Uswxo9f6ikw3AZV+Xj/TvpImmpsiYyUqAfhC6kJID1jF6w== @@ -314,7 +243,7 @@ "@typescript-eslint/types" "8.61.0" "@typescript-eslint/visitor-keys" "8.61.0" -"@typescript-eslint/tsconfig-utils@8.61.0", "@typescript-eslint/tsconfig-utils@^8.61.0": +"@typescript-eslint/tsconfig-utils@^8.61.0", "@typescript-eslint/tsconfig-utils@8.61.0": version "8.61.0" resolved "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.61.0.tgz" integrity sha512-O5Amvdv9ztMpxpf+vmFULGG78IE6Qwdr3bCGvqwG4nwc9H2qXkOYJJnRbRHyMkQTjv1d03olqwwwzHLMqpFePQ== @@ -330,7 +259,7 @@ debug "^4.4.3" ts-api-utils "^2.5.0" -"@typescript-eslint/types@8.61.0", "@typescript-eslint/types@^8.61.0": +"@typescript-eslint/types@^8.61.0", "@typescript-eslint/types@8.61.0": version "8.61.0" resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.61.0.tgz" integrity sha512-9QTQpZ5Iin4CdIodfbDQFSeiSJKidgYJYug1P9CC2xWgUTvlmixViqDZNciMjwLBZyJnG4tGmPl97rVAFb1AJg== @@ -441,7 +370,7 @@ acorn-jsx@^5.3.2: resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn@^8.16.0: +"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.16.0: version "8.16.0" resolved "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz" integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw== @@ -544,7 +473,7 @@ eslint-visitor-keys@^5.0.0, eslint-visitor-keys@^5.0.1: resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz" integrity sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA== -eslint@10.4.1: +eslint@^10.0.0, "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^8.57.0 || ^9.0.0 || ^10.0.0", eslint@10.4.1: version "10.4.1" resolved "https://registry.npmjs.org/eslint/-/eslint-10.4.1.tgz" integrity sha512-AyIKhnOBuOAdueD7RB3xB+YeAWScb9jHsJBgH2Hcde8InP5JYhqrRR6iTMHyTEwgENK54Cp44e4v8BwNhsuHuw== @@ -770,61 +699,11 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" -lightningcss-android-arm64@1.32.0: - version "1.32.0" - resolved "https://registry.yarnpkg.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz#f033885116dfefd9c6f54787523e3514b61e1968" - integrity sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg== - lightningcss-darwin-arm64@1.32.0: version "1.32.0" resolved "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz" integrity sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ== -lightningcss-darwin-x64@1.32.0: - version "1.32.0" - resolved "https://registry.yarnpkg.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz#35f3e97332d130b9ca181e11b568ded6aebc6d5e" - integrity sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w== - -lightningcss-freebsd-x64@1.32.0: - version "1.32.0" - resolved "https://registry.yarnpkg.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz#9777a76472b64ed6ff94342ad64c7bafd794a575" - integrity sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig== - -lightningcss-linux-arm-gnueabihf@1.32.0: - version "1.32.0" - resolved "https://registry.yarnpkg.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz#13ae652e1ab73b9135d7b7da172f666c410ad53d" - integrity sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw== - -lightningcss-linux-arm64-gnu@1.32.0: - version "1.32.0" - resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz#417858795a94592f680123a1b1f9da8a0e1ef335" - integrity sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ== - -lightningcss-linux-arm64-musl@1.32.0: - version "1.32.0" - resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz#6be36692e810b718040802fd809623cffe732133" - integrity sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg== - -lightningcss-linux-x64-gnu@1.32.0: - version "1.32.0" - resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz#0b7803af4eb21cfd38dd39fe2abbb53c7dd091f6" - integrity sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA== - -lightningcss-linux-x64-musl@1.32.0: - version "1.32.0" - resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz#88dc8ba865ddddb1ac5ef04b0f161804418c163b" - integrity sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg== - -lightningcss-win32-arm64-msvc@1.32.0: - version "1.32.0" - resolved "https://registry.yarnpkg.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz#4f30ba3fa5e925f5b79f945e8cc0d176c3b1ab38" - integrity sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw== - -lightningcss-win32-x64-msvc@1.32.0: - version "1.32.0" - resolved "https://registry.yarnpkg.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz#141aa5605645064928902bb4af045fa7d9f4220a" - integrity sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q== - lightningcss@^1.32.0: version "1.32.0" resolved "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz" @@ -870,11 +749,26 @@ ms@^2.1.3: resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -multiformats@^13.0.0, multiformats@^13.1.0, multiformats@^13.3.1: +multiformats@^13.0.0: + version "13.4.2" + resolved "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz" + integrity sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ== + +multiformats@^13.1.0: + version "13.4.2" + resolved "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz" + integrity sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ== + +multiformats@^13.3.1: version "13.4.2" resolved "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz" integrity sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ== +multiformats@^14.0.4: + version "14.0.5" + resolved "https://registry.npmjs.org/multiformats/-/multiformats-14.0.5.tgz" + integrity sha512-vbIm83F2yZ1pWJGS0yl0ysracIvv56LtbrIyiIQHoLdYDJOMoLfVFsXhh9DUH4SFdkdkFhucyWniihsNzVEjkQ== + nanoid@^3.3.11: version "3.3.11" resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz" @@ -943,7 +837,7 @@ picocolors@^1.1.1: resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== -picomatch@^4.0.3: +"picomatch@^3 || ^4", picomatch@^4.0.3: version "4.0.3" resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz" integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== @@ -1070,11 +964,6 @@ ts-api-utils@^2.5.0: resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz" integrity sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA== -tslib@^2.4.0: - version "2.8.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" - integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== - type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" @@ -1092,7 +981,7 @@ typescript-eslint@8.61.0: "@typescript-eslint/typescript-estree" "8.61.0" "@typescript-eslint/utils" "8.61.0" -typescript@^5.4.0: +typescript@^5.4.0, typescript@>=4.8.4, "typescript@>=4.8.4 <6.1.0": version "5.9.3" resolved "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz" integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==