@chrtco/sdk 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -0
- package/README.md +69 -0
- package/dist/fs-adapter-D5iwdb1Q.d.cts +33 -0
- package/dist/fs-adapter-D5iwdb1Q.d.ts +33 -0
- package/dist/index.cjs +825 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +398 -1
- package/dist/index.d.ts +398 -1
- package/dist/index.js +822 -0
- package/dist/index.js.map +1 -1
- package/dist/node/index.cjs +90 -0
- package/dist/node/index.cjs.map +1 -0
- package/dist/node/index.d.cts +5 -0
- package/dist/node/index.d.ts +5 -0
- package/dist/node/index.js +66 -0
- package/dist/node/index.js.map +1 -0
- package/package.json +14 -6
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts","../src/endpoints/tiles.ts","../src/endpoints/cells.ts","../src/endpoints/coverage.ts","../src/endpoints/features.ts","../src/endpoints/query.ts","../src/pagination.ts","../src/webhooks.ts","../src/index.ts"],"sourcesContent":["import type { ChrtErrorBody } from \"./types.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.chrt.co/v1\";\n\nexport class ChrtError extends Error {\n status: number;\n code?: string;\n body?: ChrtErrorBody;\n\n constructor(status: number, body?: ChrtErrorBody) {\n super(body?.message ?? body?.error ?? `CHRT API error ${status}`);\n this.name = \"ChrtError\";\n this.status = status;\n this.code = body?.code;\n this.body = body;\n }\n}\n\nexport class HttpClient {\n private apiKey: string;\n private baseUrl: string;\n private retries: number;\n\n constructor(apiKey: string, baseUrl?: string, retries = 2) {\n this.apiKey = apiKey;\n this.baseUrl = (baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n this.retries = retries;\n }\n\n getBaseUrl(): string {\n return this.baseUrl;\n }\n\n getApiKey(): string {\n return this.apiKey;\n }\n\n async request<T>(\n path: string,\n params?: Record<string, string | number | undefined>,\n signal?: AbortSignal,\n ): Promise<T> {\n const url = new URL(`${this.baseUrl}${path}`);\n if (params) {\n for (const [k, v] of Object.entries(params)) {\n if (v !== undefined) url.searchParams.set(k, String(v));\n }\n }\n\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt <= this.retries; attempt++) {\n if (signal?.aborted) {\n throw signal.reason ?? new DOMException(\"The operation was aborted.\", \"AbortError\");\n }\n\n try {\n const res = await fetch(url.toString(), {\n headers: { \"x-api-key\": this.apiKey },\n signal,\n });\n\n if (!res.ok) {\n let body: ChrtErrorBody | undefined;\n try {\n body = await res.json() as ChrtErrorBody;\n } catch {\n /* empty */\n }\n const err = new ChrtError(res.status, body);\n\n // Only retry on 5xx\n if (res.status >= 500 && attempt < this.retries) {\n lastError = err;\n await sleep(100 * 2 ** attempt);\n continue;\n }\n throw err;\n }\n\n return (await res.json()) as T;\n } catch (e) {\n if (e instanceof ChrtError) throw e;\n // Don't retry aborted requests\n if (signal?.aborted) throw e;\n lastError = e as Error;\n if (attempt < this.retries) {\n await sleep(100 * 2 ** attempt);\n continue;\n }\n }\n }\n\n throw lastError ?? new Error(\"CHRT API request failed\");\n }\n}\n\nfunction sleep(ms: number) {\n return new Promise((r) => setTimeout(r, ms));\n}\n","import type { HttpClient } from \"../client.js\";\nimport type { TileGroupsResponse } from \"../types.js\";\n\nexport class TilesEndpoint {\n constructor(private http: HttpClient) {}\n\n /** Full style.json URL with API key as query param */\n styleUrl(): string {\n return `${this.http.getBaseUrl()}/tiles/style.json?key=${this.http.getApiKey()}`;\n }\n\n /** Individual tile URL template */\n tileUrl(z: number, x: number, y: number, format: \"pbf\" | \"mvt\" = \"mvt\"): string {\n return `${this.http.getBaseUrl()}/tiles/${z}/${x}/${y}.${format}?key=${this.http.getApiKey()}`;\n }\n\n /** List available tileset groups */\n async groups(): Promise<TileGroupsResponse> {\n return this.http.request<TileGroupsResponse>(\"/tiles/groups\");\n }\n}\n","import type { HttpClient } from \"../client.js\";\nimport type { CellsOptions, CellsResponse, CellDetail } from \"../types.js\";\n\nexport class CellsEndpoint {\n constructor(private http: HttpClient) {}\n\n /** List ENC cells with optional filters */\n async list(options?: CellsOptions): Promise<CellsResponse> {\n const { signal, ...params } = options ?? {};\n return this.http.request<CellsResponse>(\n \"/cells\",\n params as Record<string, string | number | undefined>,\n signal,\n );\n }\n\n /** Get a single cell by ID */\n async get(cellId: string, signal?: AbortSignal): Promise<CellDetail> {\n return this.http.request<CellDetail>(\n `/cells/${encodeURIComponent(cellId)}`,\n undefined,\n signal,\n );\n }\n}\n","import type { HttpClient } from \"../client.js\";\nimport type { CoverageOptions, CoverageResponse } from \"../types.js\";\n\nexport class CoverageEndpoint {\n constructor(private http: HttpClient) {}\n\n /** List available chart sources with cell counts */\n async list(options?: CoverageOptions): Promise<CoverageResponse> {\n const { signal, ...params } = options ?? {};\n return this.http.request<CoverageResponse>(\n \"/coverage\",\n params as Record<string, string | number | undefined>,\n signal,\n );\n }\n}\n","import type { HttpClient } from \"../client.js\";\nimport type { FeaturesOptions, FeatureCollection } from \"../types.js\";\n\nexport class FeaturesEndpoint {\n constructor(private http: HttpClient) {}\n\n /** Query features within a bounding box */\n async list(options: FeaturesOptions): Promise<FeatureCollection> {\n return this.http.request<FeatureCollection>(\n \"/features\",\n {\n bbox: options.bbox.join(\",\"),\n layer: options.layer,\n limit: options.limit,\n offset: options.offset,\n },\n options.signal,\n );\n }\n}\n","import type { HttpClient } from \"../client.js\";\nimport type { QueryOptions, FeatureCollection } from \"../types.js\";\n\nexport class QueryEndpoint {\n constructor(private http: HttpClient) {}\n\n /** Spatial proximity search around a point */\n async nearby(options: QueryOptions): Promise<FeatureCollection> {\n return this.http.request<FeatureCollection>(\n \"/query\",\n {\n point: options.point.join(\",\"),\n radius: options.radius,\n layer: options.layer,\n limit: options.limit,\n },\n options.signal,\n );\n }\n}\n","/**\n * Generic async auto-pagination generator.\n *\n * Fetches pages from a callback that returns `{ items, total_count }` and\n * yields individual items until all pages are consumed or the signal is aborted.\n *\n * @example\n * ```ts\n * for await (const feature of paginate(\n * (offset, signal) => chrt.features({ bbox, limit: 100, offset, signal }),\n * f => f.features,\n * f => f.total_count ?? 0,\n * )) {\n * console.log(feature);\n * }\n * ```\n */\nexport async function* paginate<TResponse, TItem>(\n fetchPage: (offset: number, signal?: AbortSignal) => Promise<TResponse>,\n getItems: (response: TResponse) => TItem[],\n getTotalCount: (response: TResponse) => number,\n options?: { pageSize?: number; signal?: AbortSignal },\n): AsyncGenerator<TItem, void, undefined> {\n const pageSize = options?.pageSize ?? 100;\n const signal = options?.signal;\n let offset = 0;\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n if (signal?.aborted) return;\n\n const response = await fetchPage(offset, signal);\n const items = getItems(response);\n\n for (const item of items) {\n yield item;\n }\n\n if (items.length === 0) return;\n\n offset += items.length;\n const total = getTotalCount(response);\n if (total > 0 && offset >= total) return;\n }\n}\n","/* ── Webhook event types ───────────────────────────── */\n\n/** Base envelope for all webhook payloads */\nexport interface WebhookPayload<T extends string, D> {\n /** Unique event ID */\n id: string;\n /** ISO 8601 timestamp */\n timestamp: string;\n /** Event type discriminator */\n type: T;\n /** Event-specific data */\n data: D;\n}\n\n/* ── Event data shapes ─────────────────────────────── */\n\nexport interface CellEventData {\n cell_id: string;\n chart_source: string;\n title: string | null;\n edition: number | null;\n update_number: number | null;\n scale_band: string | null;\n}\n\nexport interface CellUpdatedData extends CellEventData {\n previous_edition: number | null;\n previous_update_number: number | null;\n}\n\n/* ── Concrete event types ──────────────────────────── */\n\nexport type CellUpdatedEvent = WebhookPayload<\"cell.updated\", CellUpdatedData>;\nexport type CellNewEvent = WebhookPayload<\"cell.new\", CellEventData>;\nexport type CellDeletedEvent = WebhookPayload<\"cell.deleted\", CellEventData>;\n\n/** Discriminated union of all webhook events */\nexport type WebhookEvent = CellUpdatedEvent | CellNewEvent | CellDeletedEvent;\n\n/** All recognised webhook event type strings */\nexport type WebhookEventType = WebhookEvent[\"type\"];\n\n/* ── Runtime parser ────────────────────────────────── */\n\nconst VALID_TYPES = new Set<string>([\"cell.updated\", \"cell.new\", \"cell.deleted\"]);\n\n/**\n * Parse and validate a raw webhook payload.\n *\n * @param body - The raw request body (parsed JSON object)\n * @returns A typed `WebhookEvent`\n * @throws Error if the payload is malformed or has an unknown event type\n *\n * @example\n * ```ts\n * import { parseWebhook } from \"@chrtco/sdk\";\n *\n * app.post(\"/webhooks/chrt\", (req, res) => {\n * const event = parseWebhook(req.body);\n * switch (event.type) {\n * case \"cell.updated\":\n * console.log(\"Cell updated:\", event.data.cell_id);\n * break;\n * case \"cell.new\":\n * console.log(\"New cell:\", event.data.cell_id);\n * break;\n * }\n * res.sendStatus(200);\n * });\n * ```\n */\nexport function parseWebhook(body: unknown): WebhookEvent {\n if (typeof body !== \"object\" || body === null) {\n throw new Error(\"Invalid webhook payload: expected an object\");\n }\n\n const obj = body as Record<string, unknown>;\n\n if (typeof obj.id !== \"string\") {\n throw new Error(\"Invalid webhook payload: missing 'id'\");\n }\n if (typeof obj.timestamp !== \"string\") {\n throw new Error(\"Invalid webhook payload: missing 'timestamp'\");\n }\n if (typeof obj.type !== \"string\" || !VALID_TYPES.has(obj.type)) {\n throw new Error(`Invalid webhook payload: unknown type '${String(obj.type)}'`);\n }\n if (typeof obj.data !== \"object\" || obj.data === null) {\n throw new Error(\"Invalid webhook payload: missing 'data'\");\n }\n\n return obj as unknown as WebhookEvent;\n}\n","import { HttpClient, ChrtError } from \"./client.js\";\nimport { TilesEndpoint } from \"./endpoints/tiles.js\";\nimport { CellsEndpoint } from \"./endpoints/cells.js\";\nimport { CoverageEndpoint } from \"./endpoints/coverage.js\";\nimport { FeaturesEndpoint } from \"./endpoints/features.js\";\nimport { QueryEndpoint } from \"./endpoints/query.js\";\nimport { paginate } from \"./pagination.js\";\nimport type {\n ChrtConfig,\n FeaturesOptions,\n QueryOptions,\n CellsOptions,\n CoverageOptions,\n FeatureCollection,\n GeoJsonFeature,\n CellsResponse,\n CellDetail,\n CoverageResponse,\n TransformRequest,\n} from \"./types.js\";\n\nexport class Chrt {\n /** Tile endpoints — styleUrl, tileUrl, groups */\n readonly tiles: TilesEndpoint;\n /** Cell catalogue endpoints — list, get */\n readonly cells: CellsEndpoint;\n /** Coverage data — list available chart sources */\n readonly coverage: CoverageEndpoint;\n\n private featuresEp: FeaturesEndpoint;\n private queryEp: QueryEndpoint;\n private http: HttpClient;\n\n constructor(apiKeyOrConfig: string | ChrtConfig) {\n const config =\n typeof apiKeyOrConfig === \"string\"\n ? { apiKey: apiKeyOrConfig }\n : apiKeyOrConfig;\n\n this.http = new HttpClient(config.apiKey, config.baseUrl, config.retries);\n this.tiles = new TilesEndpoint(this.http);\n this.cells = new CellsEndpoint(this.http);\n this.coverage = new CoverageEndpoint(this.http);\n this.featuresEp = new FeaturesEndpoint(this.http);\n this.queryEp = new QueryEndpoint(this.http);\n }\n\n /* ── Convenience shortcuts ───────────────────────── */\n\n /** Query GeoJSON features by bounding box */\n async features(options: FeaturesOptions): Promise<FeatureCollection> {\n return this.featuresEp.list(options);\n }\n\n /** Spatial proximity query around a point */\n async query(options: QueryOptions): Promise<FeatureCollection> {\n return this.queryEp.nearby(options);\n }\n\n /* ── Auto-pagination iterators ───────────────────── */\n\n /**\n * Iterate all features matching a bounding-box query, auto-paginating.\n *\n * @example\n * ```ts\n * for await (const feature of chrt.featuresIter({ bbox: [-74, 40, -73, 41] })) {\n * console.log(feature.properties);\n * }\n * ```\n */\n featuresIter(\n options: Omit<FeaturesOptions, \"offset\">,\n ): AsyncIterable<GeoJsonFeature> {\n const pageSize = options.limit ?? 100;\n return paginate<FeatureCollection, GeoJsonFeature>(\n (offset, signal) =>\n this.features({ ...options, limit: pageSize, offset, signal }),\n (r) => r.features,\n (r) => r.total_count ?? 0,\n { pageSize, signal: options.signal },\n );\n }\n\n /**\n * Iterate all cells matching a query, auto-paginating.\n *\n * @example\n * ```ts\n * for await (const cell of chrt.cellsIter({ source: \"NOAA\" })) {\n * console.log(cell.cell_id, cell.title);\n * }\n * ```\n */\n cellsIter(\n options?: Omit<CellsOptions, \"offset\">,\n ): AsyncIterable<CellDetail> {\n const pageSize = options?.limit ?? 100;\n return paginate<CellsResponse, CellDetail>(\n (offset, signal) =>\n this.cells.list({ ...options, limit: pageSize, offset, signal }),\n (r) => r.cells,\n (r) => r.total_count,\n { pageSize, signal: options?.signal },\n );\n }\n\n /* ── Map integration helpers ─────────────────────── */\n\n /** Returns a style.json URL ready for Mapbox GL / MapLibre */\n styleUrl(): string {\n return this.tiles.styleUrl();\n }\n\n /**\n * Returns a `transformRequest` function for Mapbox GL JS / MapLibre GL JS.\n * Injects the API key header for requests to api.chrt.co.\n *\n * @example\n * const map = new mapboxgl.Map({\n * style: chrt.styleUrl(),\n * transformRequest: chrt.transformRequest(),\n * });\n */\n transformRequest(): (url: string) => TransformRequest | undefined {\n const apiKey = this.http.getApiKey();\n return (url: string) => {\n if (url.includes(\"api.chrt.co\")) {\n return { url, headers: { \"x-api-key\": apiKey } };\n }\n return undefined;\n };\n }\n}\n\n/* ── Re-exports ────────────────────────────────────── */\n\nexport { ChrtError } from \"./client.js\";\nexport { paginate } from \"./pagination.js\";\nexport { parseWebhook } from \"./webhooks.js\";\nexport type {\n ChrtConfig,\n FeaturesOptions,\n QueryOptions,\n CellsOptions,\n CoverageOptions,\n FeatureCollection,\n GeoJsonFeature,\n GeoJsonGeometry,\n CellsResponse,\n CellDetail,\n CoverageSource,\n CoverageResponse,\n TileGroup,\n TileGroupsResponse,\n TransformRequest,\n} from \"./types.js\";\nexport type {\n WebhookPayload,\n WebhookEvent,\n WebhookEventType,\n CellUpdatedEvent,\n CellNewEvent,\n CellDeletedEvent,\n CellEventData,\n CellUpdatedData,\n} from \"./webhooks.js\";\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts","../src/endpoints/tiles.ts","../src/endpoints/cells.ts","../src/endpoints/coverage.ts","../src/endpoints/features.ts","../src/endpoints/query.ts","../src/endpoints/offline-errors.ts","../src/endpoints/offline-token-manager.ts","../src/endpoints/offline-style.ts","../src/endpoints/offline-manifest.ts","../src/endpoints/offline.ts","../src/pagination.ts","../src/webhooks.ts","../src/index.ts"],"sourcesContent":["import type { ChrtErrorBody } from \"./types.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.chrt.co/v1\";\n\nexport class ChrtError extends Error {\n status: number;\n code?: string;\n body?: ChrtErrorBody;\n\n constructor(status: number, body?: ChrtErrorBody) {\n super(body?.message ?? body?.error ?? `CHRT API error ${status}`);\n this.name = \"ChrtError\";\n this.status = status;\n this.code = body?.code;\n this.body = body;\n }\n}\n\nexport class HttpClient {\n private apiKey: string;\n private baseUrl: string;\n private retries: number;\n\n constructor(apiKey: string, baseUrl?: string, retries = 2) {\n this.apiKey = apiKey;\n this.baseUrl = (baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n this.retries = retries;\n }\n\n getBaseUrl(): string {\n return this.baseUrl;\n }\n\n getApiKey(): string {\n return this.apiKey;\n }\n\n async request<T>(\n path: string,\n params?: Record<string, string | number | undefined>,\n signal?: AbortSignal,\n ): Promise<T> {\n const url = new URL(`${this.baseUrl}${path}`);\n if (params) {\n for (const [k, v] of Object.entries(params)) {\n if (v !== undefined) url.searchParams.set(k, String(v));\n }\n }\n\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt <= this.retries; attempt++) {\n if (signal?.aborted) {\n throw signal.reason ?? new DOMException(\"The operation was aborted.\", \"AbortError\");\n }\n\n try {\n const res = await fetch(url.toString(), {\n headers: { \"x-api-key\": this.apiKey },\n signal,\n });\n\n if (!res.ok) {\n let body: ChrtErrorBody | undefined;\n try {\n body = await res.json() as ChrtErrorBody;\n } catch {\n /* empty */\n }\n const err = new ChrtError(res.status, body);\n\n // Only retry on 5xx\n if (res.status >= 500 && attempt < this.retries) {\n lastError = err;\n await sleep(100 * 2 ** attempt);\n continue;\n }\n throw err;\n }\n\n return (await res.json()) as T;\n } catch (e) {\n if (e instanceof ChrtError) throw e;\n // Don't retry aborted requests\n if (signal?.aborted) throw e;\n lastError = e as Error;\n if (attempt < this.retries) {\n await sleep(100 * 2 ** attempt);\n continue;\n }\n }\n }\n\n throw lastError ?? new Error(\"CHRT API request failed\");\n }\n\n /**\n * Typed wrapper for non-GET JSON requests. Returns the parsed body and HTTP\n * status together so callers (e.g. offline endpoints) can map error bodies\n * to typed errors without throwing at the HTTP layer. Auth defaults to\n * `x-api-key` header; pass `bearerToken` to swap to `Authorization: Bearer`.\n * No retry — the offline flow owns its own retry logic.\n */\n async postJson<T>(\n path: string,\n body: unknown,\n opts?: { bearerToken?: string; signal?: AbortSignal },\n ): Promise<{ ok: boolean; status: number; body: T | null }> {\n const url = new URL(`${this.baseUrl}${path}`);\n const headers: Record<string, string> = { \"content-type\": \"application/json\" };\n if (opts?.bearerToken) {\n headers[\"authorization\"] = `Bearer ${opts.bearerToken}`;\n } else {\n headers[\"x-api-key\"] = this.apiKey;\n }\n\n const res = await fetch(url.toString(), {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n signal: opts?.signal,\n });\n\n let parsed: T | null = null;\n try {\n parsed = (await res.json()) as T;\n } catch {\n parsed = null;\n }\n return { ok: res.ok, status: res.status, body: parsed };\n }\n\n /** GET with Bearer auth (for offline-worker endpoints that require a JWT). */\n async getJsonWithBearer<T>(\n path: string,\n bearerToken: string,\n signal?: AbortSignal,\n ): Promise<{ ok: boolean; status: number; body: T | null }> {\n const url = new URL(`${this.baseUrl}${path}`);\n const res = await fetch(url.toString(), {\n method: \"GET\",\n headers: { authorization: `Bearer ${bearerToken}` },\n signal,\n });\n let parsed: T | null = null;\n try {\n parsed = (await res.json()) as T;\n } catch {\n parsed = null;\n }\n return { ok: res.ok, status: res.status, body: parsed };\n }\n}\n\nfunction sleep(ms: number) {\n return new Promise((r) => setTimeout(r, ms));\n}\n","import type { HttpClient } from \"../client.js\";\nimport type { TileGroupsResponse } from \"../types.js\";\n\nexport class TilesEndpoint {\n constructor(private http: HttpClient) {}\n\n /** Full style.json URL with API key as query param */\n styleUrl(): string {\n return `${this.http.getBaseUrl()}/tiles/style.json?key=${this.http.getApiKey()}`;\n }\n\n /** Individual tile URL template */\n tileUrl(z: number, x: number, y: number, format: \"pbf\" | \"mvt\" = \"mvt\"): string {\n return `${this.http.getBaseUrl()}/tiles/${z}/${x}/${y}.${format}?key=${this.http.getApiKey()}`;\n }\n\n /** List available tileset groups */\n async groups(): Promise<TileGroupsResponse> {\n return this.http.request<TileGroupsResponse>(\"/tiles/groups\");\n }\n}\n","import type { HttpClient } from \"../client.js\";\nimport type { CellsOptions, CellsResponse, CellDetail } from \"../types.js\";\n\nexport class CellsEndpoint {\n constructor(private http: HttpClient) {}\n\n /** List ENC cells with optional filters */\n async list(options?: CellsOptions): Promise<CellsResponse> {\n const { signal, ...params } = options ?? {};\n return this.http.request<CellsResponse>(\n \"/cells\",\n params as Record<string, string | number | undefined>,\n signal,\n );\n }\n\n /** Get a single cell by ID */\n async get(cellId: string, signal?: AbortSignal): Promise<CellDetail> {\n return this.http.request<CellDetail>(\n `/cells/${encodeURIComponent(cellId)}`,\n undefined,\n signal,\n );\n }\n}\n","import type { HttpClient } from \"../client.js\";\nimport type { CoverageOptions, CoverageResponse } from \"../types.js\";\n\nexport class CoverageEndpoint {\n constructor(private http: HttpClient) {}\n\n /** List available chart sources with cell counts */\n async list(options?: CoverageOptions): Promise<CoverageResponse> {\n const { signal, ...params } = options ?? {};\n return this.http.request<CoverageResponse>(\n \"/coverage\",\n params as Record<string, string | number | undefined>,\n signal,\n );\n }\n}\n","import type { HttpClient } from \"../client.js\";\nimport type { FeaturesOptions, FeatureCollection } from \"../types.js\";\n\nexport class FeaturesEndpoint {\n constructor(private http: HttpClient) {}\n\n /** Query features within a bounding box */\n async list(options: FeaturesOptions): Promise<FeatureCollection> {\n return this.http.request<FeatureCollection>(\n \"/features\",\n {\n bbox: options.bbox.join(\",\"),\n layer: options.layer,\n limit: options.limit,\n offset: options.offset,\n },\n options.signal,\n );\n }\n}\n","import type { HttpClient } from \"../client.js\";\nimport type { QueryOptions, FeatureCollection } from \"../types.js\";\n// import type { RouteQueryOptions, RouteQueryResponse } from \"../types.js\";\n\nexport class QueryEndpoint {\n constructor(private http: HttpClient) {}\n\n /** Spatial proximity search around a point */\n async nearby(options: QueryOptions): Promise<FeatureCollection> {\n return this.http.request<FeatureCollection>(\n \"/query\",\n {\n point: options.point.join(\",\"),\n radius: options.radius,\n layer: options.layer,\n limit: options.limit,\n },\n options.signal,\n );\n }\n\n // ── [CHRTquery] Staged — uncomment when serve-query/route is live ──\n //\n // /** Discover ENC cells along a route LineString */\n // async alongRoute(options: RouteQueryOptions): Promise<RouteQueryResponse> {\n // const route = {\n // type: \"LineString\" as const,\n // coordinates: options.route,\n // };\n // return this.http.request<RouteQueryResponse>(\n // \"/query/route\",\n // {\n // route: JSON.stringify(route),\n // buffer: options.buffer,\n // source: options.source,\n // scale_band: options.scaleBand,\n // limit: options.limit,\n // },\n // options.signal,\n // );\n // }\n\n // ── [CHRTquery] Staged — uncomment when serve-query/route/features is live ──\n //\n // /** Find individual features along a route LineString */\n // async featuresAlongRoute(options: RouteFeatureQueryOptions): Promise<RouteFeatureQueryResponse> {\n // const route = {\n // type: \"LineString\" as const,\n // coordinates: options.route,\n // };\n // return this.http.request<RouteFeatureQueryResponse>(\n // \"/query/route/features\",\n // {\n // route: JSON.stringify(route),\n // buffer: options.buffer,\n // layer: options.layer,\n // limit: options.limit,\n // offset: options.offset,\n // },\n // options.signal,\n // );\n // }\n}\n","/**\n * Typed errors surfaced by `chrt.offline.*`.\n *\n * Callers switch on `.code` — never parse `.message`. `.status` is set only\n * when the error originated from an HTTP response. `.detail` carries\n * machine-readable context for programmatic handling (byte math for budget\n * violations, `resumable: true` for interrupts, etc.).\n */\n\nexport type OfflineErrorCode =\n | \"budget_exceeded\"\n | \"coverage_denied\"\n | \"quota_exceeded\"\n | \"download_corrupt\"\n | \"network_interrupted\"\n | \"partial_file_invalid\"\n | \"invalid_api_key\"\n | \"region_not_available\"\n | \"offline_not_enabled\"\n | \"missing_device_id\"\n | \"missing_manifest_path\"\n | \"sha_missing\"\n | \"unexpected_response\";\n\nexport class OfflineError extends Error {\n readonly code: OfflineErrorCode;\n readonly status?: number;\n readonly detail?: Record<string, unknown>;\n\n constructor(\n code: OfflineErrorCode,\n message: string,\n status?: number,\n detail?: Record<string, unknown>,\n ) {\n super(message);\n this.name = \"OfflineError\";\n this.code = code;\n this.status = status;\n this.detail = detail;\n }\n}\n\n/**\n * Map a server-side error body (mint-offline-token, offline-worker) to an\n * OfflineErrorCode. The server uses both `code` and `error` keys depending on\n * route; we try both.\n */\nexport function mapServerError(\n body: unknown,\n status: number,\n): { code: OfflineErrorCode; detail?: Record<string, unknown> } {\n const b = (body ?? {}) as Record<string, unknown>;\n const raw = typeof b.code === \"string\" ? b.code : typeof b.error === \"string\" ? b.error : \"\";\n\n // Server-side strings that have a direct typed analogue.\n switch (raw) {\n case \"offline_budget_exceeded\":\n return {\n code: \"budget_exceeded\",\n detail: {\n budget_bytes: b.budget_bytes,\n required_bytes: b.required_bytes,\n over_by_bytes: b.over_by_bytes,\n remaining_bytes: b.remaining_bytes,\n },\n };\n case \"offline_not_enabled\":\n return { code: \"offline_not_enabled\" };\n case \"region_not_licensed\":\n return {\n code: \"coverage_denied\",\n detail: { requested: b.requested, allowed: b.allowed },\n };\n case \"no_offline_builds\":\n case \"no_offline_build\":\n return { code: \"region_not_available\", detail: { detail: b.detail, region: b.region } };\n case \"invalid_api_key\":\n case \"missing_auth\":\n return { code: \"invalid_api_key\" };\n case \"tile_quota_exceeded\":\n return { code: \"quota_exceeded\" };\n case \"rate_limited\":\n return {\n code: \"quota_exceeded\",\n detail: { limit: b.limit, period_seconds: b.period_seconds },\n };\n case \"revoked\":\n return { code: \"invalid_api_key\", detail: { reason: \"revoked\" } };\n }\n\n // Status-based fallbacks for error strings we don't recognize.\n if (status === 401) return { code: \"invalid_api_key\" };\n if (status === 403) return { code: \"coverage_denied\" };\n if (status === 429) return { code: \"quota_exceeded\" };\n if (status === 503) return { code: \"region_not_available\" };\n\n return { code: \"unexpected_response\", detail: { raw_error: raw } };\n}\n","import type { HttpClient } from \"../client.js\";\nimport { OfflineError, mapServerError } from \"./offline-errors.js\";\n\n/**\n * Fresh-token shape from POST `/v1/mint/offline-token` (proxied to Supabase\n * `mint-offline-token`). Mirrors docs/api/tile-jwt-claims.md §4.\n */\nexport interface MintedOfflineToken {\n token: string;\n expiresAt: number;\n kid: string;\n offlineUntil: number;\n /** Per-region build_tag map returned by the mint function. */\n buildTags: Record<string, string>;\n gracePeriodDays: number;\n}\n\ninterface MintResponseBody {\n token?: unknown;\n expires_at?: unknown;\n kid?: unknown;\n offline_until?: unknown;\n regions?: unknown;\n grace_period_days?: unknown;\n}\n\n/** Refresh this many seconds before `exp`. */\nconst REFRESH_MARGIN_SECONDS = 30;\n\nfunction now(): number {\n return Math.floor(Date.now() / 1000);\n}\n\n/**\n * Cache + single-flight mint for offline JWTs.\n *\n * - Keyed by `(deviceId, sorted-regions)` so a token scoped to one region\n * doesn't accidentally get reused for a broader request.\n * - Refreshes 30s before `exp`.\n * - While a mint is in flight, concurrent callers share the same promise.\n */\nexport class OfflineTokenManager {\n private readonly cache = new Map<string, MintedOfflineToken>();\n private readonly pending = new Map<string, Promise<MintedOfflineToken>>();\n\n constructor(private readonly http: HttpClient) {}\n\n /**\n * Get a fresh-enough token for the caller's device + requested regions.\n * If `regions` is omitted, the mint function defaults to the key's full\n * `allowed_sources`.\n */\n async getToken(deviceId: string, regions?: string[]): Promise<MintedOfflineToken> {\n if (!deviceId) {\n throw new OfflineError(\"missing_device_id\", \"deviceId is required for offline operations\");\n }\n const key = this.cacheKey(deviceId, regions);\n\n const cached = this.cache.get(key);\n if (cached && cached.expiresAt - now() > REFRESH_MARGIN_SECONDS) {\n return cached;\n }\n\n const inflight = this.pending.get(key);\n if (inflight) return inflight;\n\n const promise = this.mintFresh(deviceId, regions).finally(() => {\n this.pending.delete(key);\n });\n this.pending.set(key, promise);\n const token = await promise;\n this.cache.set(key, token);\n return token;\n }\n\n /** Force-evict the cached token for a (deviceId, regions) pair. Used after a 401. */\n invalidate(deviceId: string, regions?: string[]): void {\n this.cache.delete(this.cacheKey(deviceId, regions));\n }\n\n private cacheKey(deviceId: string, regions?: string[]): string {\n const tail = regions && regions.length > 0 ? [...regions].sort().join(\",\") : \"*\";\n return `${deviceId}::${tail}`;\n }\n\n private async mintFresh(deviceId: string, regions?: string[]): Promise<MintedOfflineToken> {\n const body: { device_id: string; regions?: string[] } = { device_id: deviceId };\n if (regions && regions.length > 0) body.regions = regions;\n\n const res = await this.http.postJson<MintResponseBody>(\"/mint/offline-token\", body);\n\n if (!res.ok) {\n const mapped = mapServerError(res.body, res.status);\n throw new OfflineError(\n mapped.code,\n `mint-offline-token failed: HTTP ${res.status}`,\n res.status,\n mapped.detail,\n );\n }\n\n const body2 = res.body ?? {};\n const regionsArr = Array.isArray(body2.regions) ? (body2.regions as unknown[]) : [];\n const buildTags: Record<string, string> = {};\n for (const r of regionsArr) {\n if (r && typeof r === \"object\") {\n const row = r as { region?: unknown; build_tag?: unknown };\n if (typeof row.region === \"string\" && typeof row.build_tag === \"string\") {\n buildTags[row.region] = row.build_tag;\n }\n }\n }\n\n if (\n typeof body2.token !== \"string\" ||\n typeof body2.expires_at !== \"number\" ||\n typeof body2.kid !== \"string\" ||\n typeof body2.offline_until !== \"number\" ||\n typeof body2.grace_period_days !== \"number\"\n ) {\n throw new OfflineError(\n \"unexpected_response\",\n \"mint-offline-token returned malformed body\",\n res.status,\n );\n }\n\n return {\n token: body2.token,\n expiresAt: body2.expires_at,\n kid: body2.kid,\n offlineUntil: body2.offline_until,\n buildTags,\n gracePeriodDays: body2.grace_period_days,\n };\n }\n}\n","/**\n * Pure helper: take an online MapLibre style and rewrite the named region's\n * vector source to point at a local MBTiles file. Layers, paint, layout,\n * glyphs, and sprite are untouched.\n *\n * This is the JS counterpart of the offline-worker's server-side rewriter\n * (see `cloudflare/offline-worker/src/common.ts:rewriteStyleSources`).\n * Customers can either:\n *\n * 1. Fetch a pre-baked offline style from the worker via\n * `chrt.offline.fetchStyle(region, opts)` — recommended for native\n * MapLibre clients (iOS/Android/desktop) that don't have the JS atlas\n * available.\n *\n * 2. Generate the online style themselves (e.g. via the React app's\n * `src/lib/atlas/style-generator.ts`) and call `buildOfflineStyle()`\n * locally — recommended for JS/web SDK consumers who already use the\n * atlas at runtime.\n *\n * Both paths produce equivalent output.\n */\n\n/**\n * MapLibre style specification (subset). Matches the JSON shape MapLibre GL\n * JS, MapLibre Native iOS/Android, and the MapLibre style spec all consume.\n * Customers who want full TypeScript style typing should reference\n * `@maplibre/maplibre-gl-style-spec.StyleSpecification` directly.\n */\nexport interface MapLibreStyle {\n version: number;\n name?: string;\n metadata?: Record<string, unknown>;\n sources: Record<string, MapLibreSource | unknown>;\n sprite?: string;\n glyphs?: string;\n layers: unknown[];\n}\n\ninterface MapLibreSource {\n type: string;\n tiles?: string[];\n url?: string;\n minzoom?: number;\n maxzoom?: number;\n [key: string]: unknown;\n}\n\n/** Target platform — drives the URL scheme used in the rewritten source. */\nexport type OfflineStylePlatform = \"ios\" | \"android\" | \"desktop\";\n\nexport interface BuildOfflineStyleOptions {\n /**\n * Target platform. iOS + Android use the `mbtiles://` scheme MapLibre Native\n * understands; desktop uses `file://`. Default: `\"ios\"`.\n */\n platform?: OfflineStylePlatform;\n /**\n * Name of the source to rewrite. If omitted, defaults to the only vector\n * source in the input style. Throws `OfflineStyleError` if there are zero\n * or more than one vector sources and `sourceName` isn't provided.\n */\n sourceName?: string;\n}\n\n/**\n * Thrown when `buildOfflineStyle` can't determine which source to swap or\n * the named source isn't present in the input style.\n */\nexport class OfflineStyleError extends Error {\n readonly code: string;\n constructor(message: string, code: string) {\n super(message);\n this.name = \"OfflineStyleError\";\n this.code = code;\n }\n}\n\n/**\n * Returns a deep-copy of `onlineStyle` with the named (or auto-detected)\n * vector source rewritten to point at `mbtilesPath`. Other sources, layers,\n * paint, layout, glyphs, and sprite pass through unmodified.\n *\n * URL scheme:\n *\n * platform=\"ios\" → `mbtiles://<mbtilesPath>` MLN Native iOS\n * platform=\"android\" → `mbtiles://<mbtilesPath>` MLN Native Android\n * platform=\"desktop\" → `file://<mbtilesPath>` MLN GL native (Win/Mac/Linux)\n *\n * The `mbtilesPath` is intentionally treated as opaque text. Most native MLN\n * bindings accept the bare scheme + filename and resolve against an\n * app-defined root (typically the app's Documents directory). Pass an\n * absolute path on desktop, or a relative filename on iOS/Android.\n *\n * @param onlineStyle The base MapLibre style. Not mutated.\n * @param mbtilesPath Local-file path component (e.g. `\"NOAA_ENC.mbtiles\"`).\n * @param opts Platform + optional explicit `sourceName`.\n *\n * @example\n * import { buildOfflineStyle } from \"@chrtco/sdk\";\n *\n * // Online style obtained however you like — generated client-side or\n * // fetched from a server.\n * const onlineStyle = generateStyle(\"DAY\", { region: \"NOAA_ENC\" });\n *\n * const offlineStyle = buildOfflineStyle(\n * onlineStyle,\n * \"NOAA_ENC.mbtiles\",\n * { platform: \"ios\" },\n * );\n * // → hand `offlineStyle` to MapLibre.\n */\nexport function buildOfflineStyle(\n onlineStyle: MapLibreStyle,\n mbtilesPath: string,\n opts: BuildOfflineStyleOptions = {},\n): MapLibreStyle {\n if (!onlineStyle || typeof onlineStyle !== \"object\") {\n throw new OfflineStyleError(\"onlineStyle is required\", \"invalid_style\");\n }\n if (!onlineStyle.sources || typeof onlineStyle.sources !== \"object\") {\n throw new OfflineStyleError(\"style has no sources\", \"no_sources\");\n }\n if (!mbtilesPath || typeof mbtilesPath !== \"string\") {\n throw new OfflineStyleError(\"mbtilesPath is required\", \"invalid_path\");\n }\n\n const platform = opts.platform ?? \"ios\";\n const scheme = platform === \"desktop\" ? \"file\" : \"mbtiles\";\n const url = `${scheme}://${mbtilesPath}`;\n\n let sourceName: string;\n if (opts.sourceName) {\n if (!(opts.sourceName in onlineStyle.sources)) {\n throw new OfflineStyleError(\n `source \"${opts.sourceName}\" not found in style`,\n \"source_not_found\",\n );\n }\n sourceName = opts.sourceName;\n } else {\n const vectorSources = Object.entries(onlineStyle.sources).filter(\n ([, src]) =>\n src &&\n typeof src === \"object\" &&\n (src as MapLibreSource).type === \"vector\",\n );\n if (vectorSources.length === 0) {\n throw new OfflineStyleError(\n \"no vector sources in style — pass `opts.sourceName` to override\",\n \"no_vector_source\",\n );\n }\n if (vectorSources.length > 1) {\n throw new OfflineStyleError(\n `${vectorSources.length} vector sources in style — pass \\`opts.sourceName\\``,\n \"ambiguous_source\",\n );\n }\n sourceName = vectorSources[0][0];\n }\n\n // Deep-clone via JSON round-trip — style is plain JSON-shaped, no cycles.\n const cloned = JSON.parse(JSON.stringify(onlineStyle)) as MapLibreStyle;\n const src = cloned.sources[sourceName] as MapLibreSource;\n src.tiles = [url];\n // Collapse legacy `mapbox://...` shape if present so MLN doesn't see both.\n delete src.url;\n return cloned;\n}\n","import type { FileSystemAdapter } from \"../fs-adapter.js\";\nimport { OfflineError } from \"./offline-errors.js\";\n\n/**\n * On-disk shape. Bump `version` if we ever change it; old readers refuse.\n */\nexport interface OfflineManifest {\n version: 1;\n budget_bytes: number | null;\n regions: Record<string, ManifestRegionEntry>;\n}\n\nexport interface ManifestRegionEntry {\n path: string;\n bytes: number;\n sha256: string;\n build_id: string;\n build_tag: string;\n downloaded_at: number;\n last_accessed_at: number;\n}\n\nconst CURRENT_VERSION = 1;\n\nfunction emptyManifest(): OfflineManifest {\n return { version: CURRENT_VERSION, budget_bytes: null, regions: {} };\n}\n\nexport async function readManifest(\n manifestPath: string | undefined,\n fs: FileSystemAdapter,\n): Promise<OfflineManifest> {\n if (!manifestPath) return emptyManifest();\n if (!(await fs.exists(manifestPath))) return emptyManifest();\n\n const raw = await fs.readFile(manifestPath);\n const text = new TextDecoder().decode(raw);\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n throw new OfflineError(\n \"unexpected_response\",\n `offline manifest at ${manifestPath} is not valid JSON`,\n );\n }\n\n if (!parsed || typeof parsed !== \"object\") {\n throw new OfflineError(\"unexpected_response\", `offline manifest shape invalid`);\n }\n const m = parsed as Partial<OfflineManifest>;\n if (m.version !== CURRENT_VERSION) {\n throw new OfflineError(\n \"unexpected_response\",\n `offline manifest version mismatch: got ${String(m.version)}, expected ${CURRENT_VERSION}`,\n );\n }\n return {\n version: CURRENT_VERSION,\n budget_bytes: typeof m.budget_bytes === \"number\" ? m.budget_bytes : null,\n regions: m.regions && typeof m.regions === \"object\" ? m.regions : {},\n };\n}\n\nexport async function writeManifest(\n manifestPath: string,\n fs: FileSystemAdapter,\n manifest: OfflineManifest,\n): Promise<void> {\n const text = JSON.stringify(manifest, null, 2);\n const bytes = new TextEncoder().encode(text);\n await fs.writeFile(manifestPath, bytes);\n}\n\nexport function usedBytes(manifest: OfflineManifest, excludeRegion?: string): number {\n let total = 0;\n for (const [region, entry] of Object.entries(manifest.regions)) {\n if (region === excludeRegion) continue;\n total += entry.bytes;\n }\n return total;\n}\n\nexport function requireManifestPath(manifestPath: string | undefined): string {\n if (!manifestPath) {\n throw new OfflineError(\n \"missing_manifest_path\",\n \"ChrtConfig.manifestPath is required for this operation\",\n );\n }\n return manifestPath;\n}\n","import type { HttpClient } from \"../client.js\";\nimport type {\n OfflineBundleResponse,\n OfflineDownloadOptions,\n OfflineDownloadResult,\n OfflineDownloadProgress,\n OfflineFetchStyleOptions,\n OfflineHeartbeatOptions,\n OfflineRegionSummary,\n OfflineRemoveOptions,\n OfflineStyleResponse,\n} from \"../types.js\";\nimport { OfflineError, mapServerError } from \"./offline-errors.js\";\nimport { OfflineTokenManager } from \"./offline-token-manager.js\";\nimport {\n buildOfflineStyle,\n type BuildOfflineStyleOptions,\n type MapLibreStyle,\n} from \"./offline-style.js\";\nimport {\n readManifest,\n requireManifestPath,\n usedBytes,\n writeManifest,\n type OfflineManifest,\n} from \"./offline-manifest.js\";\n\n/** Progress is throttled to whichever of these two thresholds is larger. */\nconst PROGRESS_MIN_BYTES = 1024 * 1024; // 1 MB\nconst PROGRESS_MIN_FRACTION = 0.01; // 1%\n\ninterface RegionsListResponse {\n regions?: unknown;\n unavailable?: unknown;\n}\n\nexport class OfflineEndpoint {\n readonly tokenManager: OfflineTokenManager;\n\n constructor(\n private readonly http: HttpClient,\n private readonly deviceId: string | undefined,\n private readonly manifestPath: string | undefined,\n ) {\n this.tokenManager = new OfflineTokenManager(http);\n }\n\n /** List regions downloadable under the caller's API key. */\n async list(): Promise<OfflineRegionSummary[]> {\n const token = await this.requireToken();\n const res = await this.http.getJsonWithBearer<RegionsListResponse>(\n \"/offline/regions\",\n token.token,\n );\n if (!res.ok) {\n const mapped = mapServerError(res.body, res.status);\n throw new OfflineError(\n mapped.code,\n `offline.list failed: HTTP ${res.status}`,\n res.status,\n mapped.detail,\n );\n }\n const arr = Array.isArray(res.body?.regions) ? (res.body.regions as unknown[]) : [];\n const out: OfflineRegionSummary[] = [];\n for (const r of arr) {\n if (!r || typeof r !== \"object\") continue;\n const row = r as {\n region?: unknown;\n build_id?: unknown;\n build_tag?: unknown;\n total_bytes?: unknown;\n issued_at?: unknown;\n };\n if (\n typeof row.region === \"string\" &&\n typeof row.build_id === \"string\" &&\n typeof row.build_tag === \"string\" &&\n typeof row.total_bytes === \"number\"\n ) {\n out.push({\n region: row.region,\n build_id: row.build_id,\n build_tag: row.build_tag,\n total_bytes: row.total_bytes,\n issued_at: typeof row.issued_at === \"number\" ? row.issued_at : null,\n });\n }\n }\n return out;\n }\n\n /**\n * Download an MBTiles bundle for `region` to `opts.path`. Resumable via a\n * `<path>.partial` sidecar on interrupt. SHA-256 verified before rename.\n */\n async download(region: string, opts: OfflineDownloadOptions): Promise<OfflineDownloadResult> {\n return this.downloadInternal(region, opts, 0);\n }\n\n /** Delete local file and, if manifest is configured, drop its row + heartbeat. */\n async remove(region: string, opts: OfflineRemoveOptions): Promise<void> {\n if (await opts.fs.exists(opts.path)) {\n await opts.fs.unlink(opts.path);\n }\n const partial = `${opts.path}.partial`;\n if (await opts.fs.exists(partial)) {\n await opts.fs.unlink(partial);\n }\n\n if (this.manifestPath) {\n const manifest = await readManifest(this.manifestPath, opts.fs);\n if (manifest.regions[region]) {\n delete manifest.regions[region];\n await writeManifest(this.manifestPath, opts.fs, manifest);\n }\n // Best-effort heartbeat with the new totals; failure doesn't block removal.\n const regions = Object.keys(manifest.regions);\n const total = usedBytes(manifest);\n try {\n await this.heartbeat({ regions, total_bytes: total, fs: opts.fs });\n } catch {\n // swallow — the file is already gone locally.\n }\n }\n }\n\n /**\n * Pure helper — rewrites the named (or auto-detected) vector source in\n * `onlineStyle` to point at a local MBTiles file. Layers/paint/glyphs\n * untouched. Use when you already have an online style (e.g. generated\n * client-side via the React app's atlas) and want the offline variant.\n *\n * Equivalent to importing `buildOfflineStyle` directly — exposed on the\n * endpoint for discoverability.\n */\n style(\n onlineStyle: MapLibreStyle,\n mbtilesPath: string,\n opts?: BuildOfflineStyleOptions,\n ): MapLibreStyle {\n return buildOfflineStyle(onlineStyle, mbtilesPath, opts);\n }\n\n /**\n * Fetch a pre-baked offline style for `region` from\n * `GET /v1/offline/styles/{region}`. The worker's response already has the\n * source rewritten to `mbtiles://` (default) or `file://`, so the returned\n * `style` is ready to hand directly to MapLibre.\n *\n * Recommended for native MLN clients (iOS/Android/desktop) that don't have\n * the JS atlas available. JS/web SDK consumers can equivalently generate\n * the online style themselves and call `chrt.offline.style(...)`.\n */\n async fetchStyle(\n region: string,\n opts: OfflineFetchStyleOptions = {},\n ): Promise<OfflineStyleResponse> {\n const token = await this.requireToken([region]);\n const palette = opts.palette ?? \"DAY\";\n const protocol = opts.protocol ?? \"mbtiles\";\n const path = `/offline/styles/${encodeURIComponent(region)}?palette=${palette}&protocol=${protocol}`;\n const res = await this.http.getJsonWithBearer<OfflineStyleResponse>(\n path,\n token.token,\n );\n if (!res.ok) {\n const mapped = mapServerError(res.body, res.status);\n throw new OfflineError(\n mapped.code,\n `offline.fetchStyle failed: HTTP ${res.status}`,\n res.status,\n mapped.detail,\n );\n }\n if (!res.body || typeof res.body !== \"object\" || !res.body.style) {\n throw new OfflineError(\n \"unexpected_response\",\n \"offline.fetchStyle: malformed response (missing `style`)\",\n res.status,\n );\n }\n return res.body;\n }\n\n /**\n * Set the local storage budget in bytes. Requires `manifestPath`. Enforced\n * at the start of each `download()`. A budget smaller than current usage\n * does not auto-prune; it only refuses new downloads until the customer\n * calls `remove()`.\n */\n async setStorageBudget(bytes: number, fs: import(\"../fs-adapter.js\").FileSystemAdapter): Promise<void> {\n if (!Number.isFinite(bytes) || bytes < 0) {\n throw new OfflineError(\"unexpected_response\", `invalid budget: ${bytes}`);\n }\n const path = requireManifestPath(this.manifestPath);\n const manifest = await readManifest(path, fs);\n manifest.budget_bytes = bytes;\n await writeManifest(path, fs, manifest);\n }\n\n /**\n * Send a heartbeat. If `opts.regions` / `opts.total_bytes` are omitted and\n * a manifest is configured, values are derived from it. v1 is manual-call\n * only; auto-scheduling is a follow-up phase.\n */\n async heartbeat(opts?: OfflineHeartbeatOptions): Promise<void> {\n const deviceId = this.requireDeviceId();\n\n let regions = opts?.regions;\n let totalBytes = opts?.total_bytes;\n\n // Auto-derive from manifest when caller omitted values AND gave us an fs.\n if ((regions === undefined || totalBytes === undefined) && this.manifestPath && opts?.fs) {\n const manifest = await readManifest(this.manifestPath, opts.fs);\n if (regions === undefined) regions = Object.keys(manifest.regions);\n if (totalBytes === undefined) totalBytes = usedBytes(manifest);\n }\n if (regions === undefined) regions = [];\n if (totalBytes === undefined) totalBytes = 0;\n\n const token = await this.tokenManager.getToken(deviceId);\n const body = {\n device_id: deviceId,\n regions,\n total_bytes: totalBytes,\n ...(opts?.sdk_version ? { sdk_version: opts.sdk_version } : {}),\n ...(opts?.platform ? { platform: opts.platform } : {}),\n };\n const res = await this.http.postJson<unknown>(\"/offline/heartbeat\", body, {\n bearerToken: token.token,\n });\n if (!res.ok) {\n const mapped = mapServerError(res.body, res.status);\n throw new OfflineError(\n mapped.code,\n `offline.heartbeat failed: HTTP ${res.status}`,\n res.status,\n mapped.detail,\n );\n }\n }\n\n // ── Internals ────────────────────────────────────────────────────────────\n\n private requireDeviceId(): string {\n if (!this.deviceId) {\n throw new OfflineError(\n \"missing_device_id\",\n \"ChrtConfig.deviceId is required for offline operations\",\n );\n }\n return this.deviceId;\n }\n\n private async requireToken(regions?: string[]) {\n const deviceId = this.requireDeviceId();\n return this.tokenManager.getToken(deviceId, regions);\n }\n\n private async fetchBundle(region: string, bearerToken: string): Promise<OfflineBundleResponse> {\n const res = await this.http.getJsonWithBearer<Record<string, unknown>>(\n `/offline/bundles/${encodeURIComponent(region)}`,\n bearerToken,\n );\n if (!res.ok) {\n const mapped = mapServerError(res.body, res.status);\n throw new OfflineError(\n mapped.code,\n `offline.download bundle fetch failed: HTTP ${res.status}`,\n res.status,\n mapped.detail,\n );\n }\n const b = res.body ?? {};\n const missingFields: string[] = [];\n const field = <K extends string>(k: K): unknown => (b as Record<string, unknown>)[k];\n\n for (const k of [\"signed_url\", \"bytes\", \"build_id\", \"build_tag\", \"expires_at\"] as const) {\n if (field(k) === undefined) missingFields.push(k);\n }\n if (missingFields.length > 0) {\n throw new OfflineError(\n \"unexpected_response\",\n `bundle response missing fields: ${missingFields.join(\",\")}`,\n res.status,\n );\n }\n if (typeof field(\"sha256\") !== \"string\" || (field(\"sha256\") as string).length === 0) {\n throw new OfflineError(\n \"sha_missing\",\n \"bundle response did not include sha256; cannot verify integrity\",\n res.status,\n );\n }\n return {\n region: typeof field(\"region\") === \"string\" ? (field(\"region\") as string) : region,\n build_id: field(\"build_id\") as string,\n build_tag: field(\"build_tag\") as string,\n signed_url: field(\"signed_url\") as string,\n expires_at: field(\"expires_at\") as number,\n bytes: field(\"bytes\") as number,\n sha256: (field(\"sha256\") as string).toLowerCase(),\n offline_until: typeof field(\"offline_until\") === \"number\" ? (field(\"offline_until\") as number) : 0,\n heartbeat_after:\n typeof field(\"heartbeat_after\") === \"number\" ? (field(\"heartbeat_after\") as number) : 0,\n server_time: typeof field(\"server_time\") === \"number\" ? (field(\"server_time\") as number) : 0,\n };\n }\n\n private async downloadInternal(\n region: string,\n opts: OfflineDownloadOptions,\n retryDepth: number,\n ): Promise<OfflineDownloadResult> {\n const { path, fs, onProgress, signal } = opts;\n const partialPath = `${path}.partial`;\n\n const token = await this.requireToken([region]);\n const bundle = await this.fetchBundle(region, token.token);\n\n // Budget check against manifest (if present).\n if (this.manifestPath) {\n const manifest = await readManifest(this.manifestPath, fs);\n if (manifest.budget_bytes !== null) {\n const currentUsed = usedBytes(manifest, region);\n const projected = currentUsed + bundle.bytes;\n if (projected > manifest.budget_bytes) {\n throw new OfflineError(\n \"budget_exceeded\",\n `offline storage budget would be exceeded`,\n undefined,\n {\n budget_bytes: manifest.budget_bytes,\n used_bytes: currentUsed,\n required_bytes: bundle.bytes,\n over_by_bytes: projected - manifest.budget_bytes,\n },\n );\n }\n }\n }\n\n // Examine existing partial.\n let startByte = 0;\n let resumed = false;\n if (await fs.exists(partialPath)) {\n const partialStat = await fs.stat(partialPath);\n if (partialStat.size > bundle.bytes) {\n await fs.unlink(partialPath);\n } else if (partialStat.size === bundle.bytes) {\n const computed = await fs.sha256(partialPath);\n if (computed.toLowerCase() === bundle.sha256) {\n await fs.rename(partialPath, path);\n await this.persistManifestEntry(region, bundle, path, fs);\n return {\n path,\n bytes: bundle.bytes,\n sha256: bundle.sha256,\n build_id: bundle.build_id,\n build_tag: bundle.build_tag,\n resumed: true,\n };\n }\n // Complete but corrupt — drop and restart.\n await fs.unlink(partialPath);\n } else {\n startByte = partialStat.size;\n resumed = true;\n }\n }\n\n // Fetch (with Range if resuming).\n const fetchInit: RequestInit = { method: \"GET\", signal };\n if (startByte > 0) {\n fetchInit.headers = { Range: `bytes=${startByte}-` };\n }\n\n let response: Response;\n try {\n response = await fetch(bundle.signed_url, fetchInit);\n } catch (err) {\n if (signal?.aborted) {\n throw new OfflineError(\n \"network_interrupted\",\n \"download aborted before response\",\n undefined,\n { resumable: startByte > 0, bytes_written: startByte },\n );\n }\n throw new OfflineError(\n \"network_interrupted\",\n err instanceof Error ? err.message : \"fetch failed\",\n undefined,\n { resumable: startByte > 0, bytes_written: startByte },\n );\n }\n\n // Expired signed URL or build flipped mid-resume → re-mint once.\n if (startByte > 0 && (response.status === 403 || response.status === 404)) {\n await fs.unlink(partialPath);\n if (retryDepth > 0) {\n throw new OfflineError(\n \"unexpected_response\",\n `signed URL still rejected after re-mint (HTTP ${response.status})`,\n response.status,\n );\n }\n this.tokenManager.invalidate(this.requireDeviceId(), [region]);\n return this.downloadInternal(region, opts, retryDepth + 1);\n }\n\n if (startByte > 0 && response.status !== 206) {\n throw new OfflineError(\n \"unexpected_response\",\n `expected 206 Partial Content on Range request, got ${response.status}`,\n response.status,\n );\n }\n if (startByte === 0 && response.status !== 200) {\n throw new OfflineError(\n \"unexpected_response\",\n `expected 200 on initial GET, got ${response.status}`,\n response.status,\n );\n }\n if (!response.body) {\n throw new OfflineError(\n \"unexpected_response\",\n \"response had no body\",\n response.status,\n );\n }\n\n // Stream with progress + abort + persist-on-interrupt.\n await fs.mkdir(dirname(path), { recursive: true });\n const writer = (await fs.writeStream(partialPath, { append: startByte > 0 })).getWriter();\n let bytesDownloaded = startByte;\n let lastProgressBytes = startByte;\n const progressThreshold = Math.max(\n PROGRESS_MIN_BYTES,\n Math.floor(bundle.bytes * PROGRESS_MIN_FRACTION),\n );\n\n const emit = (final: boolean): void => {\n if (!onProgress) return;\n if (!final && bytesDownloaded - lastProgressBytes < progressThreshold) return;\n const progress: OfflineDownloadProgress = {\n bytesDownloaded,\n bytesTotal: bundle.bytes,\n fraction: bundle.bytes === 0 ? 1 : bytesDownloaded / bundle.bytes,\n };\n try {\n onProgress(progress);\n } catch {\n // customer callback failures must not break download\n }\n lastProgressBytes = bytesDownloaded;\n };\n\n emit(true); // always fire at start\n\n try {\n const reader = response.body.getReader();\n while (true) {\n if (signal?.aborted) {\n throw new OfflineError(\n \"network_interrupted\",\n \"download aborted by caller\",\n undefined,\n { resumable: true, bytes_written: bytesDownloaded },\n );\n }\n const { done, value } = await reader.read();\n if (done) break;\n if (value) {\n await writer.write(value);\n bytesDownloaded += value.byteLength;\n emit(false);\n }\n }\n await writer.close();\n } catch (err) {\n try {\n await writer.abort();\n } catch {\n // best-effort\n }\n if (err instanceof OfflineError) throw err;\n throw new OfflineError(\n \"network_interrupted\",\n err instanceof Error ? err.message : \"stream error\",\n undefined,\n { resumable: true, bytes_written: bytesDownloaded },\n );\n }\n\n emit(true); // always fire at completion\n\n // Verify SHA.\n const computedSha = await fs.sha256(partialPath);\n if (computedSha.toLowerCase() !== bundle.sha256) {\n await fs.unlink(partialPath);\n throw new OfflineError(\n \"download_corrupt\",\n `SHA-256 mismatch for ${region}: expected ${bundle.sha256}, got ${computedSha}`,\n undefined,\n { expected: bundle.sha256, actual: computedSha },\n );\n }\n\n await fs.rename(partialPath, path);\n await this.persistManifestEntry(region, bundle, path, fs);\n\n return {\n path,\n bytes: bundle.bytes,\n sha256: bundle.sha256,\n build_id: bundle.build_id,\n build_tag: bundle.build_tag,\n resumed,\n };\n }\n\n private async persistManifestEntry(\n region: string,\n bundle: OfflineBundleResponse,\n path: string,\n fs: import(\"../fs-adapter.js\").FileSystemAdapter,\n ): Promise<void> {\n if (!this.manifestPath) return;\n const manifest: OfflineManifest = await readManifest(this.manifestPath, fs);\n const nowSec = Math.floor(Date.now() / 1000);\n manifest.regions[region] = {\n path,\n bytes: bundle.bytes,\n sha256: bundle.sha256,\n build_id: bundle.build_id,\n build_tag: bundle.build_tag,\n downloaded_at: nowSec,\n last_accessed_at: nowSec,\n };\n await writeManifest(this.manifestPath, fs, manifest);\n }\n}\n\n/** Tiny helper so we don't import `node:path`. */\nfunction dirname(p: string): string {\n const i = p.lastIndexOf(\"/\");\n const j = p.lastIndexOf(\"\\\\\");\n const k = Math.max(i, j);\n if (k <= 0) return \".\";\n return p.slice(0, k);\n}\n","/**\n * Generic async auto-pagination generator.\n *\n * Fetches pages from a callback that returns `{ items, total_count }` and\n * yields individual items until all pages are consumed or the signal is aborted.\n *\n * @example\n * ```ts\n * for await (const feature of paginate(\n * (offset, signal) => chrt.features({ bbox, limit: 100, offset, signal }),\n * f => f.features,\n * f => f.total_count ?? 0,\n * )) {\n * console.log(feature);\n * }\n * ```\n */\nexport async function* paginate<TResponse, TItem>(\n fetchPage: (offset: number, signal?: AbortSignal) => Promise<TResponse>,\n getItems: (response: TResponse) => TItem[],\n getTotalCount: (response: TResponse) => number,\n options?: { pageSize?: number; signal?: AbortSignal },\n): AsyncGenerator<TItem, void, undefined> {\n const pageSize = options?.pageSize ?? 100;\n const signal = options?.signal;\n let offset = 0;\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n if (signal?.aborted) return;\n\n const response = await fetchPage(offset, signal);\n const items = getItems(response);\n\n for (const item of items) {\n yield item;\n }\n\n if (items.length === 0) return;\n\n offset += items.length;\n const total = getTotalCount(response);\n if (total > 0 && offset >= total) return;\n }\n}\n","/* ── Webhook event types ───────────────────────────── */\n\n/** Base envelope for all webhook payloads */\nexport interface WebhookPayload<T extends string, D> {\n /** Unique event ID */\n id: string;\n /** ISO 8601 timestamp */\n timestamp: string;\n /** Event type discriminator */\n type: T;\n /** Event-specific data */\n data: D;\n}\n\n/* ── Event data shapes ─────────────────────────────── */\n\nexport interface CellEventData {\n cell_id: string;\n chart_source: string;\n title: string | null;\n edition: number | null;\n update_number: number | null;\n scale_band: string | null;\n}\n\nexport interface CellUpdatedData extends CellEventData {\n previous_edition: number | null;\n previous_update_number: number | null;\n}\n\n/* ── Concrete event types ──────────────────────────── */\n\nexport type CellUpdatedEvent = WebhookPayload<\"cell.updated\", CellUpdatedData>;\nexport type CellNewEvent = WebhookPayload<\"cell.new\", CellEventData>;\nexport type CellDeletedEvent = WebhookPayload<\"cell.deleted\", CellEventData>;\n\n/** Discriminated union of all webhook events */\nexport type WebhookEvent = CellUpdatedEvent | CellNewEvent | CellDeletedEvent;\n\n/** All recognised webhook event type strings */\nexport type WebhookEventType = WebhookEvent[\"type\"];\n\n/* ── Runtime parser ────────────────────────────────── */\n\nconst VALID_TYPES = new Set<string>([\"cell.updated\", \"cell.new\", \"cell.deleted\"]);\n\n/**\n * Parse and validate a raw webhook payload.\n *\n * @param body - The raw request body (parsed JSON object)\n * @returns A typed `WebhookEvent`\n * @throws Error if the payload is malformed or has an unknown event type\n *\n * @example\n * ```ts\n * import { parseWebhook } from \"@chrtco/sdk\";\n *\n * app.post(\"/webhooks/chrt\", (req, res) => {\n * const event = parseWebhook(req.body);\n * switch (event.type) {\n * case \"cell.updated\":\n * console.log(\"Cell updated:\", event.data.cell_id);\n * break;\n * case \"cell.new\":\n * console.log(\"New cell:\", event.data.cell_id);\n * break;\n * }\n * res.sendStatus(200);\n * });\n * ```\n */\nexport function parseWebhook(body: unknown): WebhookEvent {\n if (typeof body !== \"object\" || body === null) {\n throw new Error(\"Invalid webhook payload: expected an object\");\n }\n\n const obj = body as Record<string, unknown>;\n\n if (typeof obj.id !== \"string\") {\n throw new Error(\"Invalid webhook payload: missing 'id'\");\n }\n if (typeof obj.timestamp !== \"string\") {\n throw new Error(\"Invalid webhook payload: missing 'timestamp'\");\n }\n if (typeof obj.type !== \"string\" || !VALID_TYPES.has(obj.type)) {\n throw new Error(`Invalid webhook payload: unknown type '${String(obj.type)}'`);\n }\n if (typeof obj.data !== \"object\" || obj.data === null) {\n throw new Error(\"Invalid webhook payload: missing 'data'\");\n }\n\n return obj as unknown as WebhookEvent;\n}\n","import { HttpClient, ChrtError } from \"./client.js\";\nimport { TilesEndpoint } from \"./endpoints/tiles.js\";\nimport { CellsEndpoint } from \"./endpoints/cells.js\";\nimport { CoverageEndpoint } from \"./endpoints/coverage.js\";\nimport { FeaturesEndpoint } from \"./endpoints/features.js\";\nimport { QueryEndpoint } from \"./endpoints/query.js\";\nimport { OfflineEndpoint } from \"./endpoints/offline.js\";\nimport { paginate } from \"./pagination.js\";\nimport type {\n ChrtConfig,\n FeaturesOptions,\n QueryOptions,\n CellsOptions,\n CoverageOptions,\n FeatureCollection,\n GeoJsonFeature,\n CellsResponse,\n CellDetail,\n CoverageResponse,\n TransformRequest,\n} from \"./types.js\";\n\nexport class Chrt {\n /** Tile endpoints — styleUrl, tileUrl, groups */\n readonly tiles: TilesEndpoint;\n /** Cell catalogue endpoints — list, get */\n readonly cells: CellsEndpoint;\n /** Coverage data — list available chart sources */\n readonly coverage: CoverageEndpoint;\n /**\n * CHRToffline — download MBTiles bundles for offline use.\n * Requires `deviceId` in config. Manifest-based budget enforcement\n * requires `manifestPath`.\n */\n readonly offline: OfflineEndpoint;\n\n private featuresEp: FeaturesEndpoint;\n private queryEp: QueryEndpoint;\n private http: HttpClient;\n\n constructor(apiKeyOrConfig: string | ChrtConfig) {\n const config =\n typeof apiKeyOrConfig === \"string\"\n ? { apiKey: apiKeyOrConfig }\n : apiKeyOrConfig;\n\n this.http = new HttpClient(config.apiKey, config.baseUrl, config.retries);\n this.tiles = new TilesEndpoint(this.http);\n this.cells = new CellsEndpoint(this.http);\n this.coverage = new CoverageEndpoint(this.http);\n this.featuresEp = new FeaturesEndpoint(this.http);\n this.queryEp = new QueryEndpoint(this.http);\n this.offline = new OfflineEndpoint(this.http, config.deviceId, config.manifestPath);\n }\n\n /* ── Convenience shortcuts ───────────────────────── */\n\n /** Query GeoJSON features by bounding box */\n async features(options: FeaturesOptions): Promise<FeatureCollection> {\n return this.featuresEp.list(options);\n }\n\n /** Spatial proximity query around a point */\n async query(options: QueryOptions): Promise<FeatureCollection> {\n return this.queryEp.nearby(options);\n }\n\n // ── [CHRTquery] Staged — uncomment when serve-query/route is live ──\n //\n // /** Discover ENC cells along a route */\n // async routeQuery(options: RouteQueryOptions): Promise<RouteQueryResponse> {\n // return this.queryEp.alongRoute(options);\n // }\n\n // ── [CHRTquery] Staged — uncomment when serve-query/route/features is live ──\n //\n // /** Find individual features along a route */\n // async routeFeatures(options: RouteFeatureQueryOptions): Promise<RouteFeatureQueryResponse> {\n // return this.queryEp.featuresAlongRoute(options);\n // }\n //\n // /** Iterate all features along a route, auto-paginating */\n // routeFeaturesIter(\n // options: Omit<RouteFeatureQueryOptions, \"offset\">,\n // ): AsyncIterable<GeoJsonFeature> {\n // const pageSize = options.limit ?? 100;\n // return paginate<RouteFeatureQueryResponse, GeoJsonFeature>(\n // (offset, signal) =>\n // this.routeFeatures({ ...options, limit: pageSize, offset, signal }),\n // (r) => r.features,\n // (r) => r.total_count ?? 0,\n // { pageSize, signal: options.signal },\n // );\n // }\n\n /* ── Auto-pagination iterators ───────────────────── */\n\n /**\n * Iterate all features matching a bounding-box query, auto-paginating.\n *\n * @example\n * ```ts\n * for await (const feature of chrt.featuresIter({ bbox: [-74, 40, -73, 41] })) {\n * console.log(feature.properties);\n * }\n * ```\n */\n featuresIter(\n options: Omit<FeaturesOptions, \"offset\">,\n ): AsyncIterable<GeoJsonFeature> {\n const pageSize = options.limit ?? 100;\n return paginate<FeatureCollection, GeoJsonFeature>(\n (offset, signal) =>\n this.features({ ...options, limit: pageSize, offset, signal }),\n (r) => r.features,\n (r) => r.total_count ?? 0,\n { pageSize, signal: options.signal },\n );\n }\n\n /**\n * Iterate all cells matching a query, auto-paginating.\n *\n * @example\n * ```ts\n * for await (const cell of chrt.cellsIter({ source: \"NOAA\" })) {\n * console.log(cell.cell_id, cell.title);\n * }\n * ```\n */\n cellsIter(\n options?: Omit<CellsOptions, \"offset\">,\n ): AsyncIterable<CellDetail> {\n const pageSize = options?.limit ?? 100;\n return paginate<CellsResponse, CellDetail>(\n (offset, signal) =>\n this.cells.list({ ...options, limit: pageSize, offset, signal }),\n (r) => r.cells,\n (r) => r.total_count,\n { pageSize, signal: options?.signal },\n );\n }\n\n /* ── Map integration helpers ─────────────────────── */\n\n /** Returns a style.json URL ready for Mapbox GL / MapLibre */\n styleUrl(): string {\n return this.tiles.styleUrl();\n }\n\n /**\n * Returns a `transformRequest` function for Mapbox GL JS / MapLibre GL JS.\n * Injects the API key header for requests to api.chrt.co.\n *\n * @example\n * const map = new mapboxgl.Map({\n * style: chrt.styleUrl(),\n * transformRequest: chrt.transformRequest(),\n * });\n */\n transformRequest(): (url: string) => TransformRequest | undefined {\n const apiKey = this.http.getApiKey();\n return (url: string) => {\n if (url.includes(\"api.chrt.co\")) {\n return { url, headers: { \"x-api-key\": apiKey } };\n }\n return undefined;\n };\n }\n}\n\n/* ── Re-exports ────────────────────────────────────── */\n\nexport { ChrtError } from \"./client.js\";\nexport { paginate } from \"./pagination.js\";\nexport { parseWebhook } from \"./webhooks.js\";\nexport { OfflineError } from \"./endpoints/offline-errors.js\";\nexport type { OfflineErrorCode } from \"./endpoints/offline-errors.js\";\nexport type { FileSystemAdapter } from \"./fs-adapter.js\";\n\n/* ── Offline style helper (Phase 8c) ───────────────── */\nexport {\n buildOfflineStyle,\n OfflineStyleError,\n} from \"./endpoints/offline-style.js\";\nexport type {\n MapLibreStyle,\n BuildOfflineStyleOptions,\n OfflineStylePlatform,\n} from \"./endpoints/offline-style.js\";\n\nexport type {\n ChrtConfig,\n FeaturesOptions,\n QueryOptions,\n CellsOptions,\n CoverageOptions,\n FeatureCollection,\n GeoJsonFeature,\n GeoJsonGeometry,\n CellsResponse,\n CellDetail,\n CoverageSource,\n CoverageResponse,\n TileGroup,\n TileGroupsResponse,\n TransformRequest,\n RouteQueryOptions,\n RouteQueryResponse,\n RouteFeatureQueryOptions,\n RouteFeatureQueryResponse,\n OfflineRegionSummary,\n OfflineBundleResponse,\n OfflineDownloadOptions,\n OfflineDownloadResult,\n OfflineDownloadProgress,\n OfflineRemoveOptions,\n OfflineHeartbeatOptions,\n OfflineFetchStyleOptions,\n OfflineStyleResponse,\n} from \"./types.js\";\nexport type {\n WebhookPayload,\n WebhookEvent,\n WebhookEventType,\n CellUpdatedEvent,\n CellNewEvent,\n CellDeletedEvent,\n CellEventData,\n CellUpdatedData,\n} from \"./webhooks.js\";\n"],"mappings":"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","names":["src"]}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/node/index.ts
|
|
21
|
+
var node_exports = {};
|
|
22
|
+
__export(node_exports, {
|
|
23
|
+
nodeFileSystemAdapter: () => nodeFileSystemAdapter
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(node_exports);
|
|
26
|
+
|
|
27
|
+
// src/node/fs-adapter.ts
|
|
28
|
+
var import_node_fs = require("fs");
|
|
29
|
+
var import_promises = require("fs/promises");
|
|
30
|
+
var import_node_stream = require("stream");
|
|
31
|
+
var import_node_crypto = require("crypto");
|
|
32
|
+
function nodeFileSystemAdapter() {
|
|
33
|
+
return {
|
|
34
|
+
async exists(path) {
|
|
35
|
+
try {
|
|
36
|
+
await (0, import_promises.stat)(path);
|
|
37
|
+
return true;
|
|
38
|
+
} catch (err) {
|
|
39
|
+
if (isNotFound(err)) return false;
|
|
40
|
+
throw err;
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
async stat(path) {
|
|
44
|
+
const s = await (0, import_promises.stat)(path);
|
|
45
|
+
return { size: s.size };
|
|
46
|
+
},
|
|
47
|
+
async writeStream(path, opts) {
|
|
48
|
+
const node = (0, import_node_fs.createWriteStream)(path, { flags: opts?.append ? "a" : "w" });
|
|
49
|
+
return import_node_stream.Writable.toWeb(node);
|
|
50
|
+
},
|
|
51
|
+
async rename(from, to) {
|
|
52
|
+
await (0, import_promises.rename)(from, to);
|
|
53
|
+
},
|
|
54
|
+
async unlink(path) {
|
|
55
|
+
try {
|
|
56
|
+
await (0, import_promises.unlink)(path);
|
|
57
|
+
} catch (err) {
|
|
58
|
+
if (isNotFound(err)) return;
|
|
59
|
+
throw err;
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
async mkdir(path, opts) {
|
|
63
|
+
await (0, import_promises.mkdir)(path, { recursive: opts?.recursive ?? false });
|
|
64
|
+
},
|
|
65
|
+
async readFile(path) {
|
|
66
|
+
const buf = await (0, import_promises.readFile)(path);
|
|
67
|
+
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
68
|
+
},
|
|
69
|
+
async writeFile(path, data) {
|
|
70
|
+
await (0, import_promises.writeFile)(path, data);
|
|
71
|
+
},
|
|
72
|
+
async sha256(path) {
|
|
73
|
+
return new Promise((resolve, reject) => {
|
|
74
|
+
const hash = (0, import_node_crypto.createHash)("sha256");
|
|
75
|
+
const stream = (0, import_node_fs.createReadStream)(path);
|
|
76
|
+
stream.on("data", (chunk) => hash.update(chunk));
|
|
77
|
+
stream.on("end", () => resolve(hash.digest("hex")));
|
|
78
|
+
stream.on("error", reject);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function isNotFound(err) {
|
|
84
|
+
return !!(err && typeof err === "object" && "code" in err && err.code === "ENOENT");
|
|
85
|
+
}
|
|
86
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
87
|
+
0 && (module.exports = {
|
|
88
|
+
nodeFileSystemAdapter
|
|
89
|
+
});
|
|
90
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/node/index.ts","../../src/node/fs-adapter.ts"],"sourcesContent":["/**\n * Node 18+ sub-export: `import { nodeFileSystemAdapter } from '@chrtco/sdk/node'`.\n *\n * This module imports `node:*` — do not re-export it from the root `index.ts`\n * or you'll pull `node:fs` into browser/Deno builds.\n */\nexport { nodeFileSystemAdapter } from \"./fs-adapter.js\";\n","/**\n * Node 18+ implementation of `FileSystemAdapter`.\n *\n * Imports are `node:*` prefixed so bundlers mark them as external cleanly and\n * core bundle consumers that never import `@chrtco/sdk/node` don't pull them\n * in. Every method uses streaming primitives so multi-GB MBTiles files don't\n * blow the heap.\n */\nimport {\n createReadStream,\n createWriteStream,\n} from \"node:fs\";\nimport { mkdir, rename, stat, unlink, readFile, writeFile } from \"node:fs/promises\";\nimport { Writable } from \"node:stream\";\nimport { createHash } from \"node:crypto\";\nimport type { FileSystemAdapter } from \"../fs-adapter.js\";\n\nexport function nodeFileSystemAdapter(): FileSystemAdapter {\n return {\n async exists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch (err) {\n if (isNotFound(err)) return false;\n throw err;\n }\n },\n\n async stat(path: string): Promise<{ size: number }> {\n const s = await stat(path);\n return { size: s.size };\n },\n\n async writeStream(\n path: string,\n opts?: { append?: boolean },\n ): Promise<WritableStream<Uint8Array>> {\n const node = createWriteStream(path, { flags: opts?.append ? \"a\" : \"w\" });\n return Writable.toWeb(node) as WritableStream<Uint8Array>;\n },\n\n async rename(from: string, to: string): Promise<void> {\n await rename(from, to);\n },\n\n async unlink(path: string): Promise<void> {\n try {\n await unlink(path);\n } catch (err) {\n if (isNotFound(err)) return;\n throw err;\n }\n },\n\n async mkdir(path: string, opts?: { recursive?: boolean }): Promise<void> {\n await mkdir(path, { recursive: opts?.recursive ?? false });\n },\n\n async readFile(path: string): Promise<Uint8Array> {\n const buf = await readFile(path);\n return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);\n },\n\n async writeFile(path: string, data: Uint8Array): Promise<void> {\n await writeFile(path, data);\n },\n\n async sha256(path: string): Promise<string> {\n return new Promise<string>((resolve, reject) => {\n const hash = createHash(\"sha256\");\n const stream = createReadStream(path);\n stream.on(\"data\", (chunk) => hash.update(chunk));\n stream.on(\"end\", () => resolve(hash.digest(\"hex\")));\n stream.on(\"error\", reject);\n });\n },\n };\n}\n\nfunction isNotFound(err: unknown): boolean {\n return !!(err && typeof err === \"object\" && \"code\" in err && (err as { code?: unknown }).code === \"ENOENT\");\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACQA,qBAGO;AACP,sBAAiE;AACjE,yBAAyB;AACzB,yBAA2B;AAGpB,SAAS,wBAA2C;AACzD,SAAO;AAAA,IACL,MAAM,OAAO,MAAgC;AAC3C,UAAI;AACF,kBAAM,sBAAK,IAAI;AACf,eAAO;AAAA,MACT,SAAS,KAAK;AACZ,YAAI,WAAW,GAAG,EAAG,QAAO;AAC5B,cAAM;AAAA,MACR;AAAA,IACF;AAAA,IAEA,MAAM,KAAK,MAAyC;AAClD,YAAM,IAAI,UAAM,sBAAK,IAAI;AACzB,aAAO,EAAE,MAAM,EAAE,KAAK;AAAA,IACxB;AAAA,IAEA,MAAM,YACJ,MACA,MACqC;AACrC,YAAM,WAAO,kCAAkB,MAAM,EAAE,OAAO,MAAM,SAAS,MAAM,IAAI,CAAC;AACxE,aAAO,4BAAS,MAAM,IAAI;AAAA,IAC5B;AAAA,IAEA,MAAM,OAAO,MAAc,IAA2B;AACpD,gBAAM,wBAAO,MAAM,EAAE;AAAA,IACvB;AAAA,IAEA,MAAM,OAAO,MAA6B;AACxC,UAAI;AACF,kBAAM,wBAAO,IAAI;AAAA,MACnB,SAAS,KAAK;AACZ,YAAI,WAAW,GAAG,EAAG;AACrB,cAAM;AAAA,MACR;AAAA,IACF;AAAA,IAEA,MAAM,MAAM,MAAc,MAA+C;AACvE,gBAAM,uBAAM,MAAM,EAAE,WAAW,MAAM,aAAa,MAAM,CAAC;AAAA,IAC3D;AAAA,IAEA,MAAM,SAAS,MAAmC;AAChD,YAAM,MAAM,UAAM,0BAAS,IAAI;AAC/B,aAAO,IAAI,WAAW,IAAI,QAAQ,IAAI,YAAY,IAAI,UAAU;AAAA,IAClE;AAAA,IAEA,MAAM,UAAU,MAAc,MAAiC;AAC7D,gBAAM,2BAAU,MAAM,IAAI;AAAA,IAC5B;AAAA,IAEA,MAAM,OAAO,MAA+B;AAC1C,aAAO,IAAI,QAAgB,CAAC,SAAS,WAAW;AAC9C,cAAM,WAAO,+BAAW,QAAQ;AAChC,cAAM,aAAS,iCAAiB,IAAI;AACpC,eAAO,GAAG,QAAQ,CAAC,UAAU,KAAK,OAAO,KAAK,CAAC;AAC/C,eAAO,GAAG,OAAO,MAAM,QAAQ,KAAK,OAAO,KAAK,CAAC,CAAC;AAClD,eAAO,GAAG,SAAS,MAAM;AAAA,MAC3B,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEA,SAAS,WAAW,KAAuB;AACzC,SAAO,CAAC,EAAE,OAAO,OAAO,QAAQ,YAAY,UAAU,OAAQ,IAA2B,SAAS;AACpG;","names":[]}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// src/node/fs-adapter.ts
|
|
2
|
+
import {
|
|
3
|
+
createReadStream,
|
|
4
|
+
createWriteStream
|
|
5
|
+
} from "fs";
|
|
6
|
+
import { mkdir, rename, stat, unlink, readFile, writeFile } from "fs/promises";
|
|
7
|
+
import { Writable } from "stream";
|
|
8
|
+
import { createHash } from "crypto";
|
|
9
|
+
function nodeFileSystemAdapter() {
|
|
10
|
+
return {
|
|
11
|
+
async exists(path) {
|
|
12
|
+
try {
|
|
13
|
+
await stat(path);
|
|
14
|
+
return true;
|
|
15
|
+
} catch (err) {
|
|
16
|
+
if (isNotFound(err)) return false;
|
|
17
|
+
throw err;
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
async stat(path) {
|
|
21
|
+
const s = await stat(path);
|
|
22
|
+
return { size: s.size };
|
|
23
|
+
},
|
|
24
|
+
async writeStream(path, opts) {
|
|
25
|
+
const node = createWriteStream(path, { flags: opts?.append ? "a" : "w" });
|
|
26
|
+
return Writable.toWeb(node);
|
|
27
|
+
},
|
|
28
|
+
async rename(from, to) {
|
|
29
|
+
await rename(from, to);
|
|
30
|
+
},
|
|
31
|
+
async unlink(path) {
|
|
32
|
+
try {
|
|
33
|
+
await unlink(path);
|
|
34
|
+
} catch (err) {
|
|
35
|
+
if (isNotFound(err)) return;
|
|
36
|
+
throw err;
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
async mkdir(path, opts) {
|
|
40
|
+
await mkdir(path, { recursive: opts?.recursive ?? false });
|
|
41
|
+
},
|
|
42
|
+
async readFile(path) {
|
|
43
|
+
const buf = await readFile(path);
|
|
44
|
+
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
45
|
+
},
|
|
46
|
+
async writeFile(path, data) {
|
|
47
|
+
await writeFile(path, data);
|
|
48
|
+
},
|
|
49
|
+
async sha256(path) {
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
const hash = createHash("sha256");
|
|
52
|
+
const stream = createReadStream(path);
|
|
53
|
+
stream.on("data", (chunk) => hash.update(chunk));
|
|
54
|
+
stream.on("end", () => resolve(hash.digest("hex")));
|
|
55
|
+
stream.on("error", reject);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function isNotFound(err) {
|
|
61
|
+
return !!(err && typeof err === "object" && "code" in err && err.code === "ENOENT");
|
|
62
|
+
}
|
|
63
|
+
export {
|
|
64
|
+
nodeFileSystemAdapter
|
|
65
|
+
};
|
|
66
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/node/fs-adapter.ts"],"sourcesContent":["/**\n * Node 18+ implementation of `FileSystemAdapter`.\n *\n * Imports are `node:*` prefixed so bundlers mark them as external cleanly and\n * core bundle consumers that never import `@chrtco/sdk/node` don't pull them\n * in. Every method uses streaming primitives so multi-GB MBTiles files don't\n * blow the heap.\n */\nimport {\n createReadStream,\n createWriteStream,\n} from \"node:fs\";\nimport { mkdir, rename, stat, unlink, readFile, writeFile } from \"node:fs/promises\";\nimport { Writable } from \"node:stream\";\nimport { createHash } from \"node:crypto\";\nimport type { FileSystemAdapter } from \"../fs-adapter.js\";\n\nexport function nodeFileSystemAdapter(): FileSystemAdapter {\n return {\n async exists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch (err) {\n if (isNotFound(err)) return false;\n throw err;\n }\n },\n\n async stat(path: string): Promise<{ size: number }> {\n const s = await stat(path);\n return { size: s.size };\n },\n\n async writeStream(\n path: string,\n opts?: { append?: boolean },\n ): Promise<WritableStream<Uint8Array>> {\n const node = createWriteStream(path, { flags: opts?.append ? \"a\" : \"w\" });\n return Writable.toWeb(node) as WritableStream<Uint8Array>;\n },\n\n async rename(from: string, to: string): Promise<void> {\n await rename(from, to);\n },\n\n async unlink(path: string): Promise<void> {\n try {\n await unlink(path);\n } catch (err) {\n if (isNotFound(err)) return;\n throw err;\n }\n },\n\n async mkdir(path: string, opts?: { recursive?: boolean }): Promise<void> {\n await mkdir(path, { recursive: opts?.recursive ?? false });\n },\n\n async readFile(path: string): Promise<Uint8Array> {\n const buf = await readFile(path);\n return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);\n },\n\n async writeFile(path: string, data: Uint8Array): Promise<void> {\n await writeFile(path, data);\n },\n\n async sha256(path: string): Promise<string> {\n return new Promise<string>((resolve, reject) => {\n const hash = createHash(\"sha256\");\n const stream = createReadStream(path);\n stream.on(\"data\", (chunk) => hash.update(chunk));\n stream.on(\"end\", () => resolve(hash.digest(\"hex\")));\n stream.on(\"error\", reject);\n });\n },\n };\n}\n\nfunction isNotFound(err: unknown): boolean {\n return !!(err && typeof err === \"object\" && \"code\" in err && (err as { code?: unknown }).code === \"ENOENT\");\n}\n"],"mappings":";AAQA;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,OAAO,QAAQ,MAAM,QAAQ,UAAU,iBAAiB;AACjE,SAAS,gBAAgB;AACzB,SAAS,kBAAkB;AAGpB,SAAS,wBAA2C;AACzD,SAAO;AAAA,IACL,MAAM,OAAO,MAAgC;AAC3C,UAAI;AACF,cAAM,KAAK,IAAI;AACf,eAAO;AAAA,MACT,SAAS,KAAK;AACZ,YAAI,WAAW,GAAG,EAAG,QAAO;AAC5B,cAAM;AAAA,MACR;AAAA,IACF;AAAA,IAEA,MAAM,KAAK,MAAyC;AAClD,YAAM,IAAI,MAAM,KAAK,IAAI;AACzB,aAAO,EAAE,MAAM,EAAE,KAAK;AAAA,IACxB;AAAA,IAEA,MAAM,YACJ,MACA,MACqC;AACrC,YAAM,OAAO,kBAAkB,MAAM,EAAE,OAAO,MAAM,SAAS,MAAM,IAAI,CAAC;AACxE,aAAO,SAAS,MAAM,IAAI;AAAA,IAC5B;AAAA,IAEA,MAAM,OAAO,MAAc,IAA2B;AACpD,YAAM,OAAO,MAAM,EAAE;AAAA,IACvB;AAAA,IAEA,MAAM,OAAO,MAA6B;AACxC,UAAI;AACF,cAAM,OAAO,IAAI;AAAA,MACnB,SAAS,KAAK;AACZ,YAAI,WAAW,GAAG,EAAG;AACrB,cAAM;AAAA,MACR;AAAA,IACF;AAAA,IAEA,MAAM,MAAM,MAAc,MAA+C;AACvE,YAAM,MAAM,MAAM,EAAE,WAAW,MAAM,aAAa,MAAM,CAAC;AAAA,IAC3D;AAAA,IAEA,MAAM,SAAS,MAAmC;AAChD,YAAM,MAAM,MAAM,SAAS,IAAI;AAC/B,aAAO,IAAI,WAAW,IAAI,QAAQ,IAAI,YAAY,IAAI,UAAU;AAAA,IAClE;AAAA,IAEA,MAAM,UAAU,MAAc,MAAiC;AAC7D,YAAM,UAAU,MAAM,IAAI;AAAA,IAC5B;AAAA,IAEA,MAAM,OAAO,MAA+B;AAC1C,aAAO,IAAI,QAAgB,CAAC,SAAS,WAAW;AAC9C,cAAM,OAAO,WAAW,QAAQ;AAChC,cAAM,SAAS,iBAAiB,IAAI;AACpC,eAAO,GAAG,QAAQ,CAAC,UAAU,KAAK,OAAO,KAAK,CAAC;AAC/C,eAAO,GAAG,OAAO,MAAM,QAAQ,KAAK,OAAO,KAAK,CAAC,CAAC;AAClD,eAAO,GAAG,SAAS,MAAM;AAAA,MAC3B,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEA,SAAS,WAAW,KAAuB;AACzC,SAAO,CAAC,EAAE,OAAO,OAAO,QAAQ,YAAY,UAAU,OAAQ,IAA2B,SAAS;AACpG;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chrtco/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Official TypeScript SDK for the CHRT nautical chart API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -11,25 +11,33 @@
|
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
12
|
"import": "./dist/index.js",
|
|
13
13
|
"require": "./dist/index.cjs"
|
|
14
|
+
},
|
|
15
|
+
"./node": {
|
|
16
|
+
"types": "./dist/node/index.d.ts",
|
|
17
|
+
"import": "./dist/node/index.js",
|
|
18
|
+
"require": "./dist/node/index.cjs"
|
|
14
19
|
}
|
|
15
20
|
},
|
|
16
|
-
"files": ["dist", "README.md"],
|
|
21
|
+
"files": ["dist", "README.md", "CHANGELOG.md"],
|
|
17
22
|
"scripts": {
|
|
18
23
|
"build": "tsup",
|
|
19
24
|
"dev": "tsup --watch",
|
|
20
|
-
"typecheck": "tsc --noEmit"
|
|
25
|
+
"typecheck": "tsc --noEmit",
|
|
26
|
+
"test": "vitest run"
|
|
21
27
|
},
|
|
22
|
-
"keywords": ["chrt", "nautical", "charts", "maritime", "enc", "s57", "mapbox", "maplibre", "gis", "api"],
|
|
28
|
+
"keywords": ["chrt", "nautical", "charts", "maritime", "enc", "s57", "mapbox", "maplibre", "gis", "api", "offline", "mbtiles"],
|
|
23
29
|
"license": "MIT",
|
|
24
30
|
"repository": {
|
|
25
31
|
"type": "git",
|
|
26
|
-
"url": "git+https://github.com/chrt-co/
|
|
32
|
+
"url": "git+https://github.com/chrt-co/chrt-prod.git",
|
|
27
33
|
"directory": "packages/sdk"
|
|
28
34
|
},
|
|
29
35
|
"homepage": "https://chrt.co/docs/sdks",
|
|
30
36
|
"devDependencies": {
|
|
37
|
+
"@types/node": "^20.0.0",
|
|
31
38
|
"tsup": "^8.0.0",
|
|
32
|
-
"typescript": "^5.4.0"
|
|
39
|
+
"typescript": "^5.4.0",
|
|
40
|
+
"vitest": "^2.1.0"
|
|
33
41
|
},
|
|
34
42
|
"engines": {
|
|
35
43
|
"node": ">=18.0.0"
|