@chrtco/sdk 0.8.0 → 0.9.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/dist/index.d.ts CHANGED
@@ -434,21 +434,167 @@ declare class HttpClient {
434
434
  }>;
435
435
  }
436
436
 
437
+ /**
438
+ * Tile authentication for the `tiles.chrt.co` PMTiles path.
439
+ *
440
+ * The CHRT tile-worker serves raw MVT at
441
+ * `https://tiles.chrt.co/v1/tiles/{region}/{z}/{x}/{y}.mvt`
442
+ * and authenticates each request with a short-lived ES256 JWT (5-min default,
443
+ * 15-min max) carried in the `Authorization: Bearer` header. This module mints
444
+ * those JWTs from the customer's API key and produces a MapLibre / Mapbox GL
445
+ * `transformRequest` that attaches the Bearer to tile requests only.
446
+ *
447
+ * Responsibilities (mirrors the first-party web client at
448
+ * `src/lib/map/tile-token-client.ts`, adapted to be instance-scoped and
449
+ * zero-dependency so it runs in browsers, Node 18+, Deno, and Bun):
450
+ * - Mint via `POST {baseUrl}/mint/tile-token` (default
451
+ * `https://api.chrt.co/v1/mint/tile-token`) with the `x-api-key` header.
452
+ * - Cache tokens in memory, keyed by region; refresh silently 30s before
453
+ * expiry so long-lived maps never flicker.
454
+ * - Expose a SYNC `transformRequest` (MapLibre requires sync) that attaches
455
+ * the cached Bearer; it kicks off a background mint on first use so the
456
+ * cache warms within one round-trip.
457
+ *
458
+ * NOTE: the public mint route `POST /v1/mint/tile-token` (the sibling of the
459
+ * existing `/v1/mint/offline-token`) must be present on the `api.chrt.co`
460
+ * edge worker for this path to serve. Until it lands, mints fail with a
461
+ * network/HTTP error surfaced through `getToken()`.
462
+ */
463
+
464
+ /** Default chart region — the one consolidated PMTiles served per region. */
465
+ declare const DEFAULT_TILE_REGION = "NOAA_ENC";
466
+ /** Default tile-worker origin. Override via `ChrtConfig.tileBaseUrl`. */
467
+ declare const DEFAULT_TILE_BASE_URL = "https://tiles.chrt.co";
468
+ /** Full token response from the mint endpoint. */
469
+ interface TileTokenResponse {
470
+ /** ES256-signed JWT; do not log. */
471
+ token: string;
472
+ /** Unix seconds (matches JWT `exp`). */
473
+ expiresAt: number;
474
+ /** Key id used to sign (matches JWT header `kid`). */
475
+ kid: string;
476
+ /** Build id/tag pinned into the JWT — the tile-worker cache key. */
477
+ buildTag: string;
478
+ /** Echo of the region the token is scoped to. */
479
+ region: string;
480
+ }
481
+ /** Typed error codes the mint flow surfaces; callers switch on `.code`. */
482
+ type TileTokenErrorCode = "invalid_api_key" | "feature_flag_disabled" | "region_not_licensed" | "tile_quota_exceeded" | "region_not_available" | "network_error" | "unexpected_response";
483
+ declare class TileTokenError extends Error {
484
+ readonly code: TileTokenErrorCode;
485
+ readonly status?: number;
486
+ constructor(code: TileTokenErrorCode, message: string, status?: number);
487
+ }
488
+ /** MapLibre / Mapbox GL `transformRequest` return shape. */
489
+ interface TransformedRequest {
490
+ url: string;
491
+ headers?: Record<string, string>;
492
+ }
493
+ /**
494
+ * Instance-scoped tile-token manager. One per `Chrt` client so multiple
495
+ * clients (e.g. different API keys) never share a cache.
496
+ */
497
+ declare class TileAuth {
498
+ private readonly http;
499
+ private readonly cache;
500
+ private readonly pending;
501
+ private readonly defaultRegion;
502
+ private readonly tileBaseUrl;
503
+ private lifecycleRegistered;
504
+ constructor(http: HttpClient, opts?: {
505
+ region?: string;
506
+ tileBaseUrl?: string;
507
+ });
508
+ /** Mint once via the HTTP client (POST `/mint/tile-token`, `x-api-key`). */
509
+ private mintOnce;
510
+ /** Mint with exponential backoff on network errors only. */
511
+ private mintWithRetry;
512
+ private scheduleRefresh;
513
+ private refreshInBackground;
514
+ private store;
515
+ /**
516
+ * Fetch (or reuse a cached) tile token for `region`. Concurrent callers for
517
+ * the same region await the same in-flight mint.
518
+ */
519
+ getToken(region?: string): Promise<TileTokenResponse>;
520
+ /** Synchronous cache peek — null if absent or within the stale grace window. */
521
+ peek(region?: string): string | null;
522
+ /**
523
+ * Produce a MapLibre / Mapbox GL `transformRequest`. Attaches
524
+ * `Authorization: Bearer <jwt>` only to CHRT tile URLs and the CHRT
525
+ * served-style URL (`/v1/styles/{region}/style.json`); sprite, glyph, and
526
+ * third-party URLs pass through untouched (never leak the token to
527
+ * third-party CDNs). Kicks off a background mint immediately so the cache
528
+ * warms within one round-trip; the first tile or two may go
529
+ * unauthenticated and be retried by the renderer. When pointing a map's
530
+ * `style` at the served style URL, obtain it via `chrt.styleUrl()` — it
531
+ * awaits the mint, so the style request (which MapLibre does NOT retry)
532
+ * is guaranteed to carry auth.
533
+ */
534
+ transformRequest(region?: string): (url: string) => TransformedRequest | undefined;
535
+ /** Clear the cached token + timer for a region. */
536
+ invalidate(region?: string): void;
537
+ /**
538
+ * Browser-only defense against `setTimeout` freezing while a tab is
539
+ * suspended (laptop sleep, mobile background, bfcache restore): proactively
540
+ * re-mint near-expiry entries on wake. Inert under SSR / Node / Deno.
541
+ */
542
+ private registerLifecycleHandlers;
543
+ }
544
+
545
+ /** Palette themes served by the Style API (S-52 colour tables). */
546
+ type ChrtStyleTheme = "day" | "dusk" | "night";
547
+ interface StyleUrlOptions {
548
+ /** Palette: "day" (default), "dusk", or "night". Omit to get the style's
549
+ * default — for keys with a bound CHRTstyle custom style, that's the
550
+ * style's default palette. */
551
+ theme?: ChrtStyleTheme;
552
+ }
553
+ interface FetchStyleOptions extends StyleUrlOptions {
554
+ signal?: AbortSignal;
555
+ }
437
556
  declare class TilesEndpoint {
438
557
  private http;
558
+ private tileAuth;
439
559
  private readonly region;
440
560
  private readonly tileBaseUrl;
441
- constructor(http: HttpClient, opts?: {
561
+ constructor(http: HttpClient, tileAuth: TileAuth, opts?: {
442
562
  region?: string;
443
563
  tileBaseUrl?: string;
444
564
  });
565
+ private styleEndpoint;
445
566
  /**
446
- * @deprecated CHRT has no remote `style.json` endpoint — the S-52 style is
447
- * generated client-side. Use `chrt.style(opts)` to get a ready-to-use
448
- * MapLibre style object instead. Calling this throws to prevent silently
449
- * pointing a map at a dead URL.
567
+ * URL of the served S-52 style for the configured region — the CHRTstyle
568
+ * Style API (`GET /v1/styles/{region}/style.json`). The served document
569
+ * honors the API key's bound custom style (a Dev+ feature configured at
570
+ * platform → Map Styles); Free keys and unbound keys get the default
571
+ * themes, identical to the locally generated `chrt.style()`.
572
+ *
573
+ * The endpoint is JWT-gated, so pair the URL with
574
+ * `chrt.transformRequest()`, which attaches the Bearer token to both the
575
+ * style request and its tile requests. This method is async because it
576
+ * pre-mints the tile token, guaranteeing the auth cache is warm before
577
+ * MapLibre fetches the URL.
578
+ *
579
+ * @example
580
+ * const map = new maplibregl.Map({
581
+ * container: 'map',
582
+ * style: await chrt.styleUrl({ theme: 'dusk' }),
583
+ * transformRequest: chrt.transformRequest(),
584
+ * });
585
+ */
586
+ styleUrl(opts?: StyleUrlOptions): Promise<string>;
587
+ /**
588
+ * Fetch the served S-52 style as a MapLibre style object (see
589
+ * {@link styleUrl} for what "served" means). Prefer this over `styleUrl()`
590
+ * when you want to inspect or mutate the document, or when you can't use
591
+ * `transformRequest` for the style request itself.
592
+ *
593
+ * Falls back nowhere: a network or auth failure throws (`ChrtError` /
594
+ * `TileTokenError`). For an offline-capable local fallback, use
595
+ * `chrt.style()` — the client-side generator.
450
596
  */
451
- styleUrl(): never;
597
+ fetchStyle(opts?: FetchStyleOptions): Promise<MapLibreStyle>;
452
598
  /**
453
599
  * Vector tile URL template for the configured region, served by the
454
600
  * tile-worker at `tiles.chrt.co`. Tiles are JWT-gated — pair this with
@@ -574,6 +720,468 @@ declare class OfflineEndpoint {
574
720
  private persistManifestEntry;
575
721
  }
576
722
 
723
+ /** Style-layer ids (CHRT S-52 atlas) that carry point nav-aid features. */
724
+ declare const NAV_AID_QUERY_LAYERS: string[];
725
+ /** Style-layer ids for area / hazard / regulated / infrastructure features. */
726
+ declare const AREA_QUERY_LAYERS: string[];
727
+ /**
728
+ * Bridge style layers, queried separately so bridges win over overlapping
729
+ * area polygons (click order: nav-aid → bridge → area).
730
+ */
731
+ declare const BRIDGE_QUERY_LAYERS: string[];
732
+ interface DecodedBridge {
733
+ name: string | null;
734
+ categoryCode: number | null;
735
+ categoryLabel: string | null;
736
+ verticalClearance: number | null;
737
+ verticalClearanceClosed: number | null;
738
+ verticalClearanceOpen: number | null;
739
+ horizontalClearance: number | null;
740
+ status: string | null;
741
+ condition: string | null;
742
+ information: string | null;
743
+ nationalName: string | null;
744
+ sourceIndication: string | null;
745
+ sourceDate: string | null;
746
+ }
747
+ /** Decode a BRIDGE feature's S-57 attributes into a typed record. */
748
+ declare function decodeBridgeProps(props: Record<string, unknown>): DecodedBridge;
749
+ /** A decoded label/value row, ready for direct display. */
750
+ interface DecodedProperty {
751
+ label: string;
752
+ value: string;
753
+ }
754
+ interface DecodedAreaFeature {
755
+ type: string;
756
+ title: string;
757
+ properties: DecodedProperty[];
758
+ }
759
+ /** Decode properties for area/hazard/regulated features into a human-readable record. */
760
+ declare function decodeAreaFeatureProps(layerName: string, props: Record<string, unknown>): DecodedAreaFeature;
761
+ interface SectorInfo {
762
+ sectorStart: number;
763
+ sectorEnd: number;
764
+ colour: string;
765
+ colourHex: string;
766
+ notation: string;
767
+ nominalRange?: number | null;
768
+ }
769
+ interface DecodedNavAid {
770
+ name: string;
771
+ /** Full official OBJNAM (e.g., "Boston South Channel Buoy 7") */
772
+ fullName: string | null;
773
+ type: string;
774
+ layerName: string;
775
+ colour: string | null;
776
+ colourHex: string;
777
+ /** Primary light colour hex (from co-located LIGHTS or own COLOUR for LIGHTS features) */
778
+ lightColourHex: string;
779
+ /** All light colours for alternating lights (e.g. ["#ffffff", "#ef4444"] for W R) */
780
+ colourHexList: string[];
781
+ shape: string | null;
782
+ topmark: string | null;
783
+ topmarkMeaning: string | null;
784
+ lightCharacter: string | null;
785
+ lightCharacterFull: string | null;
786
+ period: number | null;
787
+ group: number[] | null;
788
+ fogSignal: string | null;
789
+ fogSignalAbbr: string | null;
790
+ radarReflector: boolean;
791
+ depth: number | null;
792
+ status: string | null;
793
+ markingSystem: string | null;
794
+ category: string | null;
795
+ information: string | null;
796
+ notation: string;
797
+ coordinates: [number, number] | null;
798
+ sectors: SectorInfo[];
799
+ orientation: number | null;
800
+ isDirectional: boolean;
801
+ isSectorLight: boolean;
802
+ }
803
+ /**
804
+ * Parse ALL numeric codes from a list-type S-57 attribute (COLOUR, RESTRN, …).
805
+ * List attributes arrive as native arrays (["1","3"]) on the GeoJSON source
806
+ * path, JSON-stringified arrays ('["1","3"]') after tippecanoe/PMTiles, or
807
+ * comma/semicolon-separated scalars in legacy/normalized data.
808
+ */
809
+ declare function parseListCodes(val: unknown): number[];
810
+ /**
811
+ * Derive a mariner shorthand name from a verbose S-57 OBJNAM + COLOUR.
812
+ * e.g. "Weymouth Back River Channel Lighted Buoy 8" + COLOUR=3 → R "8"
813
+ */
814
+ declare function deriveMarinerName(objnam: string, colourCode: number | null): string;
815
+ /**
816
+ * What an S-57 TOPSHP topmark *denotes* given the parent buoy/beacon layer.
817
+ *
818
+ * IALA topmark shapes carry the same meaning in IALA-A and IALA-B regions —
819
+ * only the colour conventions differ between regions — so the meaning is a
820
+ * function of (TOPSHP, parent layer) and we don't need to consult MARSYS.
821
+ *
822
+ * Returns null when the shape/parent combination has no canonical meaning
823
+ * (e.g. a board topmark on a private daymark), so callers can simply skip
824
+ * rendering the hint.
825
+ */
826
+ declare function getTopmarkMeaning(topshpCode: number | null, rawLayer: string): string | null;
827
+ declare function decodeNavAidProps(layerName: string, props: Record<string, unknown>, coords?: [number, number]): DecodedNavAid;
828
+ interface FlashPhase {
829
+ on: boolean;
830
+ duration: number;
831
+ color?: string;
832
+ }
833
+ interface FlashPattern {
834
+ phases: FlashPhase[];
835
+ totalCycle: number;
836
+ notation: string;
837
+ }
838
+ declare function buildFlashPattern(decoded: DecodedNavAid): FlashPattern | null;
839
+ /** Minimal GeoJSON shapes for the sector-arc builders (kept dependency-free). */
840
+ interface SectorArcFeature {
841
+ type: "Feature";
842
+ properties: Record<string, unknown>;
843
+ geometry: {
844
+ type: "Polygon" | "LineString" | "Point";
845
+ coordinates: unknown;
846
+ };
847
+ }
848
+ interface SectorArcFeatureCollection {
849
+ type: "FeatureCollection";
850
+ features: SectorArcFeature[];
851
+ }
852
+ /**
853
+ * Build all sectors from multiple LIGHTS features at the same position.
854
+ * Each LIGHTS feature with SECTR1/SECTR2 defines one sector.
855
+ */
856
+ declare function buildSectorsFromFeatures(features: Array<{
857
+ properties: Record<string, unknown>;
858
+ }>): SectorInfo[];
859
+ /**
860
+ * Generate a GeoJSON arc polygon for a sector light.
861
+ * Bearings are TRUE bearings FROM the light (as seen FROM a vessel).
862
+ * S-57 SECTR1/SECTR2 define the arc as seen FROM seaward looking AT the light,
863
+ * so we draw arcs in bearing order from the light's position.
864
+ *
865
+ * When `innerRadiusNm > 0`, the polygon is an annulus segment (band) —
866
+ * a ring slice between the inner and outer radius — instead of a pie wedge.
867
+ *
868
+ * @param center [lng, lat]
869
+ * @param startBearing degrees true (clockwise from north)
870
+ * @param endBearing degrees true
871
+ * @param radiusNm outer radius in nautical miles
872
+ * @param innerRadiusNm inner radius in nautical miles (0 = pie wedge)
873
+ * @param steps number of arc segments
874
+ */
875
+ declare function buildSectorArcGeoJSON(center: [number, number], startBearing: number, endBearing: number, radiusNm?: number, innerRadiusNm?: number, steps?: number): SectorArcFeature;
876
+ /**
877
+ * Build a complete GeoJSON FeatureCollection of sector arcs for rendering.
878
+ * Each feature has color/notation properties for styling.
879
+ *
880
+ * Default behavior is a thin band (annulus segment) at the perimeter —
881
+ * sectors read as a colored ring around the light rather than a pie wedge.
882
+ * Set `innerRadiusFrac = 0` for the legacy pie-wedge appearance.
883
+ */
884
+ declare function buildSectorArcsFC(center: [number, number], sectors: SectorInfo[], radiusNm?: number, innerRadiusFrac?: number): SectorArcFeatureCollection;
885
+
886
+ /**
887
+ * Safety & attribution invariants for CHRTinteract.
888
+ *
889
+ * Per `.chrt/ai-agent-guide.md` §"Safety & Disclaimers" and the platform's
890
+ * non-negotiable stewardship invariants, EVERY InspectResult and the built-in
891
+ * popup MUST carry:
892
+ * 1. the non-SOLAS "not for primary navigation" disclaimer,
893
+ * 2. issuing hydrographic-office attribution, and
894
+ * 3. data currency (edition / update_number / issue_date once known).
895
+ *
896
+ * Wording matches the canonical text in `@chrtco/mcp` (`safety.ts`) so it can
897
+ * never drift between surfaces. Do not weaken or remove.
898
+ */
899
+
900
+ /** The canonical non-SOLAS disclaimer. Present on every InspectResult. */
901
+ declare const NON_SOLAS_DISCLAIMER: string;
902
+ /** Short form for space-constrained UI (the built-in popup footer). */
903
+ declare const NON_SOLAS_DISCLAIMER_SHORT = "Not for primary navigation";
904
+ /** Safety, attribution, and currency context attached to every InspectResult. */
905
+ interface InspectNotice {
906
+ /** Non-SOLAS disclaimer — always `NON_SOLAS_DISCLAIMER`. */
907
+ disclaimer: string;
908
+ /** Issuing hydrographic-office / source attribution line. */
909
+ attribution: string;
910
+ /**
911
+ * Data-currency line. Before enrichment this states how to resolve currency;
912
+ * after `inspect({ enrich: true })` it carries the source cell's edition,
913
+ * update number, and issue date.
914
+ */
915
+ dataCurrency: string;
916
+ }
917
+
918
+ /**
919
+ * Public types for CHRTinteract — click-to-inspect for CHRT charts.
920
+ */
921
+
922
+ /**
923
+ * A chart feature to inspect. Structurally compatible with a MapLibre / Mapbox
924
+ * GL `MapGeoJSONFeature` (what `queryRenderedFeatures` returns), and with a
925
+ * plain `{ layer, properties }` object for headless use.
926
+ */
927
+ interface InspectableFeature {
928
+ /** Vector-tile attributes (LITCHR, COLOUR, OBJNAM, …). */
929
+ properties?: Record<string, unknown> | null;
930
+ /** MVT source-layer name, e.g. "BOYLAT" (set by queryRenderedFeatures). */
931
+ sourceLayer?: string;
932
+ /** Style layer that rendered the feature, or a raw layer name string. */
933
+ layer?: {
934
+ id?: string;
935
+ } | string;
936
+ /** GeoJSON geometry — Point coordinates locate the popup / enrichment. */
937
+ geometry?: {
938
+ type?: string;
939
+ coordinates?: unknown;
940
+ } | null;
941
+ }
942
+ interface InspectOptions {
943
+ /**
944
+ * Override the layer used for decoding (style-layer id like "enc-BOYLAT" or
945
+ * raw S-57 name like "BOYLAT"). Defaults to the feature's own layer.
946
+ */
947
+ layer?: string;
948
+ /** Coordinates [lng, lat] when the feature's geometry doesn't carry them. */
949
+ coordinates?: [number, number];
950
+ /**
951
+ * Fetch full S-57 properties + source-cell provenance (edition, update,
952
+ * issue date) through the CHRT features API. **Metered** — counts against
953
+ * the key's monthly `featureCalls` quota.
954
+ */
955
+ enrich?: boolean;
956
+ /** AbortSignal for the enrichment fetches. */
957
+ signal?: AbortSignal;
958
+ }
959
+ /** Source-cell provenance + full properties resolved by `enrich: true`. */
960
+ interface InspectEnrichment {
961
+ /** Whether a matching feature was found in the features API. */
962
+ matched: boolean;
963
+ /** Full S-57 properties from the source data (superset of tile attrs). */
964
+ properties: Record<string, unknown> | null;
965
+ /** The ENC cell the feature came from. */
966
+ cellId: string | null;
967
+ /** Cell metadata — edition / update_number / issue_date live here. */
968
+ cell: CellDetail | null;
969
+ }
970
+ interface InspectResultCommon {
971
+ /** Raw S-57 object class, e.g. "BOYLAT". */
972
+ layer: string;
973
+ /** The style-layer id the feature was resolved from, e.g. "enc-BOYLAT". */
974
+ layerId: string;
975
+ /** Human-readable object class, e.g. "Lateral Buoy". */
976
+ objectClass: string;
977
+ /** Display title (mariner shorthand, OBJNAM, or object class). */
978
+ title: string;
979
+ /** [lng, lat] when known. */
980
+ coordinates: [number, number] | null;
981
+ /** Decoded, display-ready label/value rows. */
982
+ properties: DecodedProperty[];
983
+ /** The merged tile attributes the decode ran on. */
984
+ raw: Record<string, unknown>;
985
+ /** Non-SOLAS disclaimer + HO attribution + data currency. Always present. */
986
+ notice: InspectNotice;
987
+ /** Present when `inspect({ enrich: true })` was used. */
988
+ enrichment: InspectEnrichment | null;
989
+ }
990
+ /** A decoded navigational aid (buoy, beacon, light, fog signal, landmark…). */
991
+ interface NavAidInspectResult extends InspectResultCommon {
992
+ kind: "navaid";
993
+ /** Full decoded nav-aid record (colours, topmark, sectors, fog, status…). */
994
+ navAid: DecodedNavAid;
995
+ /** Mariner light characteristic, e.g. "Fl(2+1) W 10s". Null when unlit. */
996
+ characteristic: string | null;
997
+ /** Flash/eclipse timing phases for animating the light. Null when unlit. */
998
+ flashPattern: FlashPattern | null;
999
+ }
1000
+ /** A decoded bridge (clearances, category, condition). */
1001
+ interface BridgeInspectResult extends InspectResultCommon {
1002
+ kind: "bridge";
1003
+ bridge: DecodedBridge;
1004
+ }
1005
+ /** A decoded area / hazard / regulated / infrastructure feature. */
1006
+ interface AreaInspectResult extends InspectResultCommon {
1007
+ kind: "area";
1008
+ area: DecodedAreaFeature;
1009
+ }
1010
+ type InspectResult = NavAidInspectResult | BridgeInspectResult | AreaInspectResult;
1011
+ /** Successful server-side entitlement check. */
1012
+ interface InteractEntitlement {
1013
+ enabled: true;
1014
+ /** The team's plan id (e.g. "dev", "pro"), when the server includes it. */
1015
+ plan?: string;
1016
+ }
1017
+ /**
1018
+ * Structural subset of the MapLibre / Mapbox GL `Map` interface used by
1019
+ * `attach()`. Declared structurally so `maplibre-gl` stays an optional peer
1020
+ * dependency — any GL JS map instance satisfies it.
1021
+ */
1022
+ interface InteractMapLike {
1023
+ on(event: string, listener: (ev: InteractMapMouseEvent) => void): unknown;
1024
+ off(event: string, listener: (ev: InteractMapMouseEvent) => void): unknown;
1025
+ queryRenderedFeatures(pointOrBox?: unknown, options?: {
1026
+ layers?: string[];
1027
+ }): InspectableFeature[];
1028
+ getLayer(id: string): unknown;
1029
+ getContainer(): HTMLElement;
1030
+ project(lngLat: [number, number]): {
1031
+ x: number;
1032
+ y: number;
1033
+ };
1034
+ }
1035
+ /** The subset of a GL JS MapMouseEvent that attach() consumes. */
1036
+ interface InteractMapMouseEvent {
1037
+ point: {
1038
+ x: number;
1039
+ y: number;
1040
+ };
1041
+ lngLat: {
1042
+ lng: number;
1043
+ lat: number;
1044
+ };
1045
+ }
1046
+ /** Context passed to `onInspect` alongside the result. */
1047
+ interface InspectEventContext {
1048
+ /** Click location. */
1049
+ lngLat: {
1050
+ lng: number;
1051
+ lat: number;
1052
+ };
1053
+ /** Click location in screen pixels. */
1054
+ point: {
1055
+ x: number;
1056
+ y: number;
1057
+ };
1058
+ /** The rendered feature the result was decoded from. */
1059
+ feature: InspectableFeature;
1060
+ }
1061
+ interface AttachOptions {
1062
+ /**
1063
+ * Receive every InspectResult (headless mode). Fires whether or not the
1064
+ * built-in popup is enabled.
1065
+ */
1066
+ onInspect?: (result: InspectResult, ctx: InspectEventContext) => void;
1067
+ /** Called when a click hits no inspectable feature. */
1068
+ onMiss?: (ctx: {
1069
+ lngLat: {
1070
+ lng: number;
1071
+ lat: number;
1072
+ };
1073
+ }) => void;
1074
+ /**
1075
+ * Show the built-in minimal popup (plain DOM, no framework). Default: true
1076
+ * when `onInspect` is not provided, false otherwise.
1077
+ */
1078
+ popup?: boolean;
1079
+ /**
1080
+ * Auto-enrich every click through the features API (metered — counts
1081
+ * against the key's `featureCalls` quota). Default: false.
1082
+ */
1083
+ enrich?: boolean;
1084
+ /** Override the queried nav-aid style-layer ids. */
1085
+ navAidLayers?: string[];
1086
+ /** Override the queried bridge style-layer ids. */
1087
+ bridgeLayers?: string[];
1088
+ /** Override the queried area style-layer ids. */
1089
+ areaLayers?: string[];
1090
+ }
1091
+ /** Handle returned by `attach()`. */
1092
+ interface InteractHandle {
1093
+ /** Remove the click handler and any open popup. */
1094
+ detach(): void;
1095
+ }
1096
+
1097
+ /**
1098
+ * Typed errors for the CHRTinteract flow. Mirrors the `TileTokenError` /
1099
+ * `OfflineError` pattern: a `code` discriminator so callers can branch
1100
+ * messaging (upgrade prompt vs. auth failure vs. transient network).
1101
+ */
1102
+ type InteractErrorCode =
1103
+ /** The team's plan does not include CHRTinteract (Dev tier and above). */
1104
+ "interact_not_enabled"
1105
+ /** The API key was rejected (unknown, disabled, or expired). */
1106
+ | "invalid_api_key"
1107
+ /** The key was explicitly narrowed to scopes that exclude interact. */
1108
+ | "missing_scope"
1109
+ /** Feature enrichment failed (features/cells API error). */
1110
+ | "enrich_failed"
1111
+ /** The inspected feature could not be resolved to a chart layer. */
1112
+ | "unknown_layer"
1113
+ /** Transport-level failure reaching the entitlement endpoint. */
1114
+ | "network_error"
1115
+ /** The server answered with a shape the SDK doesn't recognize. */
1116
+ | "unexpected_response";
1117
+ declare class InteractError extends Error {
1118
+ readonly code: InteractErrorCode;
1119
+ readonly status?: number;
1120
+ /** The team's current plan id, when the server included it (denials). */
1121
+ readonly plan?: string;
1122
+ constructor(code: InteractErrorCode, message: string, opts?: {
1123
+ status?: number;
1124
+ plan?: string;
1125
+ });
1126
+ }
1127
+
1128
+ /**
1129
+ * CHRTinteract — productized click-to-inspect for CHRT charts.
1130
+ *
1131
+ * `chrt.interact.attach(map)` wires click → decode → popup/callback onto any
1132
+ * MapLibre / Mapbox GL map showing CHRT tiles; `chrt.interact.inspect(feature)`
1133
+ * decodes a single feature headlessly. Both enforce the Dev-tier-and-above
1134
+ * entitlement server-side before doing any work, and every result carries the
1135
+ * non-SOLAS disclaimer, hydrographic-office attribution, and data currency.
1136
+ *
1137
+ * Included with the Dev plan and above. Free-plan keys receive an
1138
+ * `InteractError` with code `interact_not_enabled`.
1139
+ */
1140
+
1141
+ declare class InteractClient {
1142
+ private readonly http;
1143
+ private readonly entitlementClient;
1144
+ constructor(http: HttpClient);
1145
+ /**
1146
+ * Check the CHRTinteract entitlement for this API key (server-side, plan
1147
+ * derived, cached per client). Throws `InteractError` with code
1148
+ * `interact_not_enabled` for Free-plan teams.
1149
+ */
1150
+ entitlement(): Promise<InteractEntitlement>;
1151
+ /**
1152
+ * Decode a chart feature (from `queryRenderedFeatures`, the features API,
1153
+ * or plain `{ layer, properties }`) into a typed {@link InspectResult}.
1154
+ *
1155
+ * The first call performs the server-side entitlement check. With
1156
+ * `{ enrich: true }` the result is upgraded with full S-57 properties and
1157
+ * source-cell provenance (edition / update / issue date) via the features
1158
+ * API — metered against the key's `featureCalls` quota.
1159
+ *
1160
+ * @example
1161
+ * const result = await chrt.interact.inspect(feature);
1162
+ * if (result.kind === "navaid") console.log(result.characteristic); // "Fl R 4s"
1163
+ */
1164
+ inspect(feature: InspectableFeature, opts?: InspectOptions): Promise<InspectResult>;
1165
+ /**
1166
+ * Attach click-to-inspect to a MapLibre / Mapbox GL map rendering CHRT
1167
+ * tiles. Resolves once the server-side entitlement check passes; rejects
1168
+ * with `InteractError` (`interact_not_enabled` on Free plans) otherwise —
1169
+ * no listeners are wired on denial.
1170
+ *
1171
+ * Headless-first: pass `onInspect` to receive every decoded
1172
+ * {@link InspectResult} and render your own UI. Without `onInspect`, the
1173
+ * built-in minimal popup is shown (opt back in explicitly with
1174
+ * `popup: true` to have both).
1175
+ *
1176
+ * @example
1177
+ * const handle = await chrt.interact.attach(map, {
1178
+ * onInspect: (result) => console.log(result.title, result.characteristic),
1179
+ * });
1180
+ * // later: handle.detach();
1181
+ */
1182
+ attach(map: InteractMapLike, opts?: AttachOptions): Promise<InteractHandle>;
1183
+ }
1184
+
577
1185
  /** S-52 display categories – controls default visibility */
578
1186
  type DisplayCategory = "BASE" | "STANDARD" | "OTHER" | "MARINERS" | "META";
579
1187
  /** Mapbox GL layer type we'll use for this entry */
@@ -759,6 +1367,72 @@ type LabelBehavior = "feature-band-gated" | "orientation-cross-band";
759
1367
  declare const LABEL_BEHAVIOR: Record<string, LabelBehavior>;
760
1368
  /** Returns the rendering strategy for an S-57 class. Unknown classes default to band-gated. */
761
1369
  declare function getLabelBehavior(layerName: string): LabelBehavior;
1370
+ /**
1371
+ * Class-based label priority ladder — THE single collision-priority model
1372
+ * for feature labels (portrayal-excellence P4). Converges the previous
1373
+ * per-mechanism patches (SAFETY_CRITICAL_LABELS allow-overlap, the
1374
+ * band-5/6-only Harbor collision discipline) into one explicit ranking.
1375
+ *
1376
+ * Rank semantics: LOWER rank = higher priority. The rank drives two
1377
+ * mechanisms that must stay in agreement:
1378
+ *
1379
+ * 1. `symbol-sort-key` — every feature label carries
1380
+ * `rank*1000 + within-class-key`, so if label layers are ever merged
1381
+ * (the P8 style fan-out reduction), placement priority survives the
1382
+ * merge unchanged.
1383
+ * 2. Label LAYER ORDER — MapLibre places symbols topmost-layer-first, so
1384
+ * `style-generator.ts` emits feature-label layers lowest-priority
1385
+ * first: safety-critical classes end up topmost and claim collision
1386
+ * space before anything else. (`symbol-sort-key` alone cannot do
1387
+ * this — it only orders placement WITHIN a layer.)
1388
+ *
1389
+ * The ladder (safety-critical > nav-aids > depth field > structures >
1390
+ * ambient notes):
1391
+ *
1392
+ * 100 — Safety-critical: light characteristics (LIGHTS), hazard depth
1393
+ * callouts (UWTROC/OBSTRN/WRECKS VALSOU), overhead clearances
1394
+ * (CBLOHD/PIPOHD VERCLR). Irreducible mariner-safety text.
1395
+ * 200 — Nav-aid designations: buoy / beacon `R "4"` labels.
1396
+ * 250 — Auxiliary nav-aid info: fog signal type (FOGSIG).
1397
+ * 300 — Depth field: soundings, contour values. Individually droppable —
1398
+ * a hidden sounding has visible neighbors.
1399
+ * 400 — Structures: bridges, landmarks, piers, moorings, buildings.
1400
+ * 500 — Ambient annotation: seabed nature, magnetic variation, kelp.
1401
+ * 900 — Orientation names (SEAARE etc.) — not part of this ladder;
1402
+ * they keep their settled watermark model (cell-band fan +
1403
+ * dominance composition + `orientationLabelLayout`) and always
1404
+ * paint underneath feature labels.
1405
+ *
1406
+ * Safety guardrail: a lower rung never blocks a higher rung — placement
1407
+ * order guarantees the safety-critical label got the space first. Density
1408
+ * relief comes from dropping the LOWEST rung in a pile-up, never the top.
1409
+ */
1410
+ declare const LABEL_PRIORITY: Record<string, number>;
1411
+ /**
1412
+ * Resolve the priority rank for an S-57 class's label. Orientation classes
1413
+ * report 900 regardless of ladder membership (PRDARE etc.) so the
1414
+ * layer-order sort in style-generator.ts can treat priority uniformly.
1415
+ */
1416
+ declare function getLabelPriority(layerName: string): number;
1417
+ /**
1418
+ * Compose a `symbol-sort-key` from a class rank and an optional
1419
+ * within-class key expression. The within-class key is clamped to
1420
+ * [0, 999] so it can never bleed into a neighboring rank's band.
1421
+ */
1422
+ declare function labelSortKey(rank: number, within?: unknown): ExpressionSpecification | number;
1423
+ /**
1424
+ * Extract the trailing designation token from a nav-aid OBJNAM.
1425
+ *
1426
+ * NOAA names channel aids "<waterway> <type> Buoy|Beacon <designation>"
1427
+ * where the designation is 1-4 alphanumerics: "4", "18A", "B", "BG".
1428
+ * Returns null when the name has no designation (trailing token is a
1429
+ * stopword type-name, or longer than 4 chars).
1430
+ *
1431
+ * This is the TS twin of `navAidDesignationTextExpression` — the GeoJSON
1432
+ * preprocessing path uses this function, the PMTiles path uses the GL
1433
+ * expression. Keep the two in agreement (see label-priority.test.ts).
1434
+ */
1435
+ declare function extractNavAidDesignation(objnam: string): string | null;
762
1436
  /** Get label config for a given S-57 layer, or null if no labels */
763
1437
  declare function getLabelConfig(layerName: string, opts?: LabelConfigOptions): LabelLayerConfig | null;
764
1438
  /**
@@ -1077,7 +1751,7 @@ declare function getS52SymbolStats(): {
1077
1751
  * constants are the outer envelope the MapLibre source declares, so the
1078
1752
  * client doesn't request tiles outside what the pmtiles can actually serve.
1079
1753
  */
1080
- declare const BUILD_MINZOOM = 3;
1754
+ declare const BUILD_MINZOOM = 0;
1081
1755
  declare const BUILD_MAXZOOM = 14;
1082
1756
 
1083
1757
  /**
@@ -1205,24 +1879,39 @@ interface BandPriorityRange {
1205
1879
  * (drawn last in the z-stack, so its opaque fills cover lower bands),
1206
1880
  * and lower bands fill in where the higher band has no coverage.
1207
1881
  *
1208
- * Cross-band visual overlap (doubled coastlines, blended fills) is
1882
+ * Cross-band visual overlap (doubled coastlines, duplicate labels) is
1209
1883
  * suppressed by the `_dominated_by_min` per-feature tag added at NDJSON
1210
1884
  * export time (see `tools/chrt-baremetal/server/src/tilebuild/dominanceIndex.ts`):
1211
1885
  * every feature whose centroid sits inside a higher-band cell's M_COVR
1212
1886
  * coverage polygon is tagged with the LOWEST band code (2..6) that
1213
1887
  * dominates it, and `makeBandVariants()` in the style generator emits
1214
1888
  * per-(featureBand, dominatorBand) layer variants gated by `maxzoom =
1215
- * BAND_PRIORITY_RANGES[D].minzoom`. The dominated feature renders until
1216
- * exactly the zoom where its dominator becomes eligible — solving the
1217
- * low-zoom "black silhouettes" bug where a band-3 LNDARE dominated by
1218
- * a band-5 cell was dropped at z7 even though band 5 wasn't yet rendering
1219
- * at z7. At any pixel, exactly ONE band's data renders — the
1220
- * most-detailed band whose minzoom has been reached and whose cells
1221
- * actually cover that pixel.
1889
+ * dominatedVariantMaxzoom(D)`. The dominated feature renders until
1890
+ * exactly the zoom where its dominator becomes eligible AND has data in
1891
+ * the tiles — solving the low-zoom "black silhouettes" bug where a
1892
+ * band-3 LNDARE dominated by a band-5 cell was dropped at z7 even
1893
+ * though band 5 wasn't yet rendering at z7.
1894
+ *
1895
+ * Opaque base-area FILLS are exempt from dominance gating entirely (see
1896
+ * `DOMINANCE_EXEMPT_FILL_CLASSES` in the style generator): band-major
1897
+ * draw order already makes the most-detailed band's opaque fill win at
1898
+ * any covered pixel, and the coarser band shows through wherever the
1899
+ * detailed band has no data — which is exactly the fallback semantics
1900
+ * the cascade wants. The 2026-07 E1/E2 void diagnosis
1901
+ * (docs/validation/portrayal-p1-p3-void-diagnosis-2026-07.md) proved
1902
+ * per-feature dominance gating on fills structurally unsound: the tag is
1903
+ * point-sampled but hides the WHOLE feature, so a Gulf-spanning band-1
1904
+ * DEPARE tagged dominated-by-2 left dark voids across every part of the
1905
+ * Gulf that band 2 doesn't actually cover.
1222
1906
  *
1223
1907
  * Bands 5 and 6 both start at z11; band 6 (Berthing) is drawn AFTER
1224
1908
  * band 5 (Harbor) so where Berthing exists it visually wins, and
1225
1909
  * Harbor shows through everywhere else.
1910
+ *
1911
+ * Band 1 starts at z0: the P3 low-zoom contract (charts never disappear
1912
+ * from world view down) needs Overview-band data renderable as soon as
1913
+ * tiles exist. Builds older than the z0 floor simply have no tiles below
1914
+ * z3, so the widened range is a no-op against them.
1226
1915
  */
1227
1916
  declare const BAND_PRIORITY_RANGES: Record<BandCode, BandPriorityRange>;
1228
1917
  /**
@@ -1386,108 +2075,210 @@ declare function getStyleStats(style: MapboxStyle): {
1386
2075
  };
1387
2076
 
1388
2077
  /**
1389
- * Tile authentication for the `tiles.chrt.co` PMTiles path.
2078
+ * CHRTstyle custom styles — the customer-facing map_styles config applied
2079
+ * on top of the generated S-52 style.
1390
2080
  *
1391
- * The CHRT tile-worker serves raw MVT at
1392
- * `https://tiles.chrt.co/v1/tiles/{region}/{z}/{x}/{y}.mvt`
1393
- * and authenticates each request with a short-lived ES256 JWT (5-min default,
1394
- * 15-min max) carried in the `Authorization: Bearer` header. This module mints
1395
- * those JWTs from the customer's API key and produces a MapLibre / Mapbox GL
1396
- * `transformRequest` that attaches the Bearer to tile requests only.
2081
+ * A team's `map_styles` row (platform → Map Styles) stores S-52 rendering
2082
+ * preferences: which colour tables are enabled, the default palette, the
2083
+ * S-52 display mode, per-group visibility/scale/collision knobs, and an
2084
+ * optional layer whitelist. Keys bind a style via `api_keys.map_style_id`;
2085
+ * `mint-tile-token` embeds `style_id` + `style_rev` claims for entitled
2086
+ * (Dev+) plans, and the tile-worker's `/v1/styles/{region}/style.json`
2087
+ * route resolves the config and calls `generateCustomStyle()` here.
1397
2088
  *
1398
- * Responsibilities (mirrors the first-party web client at
1399
- * `src/lib/map/tile-token-client.ts`, adapted to be instance-scoped and
1400
- * zero-dependency so it runs in browsers, Node 18+, Deno, and Bun):
1401
- * - Mint via `POST {baseUrl}/mint/tile-token` (default
1402
- * `https://api.chrt.co/v1/mint/tile-token`) with the `x-api-key` header.
1403
- * - Cache tokens in memory, keyed by region; refresh silently 30s before
1404
- * expiry so long-lived maps never flicker.
1405
- * - Expose a SYNC `transformRequest` (MapLibre requires sync) that attaches
1406
- * the cached Bearer; it kicks off a background mint on first use so the
1407
- * cache warms within one round-trip.
2089
+ * This module is the single source of truth for:
2090
+ * - the settings shapes (`NavAidSettings` etc. — the platform app
2091
+ * re-exports them from `src/lib/map/nav-aid-settings.ts`),
2092
+ * - sanitization of the stored JSONB (never trust row contents: the
2093
+ * table has no CHECK constraints, so clamp + whitelist everything),
2094
+ * - the static application of those settings to a generated style.
1408
2095
  *
1409
- * NOTE: the public mint route `POST /v1/mint/tile-token` (the sibling of the
1410
- * existing `/v1/mint/offline-token`) must be present on the `api.chrt.co`
1411
- * edge worker for this path to serve. Until it lands, mints fail with a
1412
- * network/HTTP error surfaced through `getToken()`.
2096
+ * Safety invariants honored here:
2097
+ * - `allowOverlap: false` (the default) leaves the generator's S-52
2098
+ * collision discipline untouched — safety-critical classes keep their
2099
+ * forced `*-allow-overlap: true`. `allowOverlap: true` only ever ADDS
2100
+ * visibility, never removes it.
2101
+ * - Sanitization can only narrow values into UI-legal ranges; unknown
2102
+ * fields are dropped, so a hand-crafted row can't smuggle arbitrary
2103
+ * style JSON into served output.
1413
2104
  */
1414
2105
 
1415
- /** Default chart region — the one consolidated PMTiles served per region. */
1416
- declare const DEFAULT_TILE_REGION = "NOAA_ENC";
1417
- /** Default tile-worker origin. Override via `ChrtConfig.tileBaseUrl`. */
1418
- declare const DEFAULT_TILE_BASE_URL = "https://tiles.chrt.co";
1419
- /** Full token response from the mint endpoint. */
1420
- interface TileTokenResponse {
1421
- /** ES256-signed JWT; do not log. */
1422
- token: string;
1423
- /** Unix seconds (matches JWT `exp`). */
1424
- expiresAt: number;
1425
- /** Key id used to sign (matches JWT header `kid`). */
1426
- kid: string;
1427
- /** Build id/tag pinned into the JWT — the tile-worker cache key. */
1428
- buildTag: string;
1429
- /** Echo of the region the token is scoped to. */
1430
- region: string;
1431
- }
1432
- /** Typed error codes the mint flow surfaces; callers switch on `.code`. */
1433
- type TileTokenErrorCode = "invalid_api_key" | "feature_flag_disabled" | "region_not_licensed" | "tile_quota_exceeded" | "region_not_available" | "network_error" | "unexpected_response";
1434
- declare class TileTokenError extends Error {
1435
- readonly code: TileTokenErrorCode;
1436
- readonly status?: number;
1437
- constructor(code: TileTokenErrorCode, message: string, status?: number);
1438
- }
1439
- /** MapLibre / Mapbox GL `transformRequest` return shape. */
1440
- interface TransformedRequest {
1441
- url: string;
1442
- headers?: Record<string, string>;
1443
- }
1444
- /**
1445
- * Instance-scoped tile-token manager. One per `Chrt` client so multiple
1446
- * clients (e.g. different API keys) never share a cache.
1447
- */
1448
- declare class TileAuth {
1449
- private readonly http;
1450
- private readonly cache;
1451
- private readonly pending;
1452
- private readonly defaultRegion;
1453
- private readonly tileBaseUrl;
1454
- private lifecycleRegistered;
1455
- constructor(http: HttpClient, opts?: {
1456
- region?: string;
1457
- tileBaseUrl?: string;
1458
- });
1459
- /** Mint once via the HTTP client (POST `/mint/tile-token`, `x-api-key`). */
1460
- private mintOnce;
1461
- /** Mint with exponential backoff on network errors only. */
1462
- private mintWithRetry;
1463
- private scheduleRefresh;
1464
- private refreshInBackground;
1465
- private store;
2106
+ interface NavAidSettings {
2107
+ buoysVisible: boolean;
2108
+ beaconsVisible: boolean;
2109
+ lightsVisible: boolean;
2110
+ /** null = S-52 default, 0 = always show, 1-14 = custom minzoom */
2111
+ scaminOverride: number | null;
2112
+ /** Icon size multiplier (0.2–2.0) */
2113
+ iconScale: number;
2114
+ /** When true, icon-size is a flat value (no zoom interpolation) */
2115
+ iconSizeLocked: boolean;
2116
+ /** Allow icons to overlap at all zooms */
2117
+ allowOverlap: boolean;
2118
+ /** Show sector light arcs on map */
2119
+ showSectorArcs: boolean;
2120
+ /** Show fog signal icons */
2121
+ fogSignalsVisible: boolean;
2122
+ /** Show radar transponder / radar station icons */
2123
+ radarVisible: boolean;
2124
+ }
2125
+ interface LabelSettings {
2126
+ navAidLabelsVisible: boolean;
2127
+ landLabelsVisible: boolean;
2128
+ structureLabelsVisible: boolean;
2129
+ /** Show depth contour (DEPCNT) value labels */
2130
+ depthContoursVisible: boolean;
2131
+ /** Show full OBJNAM instead of mariner shorthand for nav aids */
2132
+ navAidFullNames: boolean;
2133
+ /** null = use default minzoom, 0-14 = custom override */
2134
+ minzoomOverride: number | null;
2135
+ /** Text size multiplier (0.5–2.0) */
2136
+ textScale: number;
2137
+ /** Allow text labels to overlap */
2138
+ allowOverlap: boolean;
2139
+ }
2140
+ interface SoundingSettings {
2141
+ visible: boolean;
2142
+ minzoomOverride: number | null;
2143
+ textScale: number;
2144
+ allowOverlap: boolean;
2145
+ /** Show depth units suffix */
2146
+ showUnits: boolean;
1466
2147
  /**
1467
- * Fetch (or reuse a cached) tile token for `region`. Concurrent callers for
1468
- * the same region await the same in-flight mint.
2148
+ * S-52 §SOUNDG02 safety contour (metres). Soundings shoaler than this
2149
+ * render in high-contrast SNDG2; deeper soundings render in muted SNDG1.
2150
+ * Default 10 m.
1469
2151
  */
1470
- getToken(region?: string): Promise<TileTokenResponse>;
1471
- /** Synchronous cache peek — null if absent or within the stale grace window. */
1472
- peek(region?: string): string | null;
2152
+ safetyContour: number;
2153
+ /** Numeral weight; semibold reads sharper at small sizes on screen. */
2154
+ fontWeight: "regular" | "semibold";
2155
+ /** When false, all soundings render in a single shade (SNDG2). */
2156
+ twoShade: boolean;
2157
+ }
2158
+ interface WaterBodySettings {
2159
+ seaAreasVisible: boolean;
2160
+ lakesVisible: boolean;
2161
+ riversVisible: boolean;
2162
+ canalsVisible: boolean;
2163
+ minzoomOverride: number | null;
2164
+ textScale: number;
2165
+ allowOverlap: boolean;
2166
+ /** Uppercase major sea area names (raster chart style) */
2167
+ uppercaseSeaAreas: boolean;
2168
+ }
2169
+ declare const DEFAULT_NAV_AID_SETTINGS: NavAidSettings;
2170
+ declare const DEFAULT_LABEL_SETTINGS: LabelSettings;
2171
+ declare const DEFAULT_SOUNDING_SETTINGS: SoundingSettings;
2172
+ declare const DEFAULT_WATER_BODY_SETTINGS: WaterBodySettings;
2173
+ /** Nav-aid layer groups keyed by toggle name */
2174
+ declare const NAV_AID_LAYER_GROUPS: Record<string, string[]>;
2175
+ /** All nav-aid layer names (flat set for quick lookups) */
2176
+ declare const ALL_NAV_AID_LAYERS: Set<string>;
2177
+ /** Label layer groups keyed by toggle name (soundings moved to own panel) */
2178
+ declare const LABEL_LAYER_GROUPS: Record<string, string[]>;
2179
+ /** Sounding layers */
2180
+ declare const SOUNDING_LAYERS: string[];
2181
+ /** Water body layer groups keyed by toggle name */
2182
+ declare const WATER_BODY_LAYER_GROUPS: Record<string, string[]>;
2183
+ /** All water body layer names (flat set for quick lookups) */
2184
+ declare const ALL_WATER_BODY_LAYERS: Set<string>;
2185
+ /** Check if a nav-aid layer is visible given current settings */
2186
+ declare function isNavAidLayerVisible(layerName: string, settings: NavAidSettings): boolean;
2187
+ /** Check if a label layer is visible given current settings */
2188
+ declare function isLabelLayerVisible(layerName: string, settings: LabelSettings): boolean;
2189
+ /** Check if a sounding layer is visible */
2190
+ declare function isSoundingLayerVisible(settings: SoundingSettings): boolean;
2191
+ /** Check if a water body layer is visible given current settings */
2192
+ declare function isWaterBodyLayerVisible(layerName: string, settings: WaterBodySettings): boolean;
2193
+ interface CustomStyleConfig {
2194
+ /** Palettes the customer allows their end users to request. Never empty. */
2195
+ enabled_color_tables: ColorTableName[];
2196
+ /** Palette served when the request omits `theme` (or requests a disabled one). */
2197
+ default_color_table: ColorTableName;
1473
2198
  /**
1474
- * Produce a MapLibre / Mapbox GL `transformRequest`. Attaches
1475
- * `Authorization: Bearer <jwt>` only to CHRT tile URLs; style, sprite, and
1476
- * glyph URLs pass through untouched (never leak the token to third-party
1477
- * CDNs). Kicks off a background mint immediately so the cache warms within
1478
- * one round-trip; the first tile or two may go unauthenticated and be
1479
- * retried by the renderer.
2199
+ * S-52 display mode: BASE ⊂ STANDARD ⊂ ALL. Carried through sanitization
2200
+ * but NOT applied as a render filter in v1: the platform's vector map
2201
+ * renders every class regardless of mode (the mode only filters the /map
2202
+ * sidebar list), and several CHRT-structural classes (e.g. SEAARE, the
2203
+ * cross-band base water fill) sit in category OTHER — filtering them out
2204
+ * under the DB-default `STANDARD` would serve charts with the base water
2205
+ * missing. Render-filtering by display category is a documented follow-up
2206
+ * that needs its own category audit first.
1480
2207
  */
1481
- transformRequest(region?: string): (url: string) => TransformedRequest | undefined;
1482
- /** Clear the cached token + timer for a region. */
1483
- invalidate(region?: string): void;
1484
- /**
1485
- * Browser-only defense against `setTimeout` freezing while a tab is
1486
- * suspended (laptop sleep, mobile background, bfcache restore): proactively
1487
- * re-mint near-expiry entries on wake. Inert under SSR / Node / Deno.
1488
- */
1489
- private registerLifecycleHandlers;
2208
+ display_mode: DisplayMode;
2209
+ nav_aid_settings: NavAidSettings;
2210
+ label_settings: LabelSettings;
2211
+ sounding_settings: SoundingSettings;
2212
+ water_body_settings: WaterBodySettings;
2213
+ /** Whitelist of S-57 classes to render; null = all. */
2214
+ visible_layers: string[] | null;
2215
+ }
2216
+ declare const DEFAULT_CUSTOM_STYLE_CONFIG: CustomStyleConfig;
2217
+ /**
2218
+ * Sanitize a raw `map_styles` row (or any untrusted JSON) into a fully
2219
+ * populated `CustomStyleConfig`. Unknown fields are dropped, enums are
2220
+ * validated, numerics are clamped to the UI's legal ranges, and missing
2221
+ * fields fall back to the platform defaults. Never throws.
2222
+ */
2223
+ declare function sanitizeCustomStyleConfig(raw: unknown): CustomStyleConfig;
2224
+ /** Normalize a `?theme=` value to a colour table name; null for anything else. */
2225
+ declare function normalizeTheme(raw: string | null | undefined): ColorTableName | null;
2226
+ interface ResolvedTheme {
2227
+ palette: ColorTableName;
2228
+ /** True when the requested theme was disabled by the custom style and the
2229
+ * default palette was served instead. */
2230
+ themeFellBack: boolean;
2231
+ }
2232
+ /**
2233
+ * Pick the palette to serve. A requested theme that the custom style has
2234
+ * disabled falls back to the style's default palette — an end user of a
2235
+ * CHRT customer should get a chart, not a dead map, when the customer
2236
+ * narrowed the palette set.
2237
+ */
2238
+ declare function resolveCustomTheme(config: CustomStyleConfig, requested: ColorTableName | null): ResolvedTheme;
2239
+ /**
2240
+ * Scale the numeric outputs of a size-valued expression. Handles plain
2241
+ * numbers plus the output positions of `interpolate`, `step`, `case`, and
2242
+ * `match` expressions; anything else is returned unchanged (defensive: a
2243
+ * mis-scaled comparison operand would corrupt the expression, so only
2244
+ * positions that are guaranteed to be size outputs are touched).
2245
+ */
2246
+ declare function scaleSizeValue(value: unknown, factor: number): unknown;
2247
+ /**
2248
+ * Apply a sanitized custom-style config to a generated style, statically.
2249
+ *
2250
+ * Mirrors the platform map's runtime settings semantics (visibility per
2251
+ * class group, icon/text scale, allow-overlap, minzoom overrides, SEAARE
2252
+ * uppercase, layer whitelist) as build-time layer transforms. Fields with
2253
+ * no static vector-tile equivalent (`navAidFullNames`, `showUnits`,
2254
+ * `iconSizeLocked`, `showSectorArcs` need per-feature preprocessing) and
2255
+ * `display_mode` (see `CustomStyleConfig.display_mode`) are not applied
2256
+ * here — documented in the Style API docs.
2257
+ *
2258
+ * Sounding *rendering* settings (safetyContour / twoShade / fontWeight)
2259
+ * are generator options, not layer transforms — `generateCustomStyle()`
2260
+ * threads them into `generateStyle()`.
2261
+ *
2262
+ * The input style is mutated in place and returned (callers generate a
2263
+ * fresh style per request; a defensive deep-clone would be pure waste on
2264
+ * a ~700-layer document).
2265
+ */
2266
+ declare function applyCustomStyleSettings(style: MapboxStyle, config: CustomStyleConfig): MapboxStyle;
2267
+ interface GenerateCustomStyleOptions extends StyleGeneratorOptions {
2268
+ /** Requested palette (already normalized); null/undefined = style default. */
2269
+ theme?: ColorTableName | null;
2270
+ }
2271
+ interface GeneratedCustomStyle {
2272
+ style: MapboxStyle;
2273
+ palette: ColorTableName;
2274
+ themeFellBack: boolean;
1490
2275
  }
2276
+ /**
2277
+ * Generate the S-52 style for a customer's custom-style config: resolve the
2278
+ * palette against the config's enabled set, thread the sounding rendering
2279
+ * settings into the generator, then apply the static layer transforms.
2280
+ */
2281
+ declare function generateCustomStyle(config: CustomStyleConfig, opts?: GenerateCustomStyleOptions): GeneratedCustomStyle;
1491
2282
 
1492
2283
  /**
1493
2284
  * Generic async auto-pagination generator.
@@ -1602,6 +2393,12 @@ declare class Chrt {
1602
2393
  * requires `manifestPath`.
1603
2394
  */
1604
2395
  readonly offline: OfflineEndpoint;
2396
+ /**
2397
+ * CHRTinteract — click-to-inspect: `attach(map)` for wired popups/events,
2398
+ * `inspect(feature)` for headless decode. Included with the Dev plan and
2399
+ * above; enforced server-side at attach/inspect time.
2400
+ */
2401
+ readonly interact: InteractClient;
1605
2402
  private featuresEp;
1606
2403
  private queryEp;
1607
2404
  private http;
@@ -1652,14 +2449,36 @@ declare class Chrt {
1652
2449
  * });
1653
2450
  *
1654
2451
  * @remarks S-101 portrayal is on the roadmap; today `style()` emits S-52.
2452
+ * Generated locally, so it never applies a key-bound CHRTstyle custom
2453
+ * style — use {@link styleUrl} / {@link fetchStyle} (the served Style API)
2454
+ * for that.
1655
2455
  */
1656
2456
  style(opts?: ChrtStyleOptions): MapboxStyle;
1657
2457
  /**
1658
- * @deprecated CHRT serves no remote `style.json`. Use {@link style} to get a
1659
- * MapLibre style object instead. Throws to avoid silently pointing a map at a
1660
- * dead URL.
2458
+ * URL of the served S-52 style — the CHRTstyle Style API at
2459
+ * `tiles.chrt.co/v1/styles/{region}/style.json`. Unlike {@link style}
2460
+ * (generated locally), the served document honors the API key's bound
2461
+ * CHRTstyle custom style: palettes, layer visibility, label/icon scale,
2462
+ * and the rest of the platform's Map Styles settings, applied server-side
2463
+ * and tier-gated (custom styles are Dev+; Free keys get the default
2464
+ * themes). Async because it pre-mints the tile token, so the style
2465
+ * request is guaranteed to carry auth via `transformRequest()`.
2466
+ *
2467
+ * @example
2468
+ * const map = new maplibregl.Map({
2469
+ * container: 'map',
2470
+ * style: await chrt.styleUrl({ theme: 'day' }),
2471
+ * transformRequest: chrt.transformRequest(),
2472
+ * });
2473
+ */
2474
+ styleUrl(opts?: StyleUrlOptions): Promise<string>;
2475
+ /**
2476
+ * Fetch the served S-52 style (see {@link styleUrl}) as a MapLibre style
2477
+ * object — inspect or mutate it before handing it to the map.
2478
+ * `chrt.style()` remains the local generator that needs no network
2479
+ * round-trip (and therefore applies no custom style).
1661
2480
  */
1662
- styleUrl(): never;
2481
+ fetchStyle(opts?: FetchStyleOptions): Promise<MapLibreStyle>;
1663
2482
  /**
1664
2483
  * Returns a `transformRequest` for Mapbox GL JS / MapLibre GL JS that mints a
1665
2484
  * short-lived tile JWT from your API key and attaches it as
@@ -1684,4 +2503,4 @@ declare class Chrt {
1684
2503
  warmTiles(region?: string): Promise<void>;
1685
2504
  }
1686
2505
 
1687
- export { type AtlasStyle, BAND_DRAW_ORDER, BAND_PRIORITY_RANGES, type BBox, BUILD_MAXZOOM, BUILD_MINZOOM, type BandCode, type BandPriorityRange, type BuildOfflineStyleOptions, type BuildSourcesOptions, CHART_BAND_CODES, CHART_CASCADE_CODES, CHART_SECTIONS, type CellDeletedEvent, type CellDetail, type CellEventData, type CellNewEvent, type CellUpdatedData, type CellUpdatedEvent, type CellsOptions, type CellsResponse, type ChartSection, Chrt, type ChrtConfig, ChrtError, type ChrtStyleOptions, ColorTableName, type CoverageOptions, type CoverageResponse, type CoverageSource, DEFAULT_REGION, DEFAULT_TILE_BASE_URL, DEFAULT_TILE_REGION, type DisplayCategory, type DisplayMode, type FeatureCollection, type FeaturesOptions, FileSystemAdapter, type GeoJsonFeature, type GeoJsonGeometry, type GeomType, LABELED_LAYERS, LABEL_BEHAVIOR, type LabelBehavior, type LabelConfigOptions, type LabelLayerConfig, type LookupEntry, type MapLibreStyle, type MapboxStyle, type MapboxVectorSource, NAUTICAL_SYMBOLS, type NauticalCategory, type NauticalSymbol, type OfflineBundleResponse, type OfflineDownloadOptions, type OfflineDownloadProgress, type OfflineDownloadResult, OfflineError, type OfflineErrorCode, type OfflineFetchStyleOptions, type OfflineHeartbeatOptions, type OfflineRegionSummary, type OfflineRemoveOptions, OfflineStyleError, type OfflineStylePlatform, type OfflineStyleResponse, type PmtilesVectorSource, type QueryOptions, type ResolvedStyle, type RouteFeatureQueryOptions, type RouteFeatureQueryResponse, type RouteQueryOptions, type RouteQueryResponse, type S101Feature, S101_CHART_SECTIONS, S101_FEATURES, S101_NEW_SYMBOLS, S101_SYMBOL_GROUPS, S52_DRAW_ORDER, S52_LOOKUP, SYMBOL_GROUPS, SYMBOL_MAP, type ScaleBand, type SourceMode, type StyleGeneratorOptions, type StyleLayer, type SymbolGroup, TileAuth, type TileGroup, type TileGroupsResponse, TileTokenError, type TileTokenErrorCode, type TileTokenResponse, type TransformRequest, type TransformedRequest, type VectorSourceSpec, type WebhookEvent, type WebhookEventType, type WebhookPayload, applyColorTheme, bandFilterExpression, buildLabelLayer, buildLayersForClass, buildOfflineStyle, buildSources, combineWithBandFilter, daymarkColor, decimateFeatures, depareColor, depareFillExpression, generateSpriteManifest, generateSpriteSheet, generateStyle, generateStyleJSON, getAvailableLineSymbols, getAvailablePatterns, getAvailablePointSymbols, getCollisionSettings, getDrawPriority, getGridSizeForZoom, getIconExpression, getLabelBehavior, getLabelConfig, getLookupEntry, getMaxCellsForZoom, getOrderedClasses, getS52SvgString, getS52SymbolStats, getStyleStats, getSymbolSvgString, getThemedS52Svg, hazardColor, hazardFillExpression, isNavAidLayer, isVisibleInMode, lateralColor, lateralFillExpression, lightColor, lightFillExpression, paginate, parseWebhook, preprocessLights, preprocessNavAids, preprocessSoundings, preprocessWaterBodies, resolveStyle, scaminToMinZoom, withinBboxFilter, worldMinusBboxPolygon };
2506
+ export { ALL_NAV_AID_LAYERS, ALL_WATER_BODY_LAYERS, AREA_QUERY_LAYERS, type AreaInspectResult, type AtlasStyle, type AttachOptions, BAND_DRAW_ORDER, BAND_PRIORITY_RANGES, type BBox, BRIDGE_QUERY_LAYERS, BUILD_MAXZOOM, BUILD_MINZOOM, type BandCode, type BandPriorityRange, type BridgeInspectResult, type BuildOfflineStyleOptions, type BuildSourcesOptions, CHART_BAND_CODES, CHART_CASCADE_CODES, CHART_SECTIONS, type CellDeletedEvent, type CellDetail, type CellEventData, type CellNewEvent, type CellUpdatedData, type CellUpdatedEvent, type CellsOptions, type CellsResponse, type ChartSection, Chrt, type ChrtConfig, ChrtError, type ChrtStyleOptions, type ChrtStyleTheme, ColorTableName, type CoverageOptions, type CoverageResponse, type CoverageSource, type CustomStyleConfig, DEFAULT_CUSTOM_STYLE_CONFIG, DEFAULT_LABEL_SETTINGS, DEFAULT_NAV_AID_SETTINGS, DEFAULT_REGION, DEFAULT_SOUNDING_SETTINGS, DEFAULT_TILE_BASE_URL, DEFAULT_TILE_REGION, DEFAULT_WATER_BODY_SETTINGS, type DecodedAreaFeature, type DecodedBridge, type DecodedNavAid, type DecodedProperty, type DisplayCategory, type DisplayMode, type FeatureCollection, type FeaturesOptions, type FetchStyleOptions, FileSystemAdapter, type FlashPattern, type FlashPhase, type GenerateCustomStyleOptions, type GeneratedCustomStyle, type GeoJsonFeature, type GeoJsonGeometry, type GeomType, type InspectEnrichment, type InspectEventContext, type InspectNotice, type InspectOptions, type InspectResult, type InspectableFeature, InteractClient, type InteractEntitlement, InteractError, type InteractErrorCode, type InteractHandle, type InteractMapLike, type InteractMapMouseEvent, LABELED_LAYERS, LABEL_BEHAVIOR, LABEL_LAYER_GROUPS, LABEL_PRIORITY, type LabelBehavior, type LabelConfigOptions, type LabelLayerConfig, type LabelSettings, type LookupEntry, type MapLibreStyle, type MapboxStyle, type MapboxVectorSource, NAUTICAL_SYMBOLS, NAV_AID_LAYER_GROUPS, NAV_AID_QUERY_LAYERS, NON_SOLAS_DISCLAIMER, NON_SOLAS_DISCLAIMER_SHORT, type NauticalCategory, type NauticalSymbol, type NavAidInspectResult, type NavAidSettings, type OfflineBundleResponse, type OfflineDownloadOptions, type OfflineDownloadProgress, type OfflineDownloadResult, OfflineError, type OfflineErrorCode, type OfflineFetchStyleOptions, type OfflineHeartbeatOptions, type OfflineRegionSummary, type OfflineRemoveOptions, OfflineStyleError, type OfflineStylePlatform, type OfflineStyleResponse, type PmtilesVectorSource, type QueryOptions, type ResolvedStyle, type ResolvedTheme, type RouteFeatureQueryOptions, type RouteFeatureQueryResponse, type RouteQueryOptions, type RouteQueryResponse, type S101Feature, S101_CHART_SECTIONS, S101_FEATURES, S101_NEW_SYMBOLS, S101_SYMBOL_GROUPS, S52_DRAW_ORDER, S52_LOOKUP, SOUNDING_LAYERS, SYMBOL_GROUPS, SYMBOL_MAP, type ScaleBand, type SectorArcFeature, type SectorArcFeatureCollection, type SectorInfo, type SoundingSettings, type SourceMode, type StyleGeneratorOptions, type StyleLayer, type StyleUrlOptions, type SymbolGroup, TileAuth, type TileGroup, type TileGroupsResponse, TileTokenError, type TileTokenErrorCode, type TileTokenResponse, type TransformRequest, type TransformedRequest, type VectorSourceSpec, WATER_BODY_LAYER_GROUPS, type WaterBodySettings, type WebhookEvent, type WebhookEventType, type WebhookPayload, applyColorTheme, applyCustomStyleSettings, bandFilterExpression, buildFlashPattern, buildLabelLayer, buildLayersForClass, buildOfflineStyle, buildSectorArcGeoJSON, buildSectorArcsFC, buildSectorsFromFeatures, buildSources, combineWithBandFilter, daymarkColor, decimateFeatures, decodeAreaFeatureProps, decodeBridgeProps, decodeNavAidProps, depareColor, depareFillExpression, deriveMarinerName, extractNavAidDesignation, generateCustomStyle, generateSpriteManifest, generateSpriteSheet, generateStyle, generateStyleJSON, getAvailableLineSymbols, getAvailablePatterns, getAvailablePointSymbols, getCollisionSettings, getDrawPriority, getGridSizeForZoom, getIconExpression, getLabelBehavior, getLabelConfig, getLabelPriority, getLookupEntry, getMaxCellsForZoom, getOrderedClasses, getS52SvgString, getS52SymbolStats, getStyleStats, getSymbolSvgString, getThemedS52Svg, getTopmarkMeaning, hazardColor, hazardFillExpression, isLabelLayerVisible, isNavAidLayer, isNavAidLayerVisible, isSoundingLayerVisible, isVisibleInMode, isWaterBodyLayerVisible, labelSortKey, lateralColor, lateralFillExpression, lightColor, lightFillExpression, normalizeTheme, paginate, parseListCodes, parseWebhook, preprocessLights, preprocessNavAids, preprocessSoundings, preprocessWaterBodies, resolveCustomTheme, resolveStyle, sanitizeCustomStyleConfig, scaleSizeValue, scaminToMinZoom, withinBboxFilter, worldMinusBboxPolygon };