/** * A licence: its name and a link are always shown beside the credit. * * @example * ```ts no-check * import type { Licence } from "./data-door.ts"; * * const licence: Licence = { name: "CC BY 4.0", url: "https://creativecommons.org/licenses/by/4.0/", spdx: "CC-BY-4.0" }; * ``` */ export interface Licence { name: string; /** The name in Japanese, for a Japanese credit line: a source's own terms (`"UCAC4の利用条件"`). Left out, a plain phrase the door knows ("Public domain") is translated and an identifier ("CC BY 4.0") stays. */ nameJa?: string; url: string; /** An SPDX identifier, when there is one. */ spdx?: string; nonCommercial?: boolean; shareAlike?: boolean; } /** * What a source says about itself. * * @example * ```ts no-check * import type { SourceInfo } from "./data-door.ts"; * * const info: SourceInfo = { * id: "my-catalogue", * name: { en: "My catalogue", ja: "私のカタログ" }, * credit: { en: "My catalogue, A. Person (2026)", ja: "私のカタログ(A. Person、2026年)" }, * licence: { name: "CC BY 4.0", url: "https://creativecommons.org/licenses/by/4.0/" }, * url: "https://example.org/my-catalogue", * retrieved: "2026-11-10", * }; * ``` */ export interface SourceInfo { /** Unique in a view: lower-case letters, digits and hyphens ("ucac4", "my-catalogue"). */ id: string; name: { en: string; ja: string; }; /** The line shown on screen. Never empty, in both languages. */ credit: { en: string; ja: string; }; /** The licence: its name and link are always shown with the credit. */ licence: Licence; /** The source's own page. */ url: string; /** The day it was retrieved, yyyy-mm-dd, as NOTICE.md writes it. */ retrieved: string; } /** * The most records a record source may hold. Bigger data must be tiles. * * @example * ```ts no-check * import { MAX_RECORDS } from "./data-door.ts"; * * console.log(MAX_RECORDS); // 200000 * ``` */ export declare const MAX_RECORDS = 200000; /** * The default budget for decoded tiles in a view, all sources together: 48 MB (a picture counts with its mipmaps, 4/3 of its bytes). * * @example * ```ts no-check * import { TILE_BUDGET_BYTES } from "./data-door.ts"; * * console.log(TILE_BUDGET_BYTES / 1024 / 1024); // 48 * ``` */ export declare const TILE_BUDGET_BYTES: number; /** * The most loads in flight at once for one source. * * @example * ```ts no-check * import { MAX_IN_FLIGHT } from "./data-door.ts"; * * console.log(MAX_IN_FLIGHT); // 6 * ``` */ export declare const MAX_IN_FLIGHT = 6; /** * A list that fits in memory (at most `MAX_RECORDS` records; bigger data must be tiles). It is read once, when the source is added. * * @example * ```ts no-check * import type { RecordSource } from "./data-door.ts"; * * declare const info: import("./data-door.ts").SourceInfo; * const source: RecordSource<{ id: string }> = { kind: "records", info, records: () => [{ id: "a" }] }; * ``` */ export interface RecordSource { kind: "records"; info: SourceInfo; records(signal: AbortSignal): R[] | Promise; } /** * Data cut by place and by detail. `Place` and `Detail` are the package's own types. * * @example * ```ts no-check * import type { TileSource } from "./data-door.ts"; * * declare const info: import("./data-door.ts").SourceInfo; * const source: TileSource = { * kind: "tiles", * info, * need: (place, detail) => [`${place}/${detail}`], * load: async () => new Uint8Array(16), * bytes: (tile) => tile.byteLength, * }; * ``` */ export interface TileSource { kind: "tiles"; info: SourceInfo; /** The keys of the tiles a place needs at a detail. Pure: no fetching. */ need(place: Place, detail: Detail): readonly string[]; /** Loads one tile. The source may use the network or not; it must stop when the signal aborts. */ load(key: string, signal: AbortSignal): Promise; /** The tile's size in memory, in bytes, for the budget. */ bytes(tile: Tile): number; } /** * Either kind of source. * * @example * ```ts no-check * import type { DataSource } from "./data-door.ts"; * * const kinds = (sources: DataSource[]): string[] => sources.map((source) => source.kind); * ``` */ export type DataSource = RecordSource | TileSource; /** * A piece of a map, in degrees: longitude and latitude (right ascension and declination for the sky). One shape in every package. * * @example * ```ts no-check * import type { MapPlace } from "./data-door.ts"; * * const wholeSky: MapPlace = { lonMin: 0, lonMax: 360, latMin: -90, latMax: 90 }; * ``` */ export interface MapPlace { lonMin: number; lonMax: number; latMin: number; latMax: number; } /** * What a picture needs of its bitmap: an `ImageBitmap` fits. * * @example * ```ts no-check * import type { BitmapLike } from "./data-door.ts"; * * const size = (bitmap: BitmapLike): number => bitmap.width * bitmap.height; * ``` */ export interface BitmapLike { readonly width: number; readonly height: number; close?(): void; } /** * A map tile: its size in pixels and its picture. A map source's detail is the pixels across the whole circumference (not a level number, because the packages number their pyramids differently), * and each source maps a width to its own nearest level. * * @example * ```ts no-check * import type { ImageTile } from "./data-door.ts"; * * const tile: ImageTile = { width: 512, height: 256, image: await createImageBitmap(blob) }; * ``` */ export interface ImageTile { width: number; height: number; image: BitmapLike; } /** * One source's part in the last drawn frame. * * @example * ```ts no-check * import type { Credit } from "./data-door.ts"; * * declare const info: import("./data-door.ts").SourceInfo; * const credit: Credit = { source: info, own: false, used: 12 }; * ``` */ export interface Credit { source: SourceInfo; /** True for the package's own (bundled) data. */ own: boolean; /** The marks or tiles drawn from it. */ used: number; } /** * Refuses a source that cannot be shown honestly: it throws a `TypeError` at once, when a source is added, not at the first draw. An empty credit (either language), a licence with no name or no * link, a bad or already used id, a missing name, page or day, or a missing function is refused. * @param source What the caller handed in. * @param taken The ids already in the view, so that a clash is refused. * @example * ```ts no-check * import { validateSource } from "./data-door.ts"; * * validateSource(mySource, view.sources.map((one) => one.info.id)); // throws TypeError: source "mine": the credit is empty (en) * ``` */ export declare function validateSource(source: unknown, taken?: Iterable): asserts source is DataSource; /** * The credit line for what a view drew: each source's credit with its licence name, and "non-commercial" or "share-alike" whenever the source says so, so a page cannot hide the terms of the data it * uses. The package's own sources come first, then the rest, each group in the order given; a source listed twice is written once. English: joined with semicolons, ending in a full stop. Japanese: the * licence in full-width parentheses, joined with /, no full stop. An empty list is an empty string. * @param credits What the view drew, from its `credits()`. * @param language `"en"` or `"ja"`. * @returns The line, or an empty string for no credits. * @example * ```ts no-check * import { creditLine } from "./data-door.ts"; * * creditLine(view.credits(), "en"); // "Yale Bright Star Catalogue (Public domain); UCAC4 (CC0 1.0)." * creditLine(view.credits(), "ja"); // "イェール輝星カタログ(パブリックドメイン)/UCAC4(CC0 1.0)" * ``` */ export declare function creditLine(credits: readonly Credit[], language: "en" | "ja"): string; /** * What `loadRecords` found. `skipped` counts the records dropped for a non-finite number; a failed read has no records and says why. * * @example * ```ts no-check * import type { RecordsRead } from "./data-door.ts"; * * const say = (read: RecordsRead): string => `${read.status}: ${read.records.length} kept, ${read.skipped} skipped`; * ``` */ export interface RecordsRead { status: "ok" | "failed" | "aborted"; /** Frozen. Empty unless the read succeeded. */ records: readonly R[]; skipped: number; /** Why a read failed. */ reason?: string; } /** * Reads a record source once, when it is added: the list is frozen, a record with a non-finite number (a `NaN` or an infinity in any number `finite` names) is skipped and counted, and a list over * `MAX_RECORDS` is refused whole (bigger data must be tiles). It never throws: a source that throws or rejects is a failed read, and a read the signal aborted is `aborted`. * @param source The record source. * @param signal Aborts the read (the source is removed or switched off). * @param finite The numbers of one record that must be finite, if any: `(star) => [star.ra, star.dec, star.mag]`. * @returns What was found: the status, the frozen records, how many were skipped and, for a failed read, why. * @example * ```ts no-check * import { loadRecords } from "./data-door.ts"; * * const read = await loadRecords(source, controller.signal, (star) => [star.ra, star.dec]); // read.records is frozen; read.skipped counts the NaNs * ``` */ export declare function loadRecords(source: RecordSource, signal: AbortSignal, finite?: (record: R) => readonly number[]): Promise>; /** * The part of a tile source the store uses. * * @example * ```ts no-check * import type { TileLoader } from "./data-door.ts"; * * const loader: TileLoader = { info, load: async () => new Uint8Array(8), bytes: (tile) => tile.byteLength }; * ``` */ export type TileLoader = Pick, "info" | "load" | "bytes">; /** * Options for `createTileStore`. * * @example * ```ts no-check * import type { TileStoreOptions } from "./data-door.ts"; * * const options: TileStoreOptions = { budgetBytes: 24 * 1024 * 1024, maxTiles: 256, onChange: () => draw() }; * ``` */ export interface TileStoreOptions { /** The bytes of decoded tiles held, all sources together. Default `TILE_BUDGET_BYTES`. */ budgetBytes?: number; /** The most loads in flight for one source. Default `MAX_IN_FLIGHT`. */ maxInFlight?: number; /** The most tiles held, all sources together, as well as the byte budget. Default: no limit. */ maxTiles?: number; /** Called when a tile has arrived, failed or been refused, so the view can redraw. */ onChange?: () => void; } /** * What a tile store has done. * * @example * ```ts no-check * import type { TileStats } from "./data-door.ts"; * * const full = (stats: TileStats): boolean => stats.refused > 0; * ``` */ export interface TileStats { /** Tiles held, and their bytes: `heldBytes` is never over the budget. */ held: number; heldBytes: number; /** Loads running now, and loads waiting for a free place. */ inFlight: number; queued: number; /** Loads started since the store was made: the number a test reads to prove nothing is sent at rest. */ requested: number; /** Tiles that failed to load, came back empty, or reported a size that is not a number. They are never held or drawn. */ failed: number; /** Tiles that arrived and did not fit, because every tile held was needed by the view. They are not held; the view draws what it holds. */ refused: number; /** Tiles let go to make room. */ evicted: number; } /** * A tile store: tiles by source and key, under one byte budget. * * @example * ```ts no-check * import type { TileStore } from "./data-door.ts"; * * const held = (store: TileStore): number => store.stats().held; * ``` */ export interface TileStore { /** * Tells the store which tiles `source` needs now, and returns the ones it holds. When the keys are the same as the last call it does nothing, so a view that asks again at rest sends nothing; * when they changed, the missing ones load (a tile that failed is tried again only now), the loads of tiles no longer needed are aborted, and the held ones are marked used. * `again: true` is for a caller that means "try what is missing once more" with the same keys: it starts the missing tiles that are not already loading or waiting, and nothing else changes. */ want(source: TileLoader, keys: readonly string[], again?: boolean): ReadonlyMap; /** One held tile, or `undefined`. */ get(sourceId: string, key: string): Tile | undefined; /** Forgets a source: aborts its loads, frees its tiles. The source is switched off. */ drop(sourceId: string): void; stats(): TileStats; /** Resolves when no load is running or waiting. */ idle(): Promise; } /** * A tile store for a view: loads the tiles of every source by key and keeps them under one budget of bytes (default 48 MB, all sources together). * - At most `maxInFlight` loads (6) run at once for a source, and a load starts only because the keys changed: at rest the store sends nothing, and a tile that failed waits for the next change. * - The least recently used tile goes first when room is needed, and never a tile the view needs now. If a new tile does not fit even so, it is refused and counted: the held bytes never pass the budget (nor the count, if `maxTiles` is set). * - A tile that throws, comes back empty or reports a size that is not a finite number is counted in `failed` and never held, so it is never drawn. * - `drop(sourceId)` aborts the source's loads and frees its tiles. * @param options Budget, loads in flight, and a callback for a redraw. * @returns The store. * @example * ```ts no-check * import { createTileStore } from "./data-door.ts"; * * const store = createTileStore({ onChange: draw }); * const held = store.want(source, source.need(place, detail)); // draws what is held now; the rest arrives through onChange * store.drop(source.info.id); // the layer is switched off * ``` */ export declare function createTileStore(options?: TileStoreOptions): TileStore;