buoydata 0.1.4 → 0.2.1
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/README.md +12 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +160 -122
- package/dist/index.js.map +1 -1
- package/dist/stations/list.d.ts +23 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -74,6 +74,18 @@ Builds the realtime2 URL for a buoy and file type.
|
|
|
74
74
|
buildRealtimeUrl(buoyId: string, type?: string, baseUrl?: string): string
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
+
### fetchBuoyList and fetchStationIndex
|
|
78
|
+
|
|
79
|
+
Retrieve station IDs using the NDBC active station XML feed (no longer 404-prone). You can optionally include inactive stations from the station catalog, and `fetchStationIndex` gives you an `isActive` helper without needing to reparse data.
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
const active = await fetchBuoyList(); // active IDs only
|
|
83
|
+
const all = await fetchBuoyList({ includeInactive: true }); // full catalog
|
|
84
|
+
|
|
85
|
+
const index = await fetchStationIndex();
|
|
86
|
+
index.isActive('46026'); // true/false
|
|
87
|
+
```
|
|
88
|
+
|
|
77
89
|
### parseRealtimeData
|
|
78
90
|
|
|
79
91
|
Parses a realtime2 text file into typed `Measurement` objects. Standard fields are mapped into structured measurement fields. Unknown columns are ignored unless `includeUnknownFields` is enabled.
|
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class h{constructor(e,n){this.maxSize=e,this.ttlMs=n,this.items=new Map}get(e){const n=this.items.get(e);if(!n)return;const r=Date.now();if(n.expiresAt<=r){this.items.delete(e);return}return this.items.delete(e),this.items.set(e,n),n.value}set(e,n){const r=Date.now();this.items.has(e)&&this.items.delete(e),this.items.set(e,{value:n,expiresAt:r+this.ttlMs}),this.prune(r)}prune(e){for(const[n,r]of this.items)r.expiresAt>e||this.items.delete(n);for(;this.items.size>this.maxSize;){const n=this.items.keys().next().value;if(n===void 0)break;this.items.delete(n)}}}function T(t={}){const e=new URLSearchParams;for(const[r,s]of Object.entries(t))if(s!=null){if(Array.isArray(s)){s.forEach(i=>{e.append(r,String(i))});continue}e.append(r,String(s))}const n=e.toString();return n?`?${n}`:""}function g(t,e="",n={}){const r=t.endsWith("/")?t:`${t}/`,s=new URL(e,r),i=T(n);return s.search=i,s.toString()}const A="https://www.ndbc.noaa.gov/data/realtime2/",C=5*60*1e3,L=1024,b=new h(L,C);function S(t,e="txt",n=A){const s=`${t.toUpperCase()}.${e}`;return g(n,s)}async function D(t){const{buoyId:e,type:n="txt",fetch:r=fetch,requestInit:s,baseUrl:i=A}=t,a=e.toUpperCase();if(!r)throw new Error("No fetch implementation available.");const c=`${i}|${a}|${n}`,u=b.get(c);if(u!==void 0)return u;const o=S(a,n,i),m=await r(o,s);if(!m.ok)throw new Error(`Failed to fetch ${o}: ${m.status}`);const d=await m.text();return b.set(c,d),d}const M="https://www.ndbc.noaa.gov/activestations.xml",x="https://www.ndbc.noaa.gov/data/stations/station_table.txt",P=10*60*1e3,R=8,U=12*60*60*1e3,V=8,p=new h(R,P),w=new h(V,U);function $(t){const e=[],n=new Set,r=/<station[^>]*\bid="([A-Za-z0-9]{3,10})"/g;let s;for(;(s=r.exec(t))!==null;){const i=s[1];if(!i)continue;const a=i.toUpperCase();n.has(a)||(n.add(a),e.push(a))}return{ids:e,set:n}}function k(t){const e=[];return t.split(/\r?\n/).forEach(n=>{if(!n||n.startsWith("#"))return;const r=n.indexOf("|"),i=(r===-1?n:n.slice(0,r)).trim().toUpperCase();/^[A-Z0-9]{3,10}$/.test(i)&&e.push(i)}),e}async function B(t,e,n){const r=p.get(e);if(r!==void 0)return r;const s=await t(e,n);if(!s.ok)throw new Error(`Failed to fetch ${e}: ${s.status}`);const i=await s.text(),a=$(i);return p.set(e,a),a}async function H(t,e,n){const r=w.get(e);if(r!==void 0)return r;const s=await t(e,n);if(!s.ok)throw new Error(`Failed to fetch ${e}: ${s.status}`);const i=await s.text(),a=k(i);return w.set(e,a),a}async function E(t={}){const{fetch:e=fetch,requestInit:n,activeUrl:r=M,stationTableUrl:s=x,includeInactive:i}=t;if(!e)throw new Error("No fetch implementation available.");const a=await B(e,r,n),c=i?await H(e,s,n):a.ids,u=a.set;return{active:a.ids,all:c,isActive:o=>u.has(o.toUpperCase())}}async function O(t={}){const e=await E(t);return t.includeInactive?Array.from(e.all):Array.from(e.active)}const W=["MM"];function z(t,e){return!!(e.includes(t)||/^9{2,}(\.0+|\.9+)?$/.test(t))}function F(t,e){if(z(t,e.missingTokens))return e.missingValue;if(e.coerceNumbers){const n=Number(t);if(!Number.isNaN(n))return n}return t}function N(t,e={}){const n={coerceNumbers:e.coerceNumbers??!0,missingValue:e.missingValue??null,missingTokens:e.missingTokens??W};return t.trim().split(/\s+/).filter(Boolean).map(r=>F(r,n))}function j(t,e){return t.startsWith(`${e} `)}function G(t){return t.split(/\r?\n/).map(e=>e.trim()).filter(e=>e.length>0)}function y(t,e={}){const n=e.commentPrefix??"#",r=G(t).filter(l=>!j(l,n));if(r.length===0)return{headers:[],units:[],rows:[],rawRows:[]};const s=r[0]??"",i={coerceNumbers:!1,missingValue:null,missingTokens:[]},a=N(s,i).map(String);let c=[],u=1;const o=r[1];o&&o.startsWith(n)&&(c=N(o,i).map(l=>{const f=String(l);return f.startsWith(n)?f.slice(n.length):f}),u=2);const m={coerceNumbers:e.coerceNumbers,missingValue:e.missingValue,missingTokens:e.missingTokens},d=r.slice(u),v=d.map(l=>N(l,m));return{headers:a,units:c,rows:v,rawRows:d}}function I(t){const{headers:e,rows:n}=t;return n.map(r=>{const s={};return e.forEach((i,a)=>{s[i]=r[a]??null}),s})}function _(){return{airTemperature:Number.NaN,day:Number.NaN,dewpointTemperature:Number.NaN,hour:Number.NaN,minute:Number.NaN,month:Number.NaN,pressureTendancy:Number.NaN,seaLevelPressure:Number.NaN,stationVisibility:Number.NaN,water:{averagePeriod:Number.NaN,dominantDirection:Number.NaN,dominantPeriod:Number.NaN,significantHeight:Number.NaN,surfaceTemperature:Number.NaN,tide:Number.NaN},wind:{averageSpeed:Number.NaN,direction:Number.NaN,peakGustSpeed:Number.NaN},year:Number.NaN}}const Y={"#YY":(t,e)=>{t.year=Number(e)},YY:(t,e)=>{t.year=Number(e)},MM:(t,e)=>{t.month=Number(e)},DD:(t,e)=>{t.day=Number(e)},hh:(t,e)=>{t.hour=Number(e)},mm:(t,e)=>{t.minute=Number(e)},APD:(t,e)=>{t.water.averagePeriod=Number(e)},ATMP:(t,e)=>{t.airTemperature=Number(e)},DEWP:(t,e)=>{t.dewpointTemperature=Number(e)},DPD:(t,e)=>{t.water.dominantPeriod=Number(e)},GST:(t,e)=>{t.wind.peakGustSpeed=Number(e)},MWD:(t,e)=>{t.water.dominantDirection=Number(e)},PRES:(t,e)=>{t.seaLevelPressure=Number(e)},PTDY:(t,e)=>{t.pressureTendancy=Number(e)},TIDE:(t,e)=>{t.water.tide=Number(e)},VIS:(t,e)=>{t.stationVisibility=Number(e)},WDIR:(t,e)=>{t.wind.direction=Number(e)},WSPD:(t,e)=>{t.wind.averageSpeed=Number(e)},WTMP:(t,e)=>{t.water.surfaceTemperature=Number(e)},WVHT:(t,e)=>{t.water.significantHeight=Number(e)}};function Z(t){const e=_();return Object.entries(t).forEach(([n,r])=>{const s=Y[n];s&&s(e,r)}),e}function q(t,e,n={}){const r=y(e,{...n,missingValue:n.missingValue??Number.NaN}),s=I(r),i=s.map(a=>Z(a));if(n.includeUnknownFields){const a=i.map((c,u)=>{const o=s[u];return{...c,...o}});return{id:t,measurements:a}}return{id:t,measurements:i}}function X(t){const{year:e,month:n,day:r,hour:s,minute:i}=t;return new Date(Date.UTC(e,n-1,r,s,i))}exports.buildRealtimeUrl=S;exports.buildURL=g;exports.createMeasurement=_;exports.fetchBuoyList=O;exports.fetchRealtimeData=D;exports.fetchStationIndex=E;exports.formatQueryParams=T;exports.getMeasurementDate=X;exports.objectifyTable=I;exports.parseRealtimeData=q;exports.parseRealtimeTable=y;exports.parseRow=N;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../src/utils/lru.ts","../src/utils/url.ts","../src/realtime/fetch.ts","../src/stations/list.ts","../src/realtime/parser.ts","../src/utils/date.ts"],"sourcesContent":["export class LruCache<K, V> {\n private readonly items = new Map<K, { value: V; expiresAt: number }>();\n\n constructor(\n private readonly maxSize: number,\n private readonly ttlMs: number,\n ) {}\n\n get(key: K): V | undefined {\n const entry = this.items.get(key);\n if (!entry) {\n return undefined;\n }\n\n const now = Date.now();\n if (entry.expiresAt <= now) {\n this.items.delete(key);\n return undefined;\n }\n\n this.items.delete(key);\n this.items.set(key, entry);\n return entry.value;\n }\n\n set(key: K, value: V): void {\n const now = Date.now();\n if (this.items.has(key)) {\n this.items.delete(key);\n }\n\n this.items.set(key, { value, expiresAt: now + this.ttlMs });\n this.prune(now);\n }\n\n private prune(now: number): void {\n for (const [key, entry] of this.items) {\n if (entry.expiresAt > now) {\n continue;\n }\n this.items.delete(key);\n }\n\n while (this.items.size > this.maxSize) {\n const oldestKey = this.items.keys().next().value as K | undefined;\n if (oldestKey === undefined) {\n break;\n }\n this.items.delete(oldestKey);\n }\n }\n}\n","export type QueryParamValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | Array<string | number | boolean>;\n\nexport type QueryParams = Record<string, QueryParamValue>;\n\nexport function formatQueryParams(params: QueryParams = {}): string {\n const searchParams = new URLSearchParams();\n\n for (const [key, value] of Object.entries(params)) {\n if (value === null || value === undefined) {\n continue;\n }\n\n if (Array.isArray(value)) {\n value.forEach(item => {\n searchParams.append(key, String(item));\n });\n continue;\n }\n\n searchParams.append(key, String(value));\n }\n\n const query = searchParams.toString();\n return query ? `?${query}` : '';\n}\n\nexport function buildURL(\n base: string,\n path = '',\n params: QueryParams = {},\n): string {\n const normalizedBase = base.endsWith('/') ? base : `${base}/`;\n const url = new URL(path, normalizedBase);\n const query = formatQueryParams(params);\n url.search = query;\n return url.toString();\n}\n","import { LruCache } from '../utils/lru';\nimport { buildURL } from '../utils/url';\n\nexport interface FetchRealtimeOptions {\n buoyId: string;\n type?: string;\n fetch?: typeof fetch;\n requestInit?: RequestInit;\n baseUrl?: string;\n}\n\nconst DEFAULT_BASE_URL = 'https://www.ndbc.noaa.gov/data/realtime2/';\nconst CACHE_TTL_MS = 5 * 60 * 1000;\nconst CACHE_MAX_SIZE = 256;\nconst REALTIME_CACHE = new LruCache<string, string>(\n CACHE_MAX_SIZE,\n CACHE_TTL_MS,\n);\n\nexport function buildRealtimeUrl(\n buoyId: string,\n type = 'txt',\n baseUrl = DEFAULT_BASE_URL,\n): string {\n const normalizedBuoyId = buoyId.toUpperCase();\n const filename = `${normalizedBuoyId}.${type}`;\n return buildURL(baseUrl, filename);\n}\n\nexport async function fetchRealtimeData(\n options: FetchRealtimeOptions,\n): Promise<string> {\n const {\n buoyId,\n type = 'txt',\n fetch: fetchImpl = fetch,\n requestInit,\n baseUrl = DEFAULT_BASE_URL,\n } = options;\n const normalizedBuoyId = buoyId.toUpperCase();\n\n if (!fetchImpl) {\n throw new Error('No fetch implementation available.');\n }\n\n const cacheKey = `${baseUrl}|${normalizedBuoyId}|${type}`;\n const cached = REALTIME_CACHE.get(cacheKey);\n if (cached !== undefined) {\n return cached;\n }\n\n const url = buildRealtimeUrl(normalizedBuoyId, type, baseUrl);\n const response = await fetchImpl(url, requestInit);\n\n if (!response.ok) {\n throw new Error(`Failed to fetch ${url}: ${response.status}`);\n }\n\n const body = await response.text();\n REALTIME_CACHE.set(cacheKey, body);\n return body;\n}\n","import { LruCache } from '../utils/lru';\n\nexport interface FetchBuoyListOptions {\n fetch?: typeof fetch;\n requestInit?: RequestInit;\n url?: string;\n}\n\nconst DEFAULT_BUOY_LIST_URL = 'https://www.ndbc.noaa.gov/activestations.txt';\nconst BUOY_LIST_CACHE_TTL_MS = 4 * 60 * 60 * 1000;\nconst BUOY_LIST_CACHE_MAX_SIZE = 4;\nconst BUOY_LIST_CACHE = new LruCache<string, string[]>(\n BUOY_LIST_CACHE_MAX_SIZE,\n BUOY_LIST_CACHE_TTL_MS,\n);\n\nfunction parseBuoyList(rawText: string): string[] {\n const ids = new Set<string>();\n\n rawText\n .split(/\\r?\\n/)\n .map(line => line.trim())\n .filter(line => line.length > 0)\n .forEach(line => {\n if (line.startsWith('#')) {\n return;\n }\n\n const [token] = line.split(/\\s+/);\n if (!token) {\n return;\n }\n\n const normalized = token.toUpperCase();\n if (normalized === 'STATION' || normalized === 'STN') {\n return;\n }\n\n if (!/^[A-Z0-9]{3,10}$/.test(normalized)) {\n return;\n }\n\n ids.add(normalized);\n });\n\n return Array.from(ids);\n}\n\nexport async function fetchBuoyList(\n options: FetchBuoyListOptions = {},\n): Promise<string[]> {\n const { fetch: fetchImpl = fetch, requestInit, url = DEFAULT_BUOY_LIST_URL } =\n options;\n\n if (!fetchImpl) {\n throw new Error('No fetch implementation available.');\n }\n\n const cached = BUOY_LIST_CACHE.get(url);\n if (cached !== undefined) {\n return cached;\n }\n\n const response = await fetchImpl(url, requestInit);\n if (!response.ok) {\n throw new Error(`Failed to fetch ${url}: ${response.status}`);\n }\n\n const body = await response.text();\n const list = parseBuoyList(body);\n BUOY_LIST_CACHE.set(url, list);\n return list;\n}\n","import { BuoyData, Measurement } from '../models/measurement';\nimport { ParsedValue, RealtimeRecord, RealtimeTable } from '../models/table';\n\nexport interface ParseRowOptions {\n coerceNumbers?: boolean;\n missingValue?: number | null;\n missingTokens?: string[];\n}\n\nexport interface ParseRealtimeTableOptions extends ParseRowOptions {\n commentPrefix?: string;\n}\n\nexport interface ParseRealtimeDataOptions extends ParseRealtimeTableOptions {\n includeUnknownFields?: boolean;\n}\n\nconst DEFAULT_MISSING_TOKENS = ['MM'];\n\nfunction isMissingToken(value: string, missingTokens: string[]): boolean {\n if (missingTokens.includes(value)) {\n return true;\n }\n\n // NDBC missing values are often 9s (e.g. 99, 999, 9999, 99.0).\n if (/^9{2,}(\\.0+|\\.9+)?$/.test(value)) {\n return true;\n }\n\n return false;\n}\n\nfunction coerceValue(\n raw: string,\n options: Required<ParseRowOptions>,\n): ParsedValue {\n if (isMissingToken(raw, options.missingTokens)) {\n return options.missingValue;\n }\n\n if (options.coerceNumbers) {\n const numeric = Number(raw);\n if (!Number.isNaN(numeric)) {\n return numeric;\n }\n }\n\n return raw;\n}\n\nexport function parseRow(\n rawRow: string,\n options: ParseRowOptions = {},\n): ParsedValue[] {\n const resolved: Required<ParseRowOptions> = {\n coerceNumbers: options.coerceNumbers ?? true,\n missingValue: options.missingValue ?? null,\n missingTokens: options.missingTokens ?? DEFAULT_MISSING_TOKENS,\n };\n\n return rawRow\n .trim()\n .split(/\\s+/)\n .filter(Boolean)\n .map(value => coerceValue(value, resolved));\n}\n\nfunction isCommentLine(line: string, commentPrefix: string): boolean {\n return line.startsWith(`${commentPrefix} `);\n}\n\nfunction normalizeLines(rawText: string): string[] {\n return rawText\n .split(/\\r?\\n/)\n .map(line => line.trim())\n .filter(line => line.length > 0);\n}\n\nexport function parseRealtimeTable(\n rawText: string,\n options: ParseRealtimeTableOptions = {},\n): RealtimeTable {\n const commentPrefix = options.commentPrefix ?? '#';\n const lines = normalizeLines(rawText).filter(\n line => !isCommentLine(line, commentPrefix),\n );\n\n if (lines.length === 0) {\n return { headers: [], units: [], rows: [], rawRows: [] };\n }\n\n const headerLine = lines[0] ?? '';\n const headerOptions: ParseRowOptions = {\n coerceNumbers: false,\n missingValue: null,\n missingTokens: [],\n };\n const headers = parseRow(headerLine, headerOptions).map(String);\n\n let units: string[] = [];\n let dataStartIndex = 1;\n\n const unitLine = lines[1];\n if (unitLine && unitLine.startsWith(commentPrefix)) {\n units = parseRow(unitLine, headerOptions).map(token => {\n const text = String(token);\n return text.startsWith(commentPrefix)\n ? text.slice(commentPrefix.length)\n : text;\n });\n dataStartIndex = 2;\n }\n\n const rowOptions: ParseRowOptions = {\n coerceNumbers: options.coerceNumbers,\n missingValue: options.missingValue,\n missingTokens: options.missingTokens,\n };\n\n const dataRows = lines.slice(dataStartIndex);\n const rows = dataRows.map(row => parseRow(row, rowOptions));\n\n return {\n headers,\n units,\n rows,\n rawRows: dataRows,\n };\n}\n\nexport function objectifyTable(table: RealtimeTable): RealtimeRecord[] {\n const { headers, rows } = table;\n return rows.map(row => {\n const record: RealtimeRecord = {};\n headers.forEach((header, index) => {\n record[header] = row[index] ?? null;\n });\n return record;\n });\n}\n\nexport function createMeasurement(): Measurement {\n return {\n airTemperature: Number.NaN,\n day: Number.NaN,\n dewpointTemperature: Number.NaN,\n hour: Number.NaN,\n minute: Number.NaN,\n month: Number.NaN,\n pressureTendancy: Number.NaN,\n seaLevelPressure: Number.NaN,\n stationVisibility: Number.NaN,\n water: {\n averagePeriod: Number.NaN,\n dominantDirection: Number.NaN,\n dominantPeriod: Number.NaN,\n significantHeight: Number.NaN,\n surfaceTemperature: Number.NaN,\n tide: Number.NaN,\n },\n wind: {\n averageSpeed: Number.NaN,\n direction: Number.NaN,\n peakGustSpeed: Number.NaN,\n },\n year: Number.NaN,\n };\n}\n\nconst FIELD_MAPPINGS: Record<string, (m: Measurement, value: ParsedValue) => void> = {\n '#YY': (m, value) => {\n m.year = Number(value);\n },\n YY: (m, value) => {\n m.year = Number(value);\n },\n MM: (m, value) => {\n m.month = Number(value);\n },\n DD: (m, value) => {\n m.day = Number(value);\n },\n hh: (m, value) => {\n m.hour = Number(value);\n },\n mm: (m, value) => {\n m.minute = Number(value);\n },\n APD: (m, value) => {\n m.water.averagePeriod = Number(value);\n },\n ATMP: (m, value) => {\n m.airTemperature = Number(value);\n },\n DEWP: (m, value) => {\n m.dewpointTemperature = Number(value);\n },\n DPD: (m, value) => {\n m.water.dominantPeriod = Number(value);\n },\n GST: (m, value) => {\n m.wind.peakGustSpeed = Number(value);\n },\n MWD: (m, value) => {\n m.water.dominantDirection = Number(value);\n },\n PRES: (m, value) => {\n m.seaLevelPressure = Number(value);\n },\n PTDY: (m, value) => {\n m.pressureTendancy = Number(value);\n },\n TIDE: (m, value) => {\n m.water.tide = Number(value);\n },\n VIS: (m, value) => {\n m.stationVisibility = Number(value);\n },\n WDIR: (m, value) => {\n m.wind.direction = Number(value);\n },\n WSPD: (m, value) => {\n m.wind.averageSpeed = Number(value);\n },\n WTMP: (m, value) => {\n m.water.surfaceTemperature = Number(value);\n },\n WVHT: (m, value) => {\n m.water.significantHeight = Number(value);\n },\n};\n\nfunction toMeasurement(record: RealtimeRecord): Measurement {\n const measurement = createMeasurement();\n Object.entries(record).forEach(([field, value]) => {\n const mapper = FIELD_MAPPINGS[field];\n if (mapper) {\n mapper(measurement, value);\n }\n });\n return measurement;\n}\n\nexport function parseRealtimeData(\n buoyId: string,\n rawText: string,\n options: ParseRealtimeDataOptions = {},\n): BuoyData {\n const table = parseRealtimeTable(rawText, {\n ...options,\n missingValue: options.missingValue ?? Number.NaN,\n });\n const records = objectifyTable(table);\n\n const measurements = records.map(record => toMeasurement(record));\n\n if (options.includeUnknownFields) {\n const measurementsWithUnknowns = measurements.map((measurement, index) => {\n const record = records[index];\n return { ...measurement, ...record } as Measurement;\n });\n\n return {\n id: buoyId,\n measurements: measurementsWithUnknowns,\n };\n }\n\n return {\n id: buoyId,\n measurements,\n };\n}\n","import { Measurement } from '../models/measurement';\n\n/**\n * Returns a UTC Date instance for a buoy measurement.\n */\nexport function getMeasurementDate(measurement: Measurement): Date {\n const { year, month, day, hour, minute } = measurement;\n return new Date(Date.UTC(year, month - 1, day, hour, minute));\n}\n"],"names":["LruCache","maxSize","ttlMs","key","entry","now","value","oldestKey","formatQueryParams","params","searchParams","item","query","buildURL","base","path","normalizedBase","url","DEFAULT_BASE_URL","CACHE_TTL_MS","CACHE_MAX_SIZE","REALTIME_CACHE","buildRealtimeUrl","buoyId","type","baseUrl","filename","fetchRealtimeData","options","fetchImpl","requestInit","normalizedBuoyId","cacheKey","cached","response","body","DEFAULT_BUOY_LIST_URL","BUOY_LIST_CACHE_TTL_MS","BUOY_LIST_CACHE_MAX_SIZE","BUOY_LIST_CACHE","parseBuoyList","rawText","ids","line","token","normalized","fetchBuoyList","list","DEFAULT_MISSING_TOKENS","isMissingToken","missingTokens","coerceValue","raw","numeric","parseRow","rawRow","resolved","isCommentLine","commentPrefix","normalizeLines","parseRealtimeTable","lines","headerLine","headerOptions","headers","units","dataStartIndex","unitLine","text","rowOptions","dataRows","rows","row","objectifyTable","table","record","header","index","createMeasurement","FIELD_MAPPINGS","m","toMeasurement","measurement","field","mapper","parseRealtimeData","records","measurements","measurementsWithUnknowns","getMeasurementDate","year","month","day","hour","minute"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/utils/lru.ts","../src/utils/url.ts","../src/realtime/fetch.ts","../src/stations/list.ts","../src/realtime/parser.ts","../src/utils/date.ts"],"sourcesContent":["export class LruCache<K, V> {\n private readonly items = new Map<K, { value: V; expiresAt: number }>();\n\n constructor(\n private readonly maxSize: number,\n private readonly ttlMs: number,\n ) {}\n\n get(key: K): V | undefined {\n const entry = this.items.get(key);\n if (!entry) {\n return undefined;\n }\n\n const now = Date.now();\n if (entry.expiresAt <= now) {\n this.items.delete(key);\n return undefined;\n }\n\n this.items.delete(key);\n this.items.set(key, entry);\n return entry.value;\n }\n\n set(key: K, value: V): void {\n const now = Date.now();\n if (this.items.has(key)) {\n this.items.delete(key);\n }\n\n this.items.set(key, { value, expiresAt: now + this.ttlMs });\n this.prune(now);\n }\n\n private prune(now: number): void {\n for (const [key, entry] of this.items) {\n if (entry.expiresAt > now) {\n continue;\n }\n this.items.delete(key);\n }\n\n while (this.items.size > this.maxSize) {\n const oldestKey = this.items.keys().next().value as K | undefined;\n if (oldestKey === undefined) {\n break;\n }\n this.items.delete(oldestKey);\n }\n }\n}\n","export type QueryParamValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | Array<string | number | boolean>;\n\nexport type QueryParams = Record<string, QueryParamValue>;\n\nexport function formatQueryParams(params: QueryParams = {}): string {\n const searchParams = new URLSearchParams();\n\n for (const [key, value] of Object.entries(params)) {\n if (value === null || value === undefined) {\n continue;\n }\n\n if (Array.isArray(value)) {\n value.forEach(item => {\n searchParams.append(key, String(item));\n });\n continue;\n }\n\n searchParams.append(key, String(value));\n }\n\n const query = searchParams.toString();\n return query ? `?${query}` : '';\n}\n\nexport function buildURL(\n base: string,\n path = '',\n params: QueryParams = {},\n): string {\n const normalizedBase = base.endsWith('/') ? base : `${base}/`;\n const url = new URL(path, normalizedBase);\n const query = formatQueryParams(params);\n url.search = query;\n return url.toString();\n}\n","import { LruCache } from '../utils/lru';\nimport { buildURL } from '../utils/url';\n\nexport interface FetchRealtimeOptions {\n buoyId: string;\n type?: string;\n fetch?: typeof fetch;\n requestInit?: RequestInit;\n baseUrl?: string;\n}\n\nconst DEFAULT_BASE_URL = 'https://www.ndbc.noaa.gov/data/realtime2/';\nconst CACHE_TTL_MS = 5 * 60 * 1000;\nconst CACHE_MAX_SIZE = 1024;\nconst REALTIME_CACHE = new LruCache<string, string>(\n CACHE_MAX_SIZE,\n CACHE_TTL_MS,\n);\n\nexport function buildRealtimeUrl(\n buoyId: string,\n type = 'txt',\n baseUrl = DEFAULT_BASE_URL,\n): string {\n const normalizedBuoyId = buoyId.toUpperCase();\n const filename = `${normalizedBuoyId}.${type}`;\n return buildURL(baseUrl, filename);\n}\n\nexport async function fetchRealtimeData(\n options: FetchRealtimeOptions,\n): Promise<string> {\n const {\n buoyId,\n type = 'txt',\n fetch: fetchImpl = fetch,\n requestInit,\n baseUrl = DEFAULT_BASE_URL,\n } = options;\n const normalizedBuoyId = buoyId.toUpperCase();\n\n if (!fetchImpl) {\n throw new Error('No fetch implementation available.');\n }\n\n const cacheKey = `${baseUrl}|${normalizedBuoyId}|${type}`;\n const cached = REALTIME_CACHE.get(cacheKey);\n if (cached !== undefined) {\n return cached;\n }\n\n const url = buildRealtimeUrl(normalizedBuoyId, type, baseUrl);\n const response = await fetchImpl(url, requestInit);\n\n if (!response.ok) {\n throw new Error(`Failed to fetch ${url}: ${response.status}`);\n }\n\n const body = await response.text();\n REALTIME_CACHE.set(cacheKey, body);\n return body;\n}\n","import { LruCache } from '../utils/lru';\n\nexport interface FetchBuoyListOptions {\n fetch?: typeof fetch;\n requestInit?: RequestInit;\n /**\n * URL for the active station XML feed. Defaults to NDBC.\n */\n activeUrl?: string;\n /**\n * URL for the full station catalog. Defaults to NDBC.\n */\n stationTableUrl?: string;\n /**\n * When true, includes inactive stations from the catalog.\n */\n includeInactive?: boolean;\n}\n\nexport interface FetchStationIndexOptions extends FetchBuoyListOptions {}\n\nexport interface StationIndex {\n /** Active station IDs from the XML feed. */\n active: readonly string[];\n /** All stations from the catalog (includes inactive when available). */\n all: readonly string[];\n /** Efficient membership check for active stations. */\n isActive: (id: string) => boolean;\n}\n\nconst DEFAULT_ACTIVE_STATIONS_URL = 'https://www.ndbc.noaa.gov/activestations.xml';\nconst DEFAULT_STATION_TABLE_URL =\n 'https://www.ndbc.noaa.gov/data/stations/station_table.txt';\n\nconst ACTIVE_CACHE_TTL_MS = 10 * 60 * 1000;\nconst ACTIVE_CACHE_MAX_SIZE = 8;\nconst STATION_TABLE_CACHE_TTL_MS = 12 * 60 * 60 * 1000;\nconst STATION_TABLE_CACHE_MAX_SIZE = 8;\n\nconst ACTIVE_CACHE = new LruCache<\n string,\n { ids: readonly string[]; set: ReadonlySet<string> }\n>(ACTIVE_CACHE_MAX_SIZE, ACTIVE_CACHE_TTL_MS);\n\nconst STATION_TABLE_CACHE = new LruCache<string, readonly string[]>(\n STATION_TABLE_CACHE_MAX_SIZE,\n STATION_TABLE_CACHE_TTL_MS,\n);\n\nfunction parseActiveStationsXml(rawText: string): { ids: string[]; set: Set<string> } {\n const ids: string[] = [];\n const set = new Set<string>();\n const regex = /<station[^>]*\\bid=\"([A-Za-z0-9]{3,10})\"/g;\n let match: RegExpExecArray | null;\n\n while ((match = regex.exec(rawText)) !== null) {\n const rawId = match[1];\n if (!rawId) {\n continue;\n }\n const id = rawId.toUpperCase();\n if (set.has(id)) {\n continue;\n }\n set.add(id);\n ids.push(id);\n }\n\n return { ids, set };\n}\n\nfunction parseStationTable(rawText: string): string[] {\n const ids: string[] = [];\n\n rawText.split(/\\r?\\n/).forEach(line => {\n if (!line || line.startsWith('#')) {\n return;\n }\n\n const pipeIndex = line.indexOf('|');\n const token = pipeIndex === -1 ? line : line.slice(0, pipeIndex);\n const id = token.trim().toUpperCase();\n\n if (!/^[A-Z0-9]{3,10}$/.test(id)) {\n return;\n }\n\n ids.push(id);\n });\n\n return ids;\n}\n\nasync function fetchActiveStations(\n fetchImpl: typeof fetch,\n activeUrl: string,\n requestInit?: RequestInit,\n): Promise<{ ids: readonly string[]; set: ReadonlySet<string> }> {\n const cached = ACTIVE_CACHE.get(activeUrl);\n if (cached !== undefined) {\n return cached;\n }\n\n const response = await fetchImpl(activeUrl, requestInit);\n if (!response.ok) {\n throw new Error(`Failed to fetch ${activeUrl}: ${response.status}`);\n }\n\n const body = await response.text();\n const parsed = parseActiveStationsXml(body);\n ACTIVE_CACHE.set(activeUrl, parsed);\n return parsed;\n}\n\nasync function fetchStationTable(\n fetchImpl: typeof fetch,\n stationTableUrl: string,\n requestInit?: RequestInit,\n): Promise<readonly string[]> {\n const cached = STATION_TABLE_CACHE.get(stationTableUrl);\n if (cached !== undefined) {\n return cached;\n }\n\n const response = await fetchImpl(stationTableUrl, requestInit);\n if (!response.ok) {\n throw new Error(`Failed to fetch ${stationTableUrl}: ${response.status}`);\n }\n\n const body = await response.text();\n const parsed = parseStationTable(body);\n STATION_TABLE_CACHE.set(stationTableUrl, parsed);\n return parsed;\n}\n\nexport async function fetchStationIndex(\n options: FetchStationIndexOptions = {},\n): Promise<StationIndex> {\n const {\n fetch: fetchImpl = fetch,\n requestInit,\n activeUrl = DEFAULT_ACTIVE_STATIONS_URL,\n stationTableUrl = DEFAULT_STATION_TABLE_URL,\n includeInactive,\n } = options;\n\n if (!fetchImpl) {\n throw new Error('No fetch implementation available.');\n }\n\n const active = await fetchActiveStations(fetchImpl, activeUrl, requestInit);\n\n const all = includeInactive\n ? await fetchStationTable(fetchImpl, stationTableUrl, requestInit)\n : active.ids;\n\n const activeSet = active.set;\n\n return {\n active: active.ids,\n all,\n isActive: (id: string) => activeSet.has(id.toUpperCase()),\n };\n}\n\nexport async function fetchBuoyList(\n options: FetchBuoyListOptions = {},\n): Promise<string[]> {\n const index = await fetchStationIndex(options);\n return options.includeInactive ? Array.from(index.all) : Array.from(index.active);\n}\n","import { BuoyData, Measurement } from '../models/measurement';\nimport { ParsedValue, RealtimeRecord, RealtimeTable } from '../models/table';\n\nexport interface ParseRowOptions {\n coerceNumbers?: boolean;\n missingValue?: number | null;\n missingTokens?: string[];\n}\n\nexport interface ParseRealtimeTableOptions extends ParseRowOptions {\n commentPrefix?: string;\n}\n\nexport interface ParseRealtimeDataOptions extends ParseRealtimeTableOptions {\n includeUnknownFields?: boolean;\n}\n\nconst DEFAULT_MISSING_TOKENS = ['MM'];\n\nfunction isMissingToken(value: string, missingTokens: string[]): boolean {\n if (missingTokens.includes(value)) {\n return true;\n }\n\n // NDBC missing values are often 9s (e.g. 99, 999, 9999, 99.0).\n if (/^9{2,}(\\.0+|\\.9+)?$/.test(value)) {\n return true;\n }\n\n return false;\n}\n\nfunction coerceValue(\n raw: string,\n options: Required<ParseRowOptions>,\n): ParsedValue {\n if (isMissingToken(raw, options.missingTokens)) {\n return options.missingValue;\n }\n\n if (options.coerceNumbers) {\n const numeric = Number(raw);\n if (!Number.isNaN(numeric)) {\n return numeric;\n }\n }\n\n return raw;\n}\n\nexport function parseRow(\n rawRow: string,\n options: ParseRowOptions = {},\n): ParsedValue[] {\n const resolved: Required<ParseRowOptions> = {\n coerceNumbers: options.coerceNumbers ?? true,\n missingValue: options.missingValue ?? null,\n missingTokens: options.missingTokens ?? DEFAULT_MISSING_TOKENS,\n };\n\n return rawRow\n .trim()\n .split(/\\s+/)\n .filter(Boolean)\n .map(value => coerceValue(value, resolved));\n}\n\nfunction isCommentLine(line: string, commentPrefix: string): boolean {\n return line.startsWith(`${commentPrefix} `);\n}\n\nfunction normalizeLines(rawText: string): string[] {\n return rawText\n .split(/\\r?\\n/)\n .map(line => line.trim())\n .filter(line => line.length > 0);\n}\n\nexport function parseRealtimeTable(\n rawText: string,\n options: ParseRealtimeTableOptions = {},\n): RealtimeTable {\n const commentPrefix = options.commentPrefix ?? '#';\n const lines = normalizeLines(rawText).filter(\n line => !isCommentLine(line, commentPrefix),\n );\n\n if (lines.length === 0) {\n return { headers: [], units: [], rows: [], rawRows: [] };\n }\n\n const headerLine = lines[0] ?? '';\n const headerOptions: ParseRowOptions = {\n coerceNumbers: false,\n missingValue: null,\n missingTokens: [],\n };\n const headers = parseRow(headerLine, headerOptions).map(String);\n\n let units: string[] = [];\n let dataStartIndex = 1;\n\n const unitLine = lines[1];\n if (unitLine && unitLine.startsWith(commentPrefix)) {\n units = parseRow(unitLine, headerOptions).map(token => {\n const text = String(token);\n return text.startsWith(commentPrefix)\n ? text.slice(commentPrefix.length)\n : text;\n });\n dataStartIndex = 2;\n }\n\n const rowOptions: ParseRowOptions = {\n coerceNumbers: options.coerceNumbers,\n missingValue: options.missingValue,\n missingTokens: options.missingTokens,\n };\n\n const dataRows = lines.slice(dataStartIndex);\n const rows = dataRows.map(row => parseRow(row, rowOptions));\n\n return {\n headers,\n units,\n rows,\n rawRows: dataRows,\n };\n}\n\nexport function objectifyTable(table: RealtimeTable): RealtimeRecord[] {\n const { headers, rows } = table;\n return rows.map(row => {\n const record: RealtimeRecord = {};\n headers.forEach((header, index) => {\n record[header] = row[index] ?? null;\n });\n return record;\n });\n}\n\nexport function createMeasurement(): Measurement {\n return {\n airTemperature: Number.NaN,\n day: Number.NaN,\n dewpointTemperature: Number.NaN,\n hour: Number.NaN,\n minute: Number.NaN,\n month: Number.NaN,\n pressureTendancy: Number.NaN,\n seaLevelPressure: Number.NaN,\n stationVisibility: Number.NaN,\n water: {\n averagePeriod: Number.NaN,\n dominantDirection: Number.NaN,\n dominantPeriod: Number.NaN,\n significantHeight: Number.NaN,\n surfaceTemperature: Number.NaN,\n tide: Number.NaN,\n },\n wind: {\n averageSpeed: Number.NaN,\n direction: Number.NaN,\n peakGustSpeed: Number.NaN,\n },\n year: Number.NaN,\n };\n}\n\nconst FIELD_MAPPINGS: Record<string, (m: Measurement, value: ParsedValue) => void> = {\n '#YY': (m, value) => {\n m.year = Number(value);\n },\n YY: (m, value) => {\n m.year = Number(value);\n },\n MM: (m, value) => {\n m.month = Number(value);\n },\n DD: (m, value) => {\n m.day = Number(value);\n },\n hh: (m, value) => {\n m.hour = Number(value);\n },\n mm: (m, value) => {\n m.minute = Number(value);\n },\n APD: (m, value) => {\n m.water.averagePeriod = Number(value);\n },\n ATMP: (m, value) => {\n m.airTemperature = Number(value);\n },\n DEWP: (m, value) => {\n m.dewpointTemperature = Number(value);\n },\n DPD: (m, value) => {\n m.water.dominantPeriod = Number(value);\n },\n GST: (m, value) => {\n m.wind.peakGustSpeed = Number(value);\n },\n MWD: (m, value) => {\n m.water.dominantDirection = Number(value);\n },\n PRES: (m, value) => {\n m.seaLevelPressure = Number(value);\n },\n PTDY: (m, value) => {\n m.pressureTendancy = Number(value);\n },\n TIDE: (m, value) => {\n m.water.tide = Number(value);\n },\n VIS: (m, value) => {\n m.stationVisibility = Number(value);\n },\n WDIR: (m, value) => {\n m.wind.direction = Number(value);\n },\n WSPD: (m, value) => {\n m.wind.averageSpeed = Number(value);\n },\n WTMP: (m, value) => {\n m.water.surfaceTemperature = Number(value);\n },\n WVHT: (m, value) => {\n m.water.significantHeight = Number(value);\n },\n};\n\nfunction toMeasurement(record: RealtimeRecord): Measurement {\n const measurement = createMeasurement();\n Object.entries(record).forEach(([field, value]) => {\n const mapper = FIELD_MAPPINGS[field];\n if (mapper) {\n mapper(measurement, value);\n }\n });\n return measurement;\n}\n\nexport function parseRealtimeData(\n buoyId: string,\n rawText: string,\n options: ParseRealtimeDataOptions = {},\n): BuoyData {\n const table = parseRealtimeTable(rawText, {\n ...options,\n missingValue: options.missingValue ?? Number.NaN,\n });\n const records = objectifyTable(table);\n\n const measurements = records.map(record => toMeasurement(record));\n\n if (options.includeUnknownFields) {\n const measurementsWithUnknowns = measurements.map((measurement, index) => {\n const record = records[index];\n return { ...measurement, ...record } as Measurement;\n });\n\n return {\n id: buoyId,\n measurements: measurementsWithUnknowns,\n };\n }\n\n return {\n id: buoyId,\n measurements,\n };\n}\n","import { Measurement } from '../models/measurement';\n\n/**\n * Returns a UTC Date instance for a buoy measurement.\n */\nexport function getMeasurementDate(measurement: Measurement): Date {\n const { year, month, day, hour, minute } = measurement;\n return new Date(Date.UTC(year, month - 1, day, hour, minute));\n}\n"],"names":["LruCache","maxSize","ttlMs","key","entry","now","value","oldestKey","formatQueryParams","params","searchParams","item","query","buildURL","base","path","normalizedBase","url","DEFAULT_BASE_URL","CACHE_TTL_MS","CACHE_MAX_SIZE","REALTIME_CACHE","buildRealtimeUrl","buoyId","type","baseUrl","filename","fetchRealtimeData","options","fetchImpl","requestInit","normalizedBuoyId","cacheKey","cached","response","body","DEFAULT_ACTIVE_STATIONS_URL","DEFAULT_STATION_TABLE_URL","ACTIVE_CACHE_TTL_MS","ACTIVE_CACHE_MAX_SIZE","STATION_TABLE_CACHE_TTL_MS","STATION_TABLE_CACHE_MAX_SIZE","ACTIVE_CACHE","STATION_TABLE_CACHE","parseActiveStationsXml","rawText","ids","set","regex","match","rawId","id","parseStationTable","line","pipeIndex","fetchActiveStations","activeUrl","parsed","fetchStationTable","stationTableUrl","fetchStationIndex","includeInactive","active","all","activeSet","fetchBuoyList","index","DEFAULT_MISSING_TOKENS","isMissingToken","missingTokens","coerceValue","raw","numeric","parseRow","rawRow","resolved","isCommentLine","commentPrefix","normalizeLines","parseRealtimeTable","lines","headerLine","headerOptions","headers","units","dataStartIndex","unitLine","token","text","rowOptions","dataRows","rows","row","objectifyTable","table","record","header","createMeasurement","FIELD_MAPPINGS","m","toMeasurement","measurement","field","mapper","parseRealtimeData","records","measurements","measurementsWithUnknowns","getMeasurementDate","year","month","day","hour","minute"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
export type { BuoyData, Measurement, WaterMeasurement, WindMeasurement } from './models/measurement';
|
|
2
2
|
export type { ParsedValue, RealtimeRecord, RealtimeTable } from './models/table';
|
|
3
3
|
export type { FetchRealtimeOptions, } from './realtime/fetch';
|
|
4
|
-
export type { FetchBuoyListOptions } from './stations/list';
|
|
4
|
+
export type { FetchBuoyListOptions, FetchStationIndexOptions, StationIndex, } from './stations/list';
|
|
5
5
|
export type { ParseRowOptions, ParseRealtimeTableOptions, ParseRealtimeDataOptions, } from './realtime/parser';
|
|
6
6
|
export { fetchRealtimeData, buildRealtimeUrl } from './realtime/fetch';
|
|
7
|
-
export { fetchBuoyList } from './stations/list';
|
|
7
|
+
export { fetchBuoyList, fetchStationIndex } from './stations/list';
|
|
8
8
|
export { parseRealtimeData, parseRealtimeTable, parseRow, objectifyTable, createMeasurement, } from './realtime/parser';
|
|
9
9
|
export { getMeasurementDate } from './utils/date';
|
|
10
10
|
export { buildURL, formatQueryParams } from './utils/url';
|
package/dist/index.js
CHANGED
|
@@ -1,175 +1,212 @@
|
|
|
1
|
-
class
|
|
2
|
-
constructor(e,
|
|
3
|
-
this.maxSize = e, this.ttlMs =
|
|
1
|
+
class h {
|
|
2
|
+
constructor(e, n) {
|
|
3
|
+
this.maxSize = e, this.ttlMs = n, this.items = /* @__PURE__ */ new Map();
|
|
4
4
|
}
|
|
5
5
|
get(e) {
|
|
6
|
-
const
|
|
7
|
-
if (!
|
|
6
|
+
const n = this.items.get(e);
|
|
7
|
+
if (!n)
|
|
8
8
|
return;
|
|
9
|
-
const
|
|
10
|
-
if (
|
|
9
|
+
const r = Date.now();
|
|
10
|
+
if (n.expiresAt <= r) {
|
|
11
11
|
this.items.delete(e);
|
|
12
12
|
return;
|
|
13
13
|
}
|
|
14
|
-
return this.items.delete(e), this.items.set(e,
|
|
14
|
+
return this.items.delete(e), this.items.set(e, n), n.value;
|
|
15
15
|
}
|
|
16
|
-
set(e,
|
|
17
|
-
const
|
|
18
|
-
this.items.has(e) && this.items.delete(e), this.items.set(e, { value:
|
|
16
|
+
set(e, n) {
|
|
17
|
+
const r = Date.now();
|
|
18
|
+
this.items.has(e) && this.items.delete(e), this.items.set(e, { value: n, expiresAt: r + this.ttlMs }), this.prune(r);
|
|
19
19
|
}
|
|
20
20
|
prune(e) {
|
|
21
|
-
for (const [
|
|
22
|
-
|
|
21
|
+
for (const [n, r] of this.items)
|
|
22
|
+
r.expiresAt > e || this.items.delete(n);
|
|
23
23
|
for (; this.items.size > this.maxSize; ) {
|
|
24
|
-
const
|
|
25
|
-
if (
|
|
24
|
+
const n = this.items.keys().next().value;
|
|
25
|
+
if (n === void 0)
|
|
26
26
|
break;
|
|
27
|
-
this.items.delete(
|
|
27
|
+
this.items.delete(n);
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
-
function
|
|
31
|
+
function g(t = {}) {
|
|
32
32
|
const e = new URLSearchParams();
|
|
33
|
-
for (const [
|
|
33
|
+
for (const [r, s] of Object.entries(t))
|
|
34
34
|
if (s != null) {
|
|
35
35
|
if (Array.isArray(s)) {
|
|
36
36
|
s.forEach((i) => {
|
|
37
|
-
e.append(
|
|
37
|
+
e.append(r, String(i));
|
|
38
38
|
});
|
|
39
39
|
continue;
|
|
40
40
|
}
|
|
41
|
-
e.append(
|
|
41
|
+
e.append(r, String(s));
|
|
42
42
|
}
|
|
43
|
-
const
|
|
44
|
-
return
|
|
43
|
+
const n = e.toString();
|
|
44
|
+
return n ? `?${n}` : "";
|
|
45
45
|
}
|
|
46
|
-
function S(t, e = "",
|
|
47
|
-
const
|
|
46
|
+
function S(t, e = "", n = {}) {
|
|
47
|
+
const r = t.endsWith("/") ? t : `${t}/`, s = new URL(e, r), i = g(n);
|
|
48
48
|
return s.search = i, s.toString();
|
|
49
49
|
}
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
const T = "https://www.ndbc.noaa.gov/data/realtime2/", E = 5 * 60 * 1e3, y = 1024, b = new h(
|
|
51
|
+
y,
|
|
52
|
+
E
|
|
53
53
|
);
|
|
54
|
-
function
|
|
54
|
+
function _(t, e = "txt", n = T) {
|
|
55
55
|
const s = `${t.toUpperCase()}.${e}`;
|
|
56
|
-
return S(
|
|
56
|
+
return S(n, s);
|
|
57
57
|
}
|
|
58
|
-
async function
|
|
58
|
+
async function Z(t) {
|
|
59
59
|
const {
|
|
60
60
|
buoyId: e,
|
|
61
|
-
type:
|
|
62
|
-
fetch:
|
|
61
|
+
type: n = "txt",
|
|
62
|
+
fetch: r = fetch,
|
|
63
63
|
requestInit: s,
|
|
64
|
-
baseUrl: i =
|
|
64
|
+
baseUrl: i = T
|
|
65
65
|
} = t, a = e.toUpperCase();
|
|
66
|
-
if (!
|
|
66
|
+
if (!r)
|
|
67
67
|
throw new Error("No fetch implementation available.");
|
|
68
|
-
const
|
|
69
|
-
if (
|
|
70
|
-
return
|
|
71
|
-
const
|
|
68
|
+
const c = `${i}|${a}|${n}`, u = b.get(c);
|
|
69
|
+
if (u !== void 0)
|
|
70
|
+
return u;
|
|
71
|
+
const o = _(a, n, i), m = await r(o, s);
|
|
72
72
|
if (!m.ok)
|
|
73
|
-
throw new Error(`Failed to fetch ${
|
|
74
|
-
const
|
|
75
|
-
return
|
|
73
|
+
throw new Error(`Failed to fetch ${o}: ${m.status}`);
|
|
74
|
+
const d = await m.text();
|
|
75
|
+
return b.set(c, d), d;
|
|
76
76
|
}
|
|
77
|
-
const
|
|
78
|
-
|
|
77
|
+
const I = "https://www.ndbc.noaa.gov/activestations.xml", C = "https://www.ndbc.noaa.gov/data/stations/station_table.txt", v = 10 * 60 * 1e3, L = 8, D = 12 * 60 * 60 * 1e3, x = 8, w = new h(L, v), p = new h(
|
|
78
|
+
x,
|
|
79
79
|
D
|
|
80
80
|
);
|
|
81
|
-
function
|
|
82
|
-
const e = /* @__PURE__ */ new Set();
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
81
|
+
function M(t) {
|
|
82
|
+
const e = [], n = /* @__PURE__ */ new Set(), r = /<station[^>]*\bid="([A-Za-z0-9]{3,10})"/g;
|
|
83
|
+
let s;
|
|
84
|
+
for (; (s = r.exec(t)) !== null; ) {
|
|
85
|
+
const i = s[1];
|
|
86
|
+
if (!i)
|
|
87
|
+
continue;
|
|
88
|
+
const a = i.toUpperCase();
|
|
89
|
+
n.has(a) || (n.add(a), e.push(a));
|
|
90
|
+
}
|
|
91
|
+
return { ids: e, set: n };
|
|
92
|
+
}
|
|
93
|
+
function P(t) {
|
|
94
|
+
const e = [];
|
|
95
|
+
return t.split(/\r?\n/).forEach((n) => {
|
|
96
|
+
if (!n || n.startsWith("#"))
|
|
88
97
|
return;
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
}),
|
|
98
|
+
const r = n.indexOf("|"), i = (r === -1 ? n : n.slice(0, r)).trim().toUpperCase();
|
|
99
|
+
/^[A-Z0-9]{3,10}$/.test(i) && e.push(i);
|
|
100
|
+
}), e;
|
|
101
|
+
}
|
|
102
|
+
async function R(t, e, n) {
|
|
103
|
+
const r = w.get(e);
|
|
104
|
+
if (r !== void 0)
|
|
105
|
+
return r;
|
|
106
|
+
const s = await t(e, n);
|
|
107
|
+
if (!s.ok)
|
|
108
|
+
throw new Error(`Failed to fetch ${e}: ${s.status}`);
|
|
109
|
+
const i = await s.text(), a = M(i);
|
|
110
|
+
return w.set(e, a), a;
|
|
92
111
|
}
|
|
93
|
-
async function
|
|
94
|
-
const
|
|
112
|
+
async function V(t, e, n) {
|
|
113
|
+
const r = p.get(e);
|
|
114
|
+
if (r !== void 0)
|
|
115
|
+
return r;
|
|
116
|
+
const s = await t(e, n);
|
|
117
|
+
if (!s.ok)
|
|
118
|
+
throw new Error(`Failed to fetch ${e}: ${s.status}`);
|
|
119
|
+
const i = await s.text(), a = P(i);
|
|
120
|
+
return p.set(e, a), a;
|
|
121
|
+
}
|
|
122
|
+
async function U(t = {}) {
|
|
123
|
+
const {
|
|
124
|
+
fetch: e = fetch,
|
|
125
|
+
requestInit: n,
|
|
126
|
+
activeUrl: r = I,
|
|
127
|
+
stationTableUrl: s = C,
|
|
128
|
+
includeInactive: i
|
|
129
|
+
} = t;
|
|
95
130
|
if (!e)
|
|
96
131
|
throw new Error("No fetch implementation available.");
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const a = await i.text(), o = _(a);
|
|
104
|
-
return b.set(n, o), o;
|
|
132
|
+
const a = await R(e, r, n), c = i ? await V(e, s, n) : a.ids, u = a.set;
|
|
133
|
+
return {
|
|
134
|
+
active: a.ids,
|
|
135
|
+
all: c,
|
|
136
|
+
isActive: (o) => u.has(o.toUpperCase())
|
|
137
|
+
};
|
|
105
138
|
}
|
|
106
|
-
|
|
107
|
-
|
|
139
|
+
async function q(t = {}) {
|
|
140
|
+
const e = await U(t);
|
|
141
|
+
return t.includeInactive ? Array.from(e.all) : Array.from(e.active);
|
|
142
|
+
}
|
|
143
|
+
const $ = ["MM"];
|
|
144
|
+
function k(t, e) {
|
|
108
145
|
return !!(e.includes(t) || /^9{2,}(\.0+|\.9+)?$/.test(t));
|
|
109
146
|
}
|
|
110
|
-
function
|
|
111
|
-
if (
|
|
147
|
+
function H(t, e) {
|
|
148
|
+
if (k(t, e.missingTokens))
|
|
112
149
|
return e.missingValue;
|
|
113
150
|
if (e.coerceNumbers) {
|
|
114
|
-
const
|
|
115
|
-
if (!Number.isNaN(
|
|
116
|
-
return
|
|
151
|
+
const n = Number(t);
|
|
152
|
+
if (!Number.isNaN(n))
|
|
153
|
+
return n;
|
|
117
154
|
}
|
|
118
155
|
return t;
|
|
119
156
|
}
|
|
120
157
|
function f(t, e = {}) {
|
|
121
|
-
const
|
|
158
|
+
const n = {
|
|
122
159
|
coerceNumbers: e.coerceNumbers ?? !0,
|
|
123
160
|
missingValue: e.missingValue ?? null,
|
|
124
|
-
missingTokens: e.missingTokens ??
|
|
161
|
+
missingTokens: e.missingTokens ?? $
|
|
125
162
|
};
|
|
126
|
-
return t.trim().split(/\s+/).filter(Boolean).map((
|
|
163
|
+
return t.trim().split(/\s+/).filter(Boolean).map((r) => H(r, n));
|
|
127
164
|
}
|
|
128
|
-
function
|
|
165
|
+
function W(t, e) {
|
|
129
166
|
return t.startsWith(`${e} `);
|
|
130
167
|
}
|
|
131
|
-
function
|
|
168
|
+
function B(t) {
|
|
132
169
|
return t.split(/\r?\n/).map((e) => e.trim()).filter((e) => e.length > 0);
|
|
133
170
|
}
|
|
134
|
-
function
|
|
135
|
-
const
|
|
136
|
-
(
|
|
171
|
+
function O(t, e = {}) {
|
|
172
|
+
const n = e.commentPrefix ?? "#", r = B(t).filter(
|
|
173
|
+
(N) => !W(N, n)
|
|
137
174
|
);
|
|
138
|
-
if (
|
|
175
|
+
if (r.length === 0)
|
|
139
176
|
return { headers: [], units: [], rows: [], rawRows: [] };
|
|
140
|
-
const s =
|
|
177
|
+
const s = r[0] ?? "", i = {
|
|
141
178
|
coerceNumbers: !1,
|
|
142
179
|
missingValue: null,
|
|
143
180
|
missingTokens: []
|
|
144
181
|
}, a = f(s, i).map(String);
|
|
145
|
-
let
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
return
|
|
150
|
-
}),
|
|
182
|
+
let c = [], u = 1;
|
|
183
|
+
const o = r[1];
|
|
184
|
+
o && o.startsWith(n) && (c = f(o, i).map((N) => {
|
|
185
|
+
const l = String(N);
|
|
186
|
+
return l.startsWith(n) ? l.slice(n.length) : l;
|
|
187
|
+
}), u = 2);
|
|
151
188
|
const m = {
|
|
152
189
|
coerceNumbers: e.coerceNumbers,
|
|
153
190
|
missingValue: e.missingValue,
|
|
154
191
|
missingTokens: e.missingTokens
|
|
155
|
-
},
|
|
192
|
+
}, d = r.slice(u), A = d.map((N) => f(N, m));
|
|
156
193
|
return {
|
|
157
194
|
headers: a,
|
|
158
|
-
units:
|
|
159
|
-
rows:
|
|
160
|
-
rawRows:
|
|
195
|
+
units: c,
|
|
196
|
+
rows: A,
|
|
197
|
+
rawRows: d
|
|
161
198
|
};
|
|
162
199
|
}
|
|
163
|
-
function
|
|
164
|
-
const { headers: e, rows:
|
|
165
|
-
return
|
|
200
|
+
function z(t) {
|
|
201
|
+
const { headers: e, rows: n } = t;
|
|
202
|
+
return n.map((r) => {
|
|
166
203
|
const s = {};
|
|
167
204
|
return e.forEach((i, a) => {
|
|
168
|
-
s[i] =
|
|
205
|
+
s[i] = r[a] ?? null;
|
|
169
206
|
}), s;
|
|
170
207
|
});
|
|
171
208
|
}
|
|
172
|
-
function
|
|
209
|
+
function F() {
|
|
173
210
|
return {
|
|
174
211
|
airTemperature: Number.NaN,
|
|
175
212
|
day: Number.NaN,
|
|
@@ -196,7 +233,7 @@ function $() {
|
|
|
196
233
|
year: Number.NaN
|
|
197
234
|
};
|
|
198
235
|
}
|
|
199
|
-
const
|
|
236
|
+
const G = {
|
|
200
237
|
"#YY": (t, e) => {
|
|
201
238
|
t.year = Number(e);
|
|
202
239
|
},
|
|
@@ -258,22 +295,22 @@ const k = {
|
|
|
258
295
|
t.water.significantHeight = Number(e);
|
|
259
296
|
}
|
|
260
297
|
};
|
|
261
|
-
function
|
|
262
|
-
const e =
|
|
263
|
-
return Object.entries(t).forEach(([
|
|
264
|
-
const s =
|
|
265
|
-
s && s(e,
|
|
298
|
+
function Y(t) {
|
|
299
|
+
const e = F();
|
|
300
|
+
return Object.entries(t).forEach(([n, r]) => {
|
|
301
|
+
const s = G[n];
|
|
302
|
+
s && s(e, r);
|
|
266
303
|
}), e;
|
|
267
304
|
}
|
|
268
|
-
function
|
|
269
|
-
const
|
|
270
|
-
...
|
|
271
|
-
missingValue:
|
|
272
|
-
}), s =
|
|
273
|
-
if (
|
|
274
|
-
const a = i.map((
|
|
275
|
-
const
|
|
276
|
-
return { ...
|
|
305
|
+
function X(t, e, n = {}) {
|
|
306
|
+
const r = O(e, {
|
|
307
|
+
...n,
|
|
308
|
+
missingValue: n.missingValue ?? Number.NaN
|
|
309
|
+
}), s = z(r), i = s.map((a) => Y(a));
|
|
310
|
+
if (n.includeUnknownFields) {
|
|
311
|
+
const a = i.map((c, u) => {
|
|
312
|
+
const o = s[u];
|
|
313
|
+
return { ...c, ...o };
|
|
277
314
|
});
|
|
278
315
|
return {
|
|
279
316
|
id: t,
|
|
@@ -285,21 +322,22 @@ function O(t, e, r = {}) {
|
|
|
285
322
|
measurements: i
|
|
286
323
|
};
|
|
287
324
|
}
|
|
288
|
-
function
|
|
289
|
-
const { year: e, month:
|
|
290
|
-
return new Date(Date.UTC(e,
|
|
325
|
+
function j(t) {
|
|
326
|
+
const { year: e, month: n, day: r, hour: s, minute: i } = t;
|
|
327
|
+
return new Date(Date.UTC(e, n - 1, r, s, i));
|
|
291
328
|
}
|
|
292
329
|
export {
|
|
293
|
-
|
|
330
|
+
_ as buildRealtimeUrl,
|
|
294
331
|
S as buildURL,
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
332
|
+
F as createMeasurement,
|
|
333
|
+
q as fetchBuoyList,
|
|
334
|
+
Z as fetchRealtimeData,
|
|
335
|
+
U as fetchStationIndex,
|
|
336
|
+
g as formatQueryParams,
|
|
337
|
+
j as getMeasurementDate,
|
|
338
|
+
z as objectifyTable,
|
|
339
|
+
X as parseRealtimeData,
|
|
340
|
+
O as parseRealtimeTable,
|
|
303
341
|
f as parseRow
|
|
304
342
|
};
|
|
305
343
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/utils/lru.ts","../src/utils/url.ts","../src/realtime/fetch.ts","../src/stations/list.ts","../src/realtime/parser.ts","../src/utils/date.ts"],"sourcesContent":["export class LruCache<K, V> {\n private readonly items = new Map<K, { value: V; expiresAt: number }>();\n\n constructor(\n private readonly maxSize: number,\n private readonly ttlMs: number,\n ) {}\n\n get(key: K): V | undefined {\n const entry = this.items.get(key);\n if (!entry) {\n return undefined;\n }\n\n const now = Date.now();\n if (entry.expiresAt <= now) {\n this.items.delete(key);\n return undefined;\n }\n\n this.items.delete(key);\n this.items.set(key, entry);\n return entry.value;\n }\n\n set(key: K, value: V): void {\n const now = Date.now();\n if (this.items.has(key)) {\n this.items.delete(key);\n }\n\n this.items.set(key, { value, expiresAt: now + this.ttlMs });\n this.prune(now);\n }\n\n private prune(now: number): void {\n for (const [key, entry] of this.items) {\n if (entry.expiresAt > now) {\n continue;\n }\n this.items.delete(key);\n }\n\n while (this.items.size > this.maxSize) {\n const oldestKey = this.items.keys().next().value as K | undefined;\n if (oldestKey === undefined) {\n break;\n }\n this.items.delete(oldestKey);\n }\n }\n}\n","export type QueryParamValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | Array<string | number | boolean>;\n\nexport type QueryParams = Record<string, QueryParamValue>;\n\nexport function formatQueryParams(params: QueryParams = {}): string {\n const searchParams = new URLSearchParams();\n\n for (const [key, value] of Object.entries(params)) {\n if (value === null || value === undefined) {\n continue;\n }\n\n if (Array.isArray(value)) {\n value.forEach(item => {\n searchParams.append(key, String(item));\n });\n continue;\n }\n\n searchParams.append(key, String(value));\n }\n\n const query = searchParams.toString();\n return query ? `?${query}` : '';\n}\n\nexport function buildURL(\n base: string,\n path = '',\n params: QueryParams = {},\n): string {\n const normalizedBase = base.endsWith('/') ? base : `${base}/`;\n const url = new URL(path, normalizedBase);\n const query = formatQueryParams(params);\n url.search = query;\n return url.toString();\n}\n","import { LruCache } from '../utils/lru';\nimport { buildURL } from '../utils/url';\n\nexport interface FetchRealtimeOptions {\n buoyId: string;\n type?: string;\n fetch?: typeof fetch;\n requestInit?: RequestInit;\n baseUrl?: string;\n}\n\nconst DEFAULT_BASE_URL = 'https://www.ndbc.noaa.gov/data/realtime2/';\nconst CACHE_TTL_MS = 5 * 60 * 1000;\nconst CACHE_MAX_SIZE = 256;\nconst REALTIME_CACHE = new LruCache<string, string>(\n CACHE_MAX_SIZE,\n CACHE_TTL_MS,\n);\n\nexport function buildRealtimeUrl(\n buoyId: string,\n type = 'txt',\n baseUrl = DEFAULT_BASE_URL,\n): string {\n const normalizedBuoyId = buoyId.toUpperCase();\n const filename = `${normalizedBuoyId}.${type}`;\n return buildURL(baseUrl, filename);\n}\n\nexport async function fetchRealtimeData(\n options: FetchRealtimeOptions,\n): Promise<string> {\n const {\n buoyId,\n type = 'txt',\n fetch: fetchImpl = fetch,\n requestInit,\n baseUrl = DEFAULT_BASE_URL,\n } = options;\n const normalizedBuoyId = buoyId.toUpperCase();\n\n if (!fetchImpl) {\n throw new Error('No fetch implementation available.');\n }\n\n const cacheKey = `${baseUrl}|${normalizedBuoyId}|${type}`;\n const cached = REALTIME_CACHE.get(cacheKey);\n if (cached !== undefined) {\n return cached;\n }\n\n const url = buildRealtimeUrl(normalizedBuoyId, type, baseUrl);\n const response = await fetchImpl(url, requestInit);\n\n if (!response.ok) {\n throw new Error(`Failed to fetch ${url}: ${response.status}`);\n }\n\n const body = await response.text();\n REALTIME_CACHE.set(cacheKey, body);\n return body;\n}\n","import { LruCache } from '../utils/lru';\n\nexport interface FetchBuoyListOptions {\n fetch?: typeof fetch;\n requestInit?: RequestInit;\n url?: string;\n}\n\nconst DEFAULT_BUOY_LIST_URL = 'https://www.ndbc.noaa.gov/activestations.txt';\nconst BUOY_LIST_CACHE_TTL_MS = 4 * 60 * 60 * 1000;\nconst BUOY_LIST_CACHE_MAX_SIZE = 4;\nconst BUOY_LIST_CACHE = new LruCache<string, string[]>(\n BUOY_LIST_CACHE_MAX_SIZE,\n BUOY_LIST_CACHE_TTL_MS,\n);\n\nfunction parseBuoyList(rawText: string): string[] {\n const ids = new Set<string>();\n\n rawText\n .split(/\\r?\\n/)\n .map(line => line.trim())\n .filter(line => line.length > 0)\n .forEach(line => {\n if (line.startsWith('#')) {\n return;\n }\n\n const [token] = line.split(/\\s+/);\n if (!token) {\n return;\n }\n\n const normalized = token.toUpperCase();\n if (normalized === 'STATION' || normalized === 'STN') {\n return;\n }\n\n if (!/^[A-Z0-9]{3,10}$/.test(normalized)) {\n return;\n }\n\n ids.add(normalized);\n });\n\n return Array.from(ids);\n}\n\nexport async function fetchBuoyList(\n options: FetchBuoyListOptions = {},\n): Promise<string[]> {\n const { fetch: fetchImpl = fetch, requestInit, url = DEFAULT_BUOY_LIST_URL } =\n options;\n\n if (!fetchImpl) {\n throw new Error('No fetch implementation available.');\n }\n\n const cached = BUOY_LIST_CACHE.get(url);\n if (cached !== undefined) {\n return cached;\n }\n\n const response = await fetchImpl(url, requestInit);\n if (!response.ok) {\n throw new Error(`Failed to fetch ${url}: ${response.status}`);\n }\n\n const body = await response.text();\n const list = parseBuoyList(body);\n BUOY_LIST_CACHE.set(url, list);\n return list;\n}\n","import { BuoyData, Measurement } from '../models/measurement';\nimport { ParsedValue, RealtimeRecord, RealtimeTable } from '../models/table';\n\nexport interface ParseRowOptions {\n coerceNumbers?: boolean;\n missingValue?: number | null;\n missingTokens?: string[];\n}\n\nexport interface ParseRealtimeTableOptions extends ParseRowOptions {\n commentPrefix?: string;\n}\n\nexport interface ParseRealtimeDataOptions extends ParseRealtimeTableOptions {\n includeUnknownFields?: boolean;\n}\n\nconst DEFAULT_MISSING_TOKENS = ['MM'];\n\nfunction isMissingToken(value: string, missingTokens: string[]): boolean {\n if (missingTokens.includes(value)) {\n return true;\n }\n\n // NDBC missing values are often 9s (e.g. 99, 999, 9999, 99.0).\n if (/^9{2,}(\\.0+|\\.9+)?$/.test(value)) {\n return true;\n }\n\n return false;\n}\n\nfunction coerceValue(\n raw: string,\n options: Required<ParseRowOptions>,\n): ParsedValue {\n if (isMissingToken(raw, options.missingTokens)) {\n return options.missingValue;\n }\n\n if (options.coerceNumbers) {\n const numeric = Number(raw);\n if (!Number.isNaN(numeric)) {\n return numeric;\n }\n }\n\n return raw;\n}\n\nexport function parseRow(\n rawRow: string,\n options: ParseRowOptions = {},\n): ParsedValue[] {\n const resolved: Required<ParseRowOptions> = {\n coerceNumbers: options.coerceNumbers ?? true,\n missingValue: options.missingValue ?? null,\n missingTokens: options.missingTokens ?? DEFAULT_MISSING_TOKENS,\n };\n\n return rawRow\n .trim()\n .split(/\\s+/)\n .filter(Boolean)\n .map(value => coerceValue(value, resolved));\n}\n\nfunction isCommentLine(line: string, commentPrefix: string): boolean {\n return line.startsWith(`${commentPrefix} `);\n}\n\nfunction normalizeLines(rawText: string): string[] {\n return rawText\n .split(/\\r?\\n/)\n .map(line => line.trim())\n .filter(line => line.length > 0);\n}\n\nexport function parseRealtimeTable(\n rawText: string,\n options: ParseRealtimeTableOptions = {},\n): RealtimeTable {\n const commentPrefix = options.commentPrefix ?? '#';\n const lines = normalizeLines(rawText).filter(\n line => !isCommentLine(line, commentPrefix),\n );\n\n if (lines.length === 0) {\n return { headers: [], units: [], rows: [], rawRows: [] };\n }\n\n const headerLine = lines[0] ?? '';\n const headerOptions: ParseRowOptions = {\n coerceNumbers: false,\n missingValue: null,\n missingTokens: [],\n };\n const headers = parseRow(headerLine, headerOptions).map(String);\n\n let units: string[] = [];\n let dataStartIndex = 1;\n\n const unitLine = lines[1];\n if (unitLine && unitLine.startsWith(commentPrefix)) {\n units = parseRow(unitLine, headerOptions).map(token => {\n const text = String(token);\n return text.startsWith(commentPrefix)\n ? text.slice(commentPrefix.length)\n : text;\n });\n dataStartIndex = 2;\n }\n\n const rowOptions: ParseRowOptions = {\n coerceNumbers: options.coerceNumbers,\n missingValue: options.missingValue,\n missingTokens: options.missingTokens,\n };\n\n const dataRows = lines.slice(dataStartIndex);\n const rows = dataRows.map(row => parseRow(row, rowOptions));\n\n return {\n headers,\n units,\n rows,\n rawRows: dataRows,\n };\n}\n\nexport function objectifyTable(table: RealtimeTable): RealtimeRecord[] {\n const { headers, rows } = table;\n return rows.map(row => {\n const record: RealtimeRecord = {};\n headers.forEach((header, index) => {\n record[header] = row[index] ?? null;\n });\n return record;\n });\n}\n\nexport function createMeasurement(): Measurement {\n return {\n airTemperature: Number.NaN,\n day: Number.NaN,\n dewpointTemperature: Number.NaN,\n hour: Number.NaN,\n minute: Number.NaN,\n month: Number.NaN,\n pressureTendancy: Number.NaN,\n seaLevelPressure: Number.NaN,\n stationVisibility: Number.NaN,\n water: {\n averagePeriod: Number.NaN,\n dominantDirection: Number.NaN,\n dominantPeriod: Number.NaN,\n significantHeight: Number.NaN,\n surfaceTemperature: Number.NaN,\n tide: Number.NaN,\n },\n wind: {\n averageSpeed: Number.NaN,\n direction: Number.NaN,\n peakGustSpeed: Number.NaN,\n },\n year: Number.NaN,\n };\n}\n\nconst FIELD_MAPPINGS: Record<string, (m: Measurement, value: ParsedValue) => void> = {\n '#YY': (m, value) => {\n m.year = Number(value);\n },\n YY: (m, value) => {\n m.year = Number(value);\n },\n MM: (m, value) => {\n m.month = Number(value);\n },\n DD: (m, value) => {\n m.day = Number(value);\n },\n hh: (m, value) => {\n m.hour = Number(value);\n },\n mm: (m, value) => {\n m.minute = Number(value);\n },\n APD: (m, value) => {\n m.water.averagePeriod = Number(value);\n },\n ATMP: (m, value) => {\n m.airTemperature = Number(value);\n },\n DEWP: (m, value) => {\n m.dewpointTemperature = Number(value);\n },\n DPD: (m, value) => {\n m.water.dominantPeriod = Number(value);\n },\n GST: (m, value) => {\n m.wind.peakGustSpeed = Number(value);\n },\n MWD: (m, value) => {\n m.water.dominantDirection = Number(value);\n },\n PRES: (m, value) => {\n m.seaLevelPressure = Number(value);\n },\n PTDY: (m, value) => {\n m.pressureTendancy = Number(value);\n },\n TIDE: (m, value) => {\n m.water.tide = Number(value);\n },\n VIS: (m, value) => {\n m.stationVisibility = Number(value);\n },\n WDIR: (m, value) => {\n m.wind.direction = Number(value);\n },\n WSPD: (m, value) => {\n m.wind.averageSpeed = Number(value);\n },\n WTMP: (m, value) => {\n m.water.surfaceTemperature = Number(value);\n },\n WVHT: (m, value) => {\n m.water.significantHeight = Number(value);\n },\n};\n\nfunction toMeasurement(record: RealtimeRecord): Measurement {\n const measurement = createMeasurement();\n Object.entries(record).forEach(([field, value]) => {\n const mapper = FIELD_MAPPINGS[field];\n if (mapper) {\n mapper(measurement, value);\n }\n });\n return measurement;\n}\n\nexport function parseRealtimeData(\n buoyId: string,\n rawText: string,\n options: ParseRealtimeDataOptions = {},\n): BuoyData {\n const table = parseRealtimeTable(rawText, {\n ...options,\n missingValue: options.missingValue ?? Number.NaN,\n });\n const records = objectifyTable(table);\n\n const measurements = records.map(record => toMeasurement(record));\n\n if (options.includeUnknownFields) {\n const measurementsWithUnknowns = measurements.map((measurement, index) => {\n const record = records[index];\n return { ...measurement, ...record } as Measurement;\n });\n\n return {\n id: buoyId,\n measurements: measurementsWithUnknowns,\n };\n }\n\n return {\n id: buoyId,\n measurements,\n };\n}\n","import { Measurement } from '../models/measurement';\n\n/**\n * Returns a UTC Date instance for a buoy measurement.\n */\nexport function getMeasurementDate(measurement: Measurement): Date {\n const { year, month, day, hour, minute } = measurement;\n return new Date(Date.UTC(year, month - 1, day, hour, minute));\n}\n"],"names":["LruCache","maxSize","ttlMs","key","entry","now","value","oldestKey","formatQueryParams","params","searchParams","item","query","buildURL","base","path","normalizedBase","url","DEFAULT_BASE_URL","CACHE_TTL_MS","CACHE_MAX_SIZE","REALTIME_CACHE","buildRealtimeUrl","buoyId","type","baseUrl","filename","fetchRealtimeData","options","fetchImpl","requestInit","normalizedBuoyId","cacheKey","cached","response","body","DEFAULT_BUOY_LIST_URL","BUOY_LIST_CACHE_TTL_MS","BUOY_LIST_CACHE_MAX_SIZE","BUOY_LIST_CACHE","parseBuoyList","rawText","ids","line","token","normalized","fetchBuoyList","list","DEFAULT_MISSING_TOKENS","isMissingToken","missingTokens","coerceValue","raw","numeric","parseRow","rawRow","resolved","isCommentLine","commentPrefix","normalizeLines","parseRealtimeTable","lines","headerLine","headerOptions","headers","units","dataStartIndex","unitLine","text","rowOptions","dataRows","rows","row","objectifyTable","table","record","header","index","createMeasurement","FIELD_MAPPINGS","m","toMeasurement","measurement","field","mapper","parseRealtimeData","records","measurements","measurementsWithUnknowns","getMeasurementDate","year","month","day","hour","minute"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/utils/lru.ts","../src/utils/url.ts","../src/realtime/fetch.ts","../src/stations/list.ts","../src/realtime/parser.ts","../src/utils/date.ts"],"sourcesContent":["export class LruCache<K, V> {\n private readonly items = new Map<K, { value: V; expiresAt: number }>();\n\n constructor(\n private readonly maxSize: number,\n private readonly ttlMs: number,\n ) {}\n\n get(key: K): V | undefined {\n const entry = this.items.get(key);\n if (!entry) {\n return undefined;\n }\n\n const now = Date.now();\n if (entry.expiresAt <= now) {\n this.items.delete(key);\n return undefined;\n }\n\n this.items.delete(key);\n this.items.set(key, entry);\n return entry.value;\n }\n\n set(key: K, value: V): void {\n const now = Date.now();\n if (this.items.has(key)) {\n this.items.delete(key);\n }\n\n this.items.set(key, { value, expiresAt: now + this.ttlMs });\n this.prune(now);\n }\n\n private prune(now: number): void {\n for (const [key, entry] of this.items) {\n if (entry.expiresAt > now) {\n continue;\n }\n this.items.delete(key);\n }\n\n while (this.items.size > this.maxSize) {\n const oldestKey = this.items.keys().next().value as K | undefined;\n if (oldestKey === undefined) {\n break;\n }\n this.items.delete(oldestKey);\n }\n }\n}\n","export type QueryParamValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | Array<string | number | boolean>;\n\nexport type QueryParams = Record<string, QueryParamValue>;\n\nexport function formatQueryParams(params: QueryParams = {}): string {\n const searchParams = new URLSearchParams();\n\n for (const [key, value] of Object.entries(params)) {\n if (value === null || value === undefined) {\n continue;\n }\n\n if (Array.isArray(value)) {\n value.forEach(item => {\n searchParams.append(key, String(item));\n });\n continue;\n }\n\n searchParams.append(key, String(value));\n }\n\n const query = searchParams.toString();\n return query ? `?${query}` : '';\n}\n\nexport function buildURL(\n base: string,\n path = '',\n params: QueryParams = {},\n): string {\n const normalizedBase = base.endsWith('/') ? base : `${base}/`;\n const url = new URL(path, normalizedBase);\n const query = formatQueryParams(params);\n url.search = query;\n return url.toString();\n}\n","import { LruCache } from '../utils/lru';\nimport { buildURL } from '../utils/url';\n\nexport interface FetchRealtimeOptions {\n buoyId: string;\n type?: string;\n fetch?: typeof fetch;\n requestInit?: RequestInit;\n baseUrl?: string;\n}\n\nconst DEFAULT_BASE_URL = 'https://www.ndbc.noaa.gov/data/realtime2/';\nconst CACHE_TTL_MS = 5 * 60 * 1000;\nconst CACHE_MAX_SIZE = 1024;\nconst REALTIME_CACHE = new LruCache<string, string>(\n CACHE_MAX_SIZE,\n CACHE_TTL_MS,\n);\n\nexport function buildRealtimeUrl(\n buoyId: string,\n type = 'txt',\n baseUrl = DEFAULT_BASE_URL,\n): string {\n const normalizedBuoyId = buoyId.toUpperCase();\n const filename = `${normalizedBuoyId}.${type}`;\n return buildURL(baseUrl, filename);\n}\n\nexport async function fetchRealtimeData(\n options: FetchRealtimeOptions,\n): Promise<string> {\n const {\n buoyId,\n type = 'txt',\n fetch: fetchImpl = fetch,\n requestInit,\n baseUrl = DEFAULT_BASE_URL,\n } = options;\n const normalizedBuoyId = buoyId.toUpperCase();\n\n if (!fetchImpl) {\n throw new Error('No fetch implementation available.');\n }\n\n const cacheKey = `${baseUrl}|${normalizedBuoyId}|${type}`;\n const cached = REALTIME_CACHE.get(cacheKey);\n if (cached !== undefined) {\n return cached;\n }\n\n const url = buildRealtimeUrl(normalizedBuoyId, type, baseUrl);\n const response = await fetchImpl(url, requestInit);\n\n if (!response.ok) {\n throw new Error(`Failed to fetch ${url}: ${response.status}`);\n }\n\n const body = await response.text();\n REALTIME_CACHE.set(cacheKey, body);\n return body;\n}\n","import { LruCache } from '../utils/lru';\n\nexport interface FetchBuoyListOptions {\n fetch?: typeof fetch;\n requestInit?: RequestInit;\n /**\n * URL for the active station XML feed. Defaults to NDBC.\n */\n activeUrl?: string;\n /**\n * URL for the full station catalog. Defaults to NDBC.\n */\n stationTableUrl?: string;\n /**\n * When true, includes inactive stations from the catalog.\n */\n includeInactive?: boolean;\n}\n\nexport interface FetchStationIndexOptions extends FetchBuoyListOptions {}\n\nexport interface StationIndex {\n /** Active station IDs from the XML feed. */\n active: readonly string[];\n /** All stations from the catalog (includes inactive when available). */\n all: readonly string[];\n /** Efficient membership check for active stations. */\n isActive: (id: string) => boolean;\n}\n\nconst DEFAULT_ACTIVE_STATIONS_URL = 'https://www.ndbc.noaa.gov/activestations.xml';\nconst DEFAULT_STATION_TABLE_URL =\n 'https://www.ndbc.noaa.gov/data/stations/station_table.txt';\n\nconst ACTIVE_CACHE_TTL_MS = 10 * 60 * 1000;\nconst ACTIVE_CACHE_MAX_SIZE = 8;\nconst STATION_TABLE_CACHE_TTL_MS = 12 * 60 * 60 * 1000;\nconst STATION_TABLE_CACHE_MAX_SIZE = 8;\n\nconst ACTIVE_CACHE = new LruCache<\n string,\n { ids: readonly string[]; set: ReadonlySet<string> }\n>(ACTIVE_CACHE_MAX_SIZE, ACTIVE_CACHE_TTL_MS);\n\nconst STATION_TABLE_CACHE = new LruCache<string, readonly string[]>(\n STATION_TABLE_CACHE_MAX_SIZE,\n STATION_TABLE_CACHE_TTL_MS,\n);\n\nfunction parseActiveStationsXml(rawText: string): { ids: string[]; set: Set<string> } {\n const ids: string[] = [];\n const set = new Set<string>();\n const regex = /<station[^>]*\\bid=\"([A-Za-z0-9]{3,10})\"/g;\n let match: RegExpExecArray | null;\n\n while ((match = regex.exec(rawText)) !== null) {\n const rawId = match[1];\n if (!rawId) {\n continue;\n }\n const id = rawId.toUpperCase();\n if (set.has(id)) {\n continue;\n }\n set.add(id);\n ids.push(id);\n }\n\n return { ids, set };\n}\n\nfunction parseStationTable(rawText: string): string[] {\n const ids: string[] = [];\n\n rawText.split(/\\r?\\n/).forEach(line => {\n if (!line || line.startsWith('#')) {\n return;\n }\n\n const pipeIndex = line.indexOf('|');\n const token = pipeIndex === -1 ? line : line.slice(0, pipeIndex);\n const id = token.trim().toUpperCase();\n\n if (!/^[A-Z0-9]{3,10}$/.test(id)) {\n return;\n }\n\n ids.push(id);\n });\n\n return ids;\n}\n\nasync function fetchActiveStations(\n fetchImpl: typeof fetch,\n activeUrl: string,\n requestInit?: RequestInit,\n): Promise<{ ids: readonly string[]; set: ReadonlySet<string> }> {\n const cached = ACTIVE_CACHE.get(activeUrl);\n if (cached !== undefined) {\n return cached;\n }\n\n const response = await fetchImpl(activeUrl, requestInit);\n if (!response.ok) {\n throw new Error(`Failed to fetch ${activeUrl}: ${response.status}`);\n }\n\n const body = await response.text();\n const parsed = parseActiveStationsXml(body);\n ACTIVE_CACHE.set(activeUrl, parsed);\n return parsed;\n}\n\nasync function fetchStationTable(\n fetchImpl: typeof fetch,\n stationTableUrl: string,\n requestInit?: RequestInit,\n): Promise<readonly string[]> {\n const cached = STATION_TABLE_CACHE.get(stationTableUrl);\n if (cached !== undefined) {\n return cached;\n }\n\n const response = await fetchImpl(stationTableUrl, requestInit);\n if (!response.ok) {\n throw new Error(`Failed to fetch ${stationTableUrl}: ${response.status}`);\n }\n\n const body = await response.text();\n const parsed = parseStationTable(body);\n STATION_TABLE_CACHE.set(stationTableUrl, parsed);\n return parsed;\n}\n\nexport async function fetchStationIndex(\n options: FetchStationIndexOptions = {},\n): Promise<StationIndex> {\n const {\n fetch: fetchImpl = fetch,\n requestInit,\n activeUrl = DEFAULT_ACTIVE_STATIONS_URL,\n stationTableUrl = DEFAULT_STATION_TABLE_URL,\n includeInactive,\n } = options;\n\n if (!fetchImpl) {\n throw new Error('No fetch implementation available.');\n }\n\n const active = await fetchActiveStations(fetchImpl, activeUrl, requestInit);\n\n const all = includeInactive\n ? await fetchStationTable(fetchImpl, stationTableUrl, requestInit)\n : active.ids;\n\n const activeSet = active.set;\n\n return {\n active: active.ids,\n all,\n isActive: (id: string) => activeSet.has(id.toUpperCase()),\n };\n}\n\nexport async function fetchBuoyList(\n options: FetchBuoyListOptions = {},\n): Promise<string[]> {\n const index = await fetchStationIndex(options);\n return options.includeInactive ? Array.from(index.all) : Array.from(index.active);\n}\n","import { BuoyData, Measurement } from '../models/measurement';\nimport { ParsedValue, RealtimeRecord, RealtimeTable } from '../models/table';\n\nexport interface ParseRowOptions {\n coerceNumbers?: boolean;\n missingValue?: number | null;\n missingTokens?: string[];\n}\n\nexport interface ParseRealtimeTableOptions extends ParseRowOptions {\n commentPrefix?: string;\n}\n\nexport interface ParseRealtimeDataOptions extends ParseRealtimeTableOptions {\n includeUnknownFields?: boolean;\n}\n\nconst DEFAULT_MISSING_TOKENS = ['MM'];\n\nfunction isMissingToken(value: string, missingTokens: string[]): boolean {\n if (missingTokens.includes(value)) {\n return true;\n }\n\n // NDBC missing values are often 9s (e.g. 99, 999, 9999, 99.0).\n if (/^9{2,}(\\.0+|\\.9+)?$/.test(value)) {\n return true;\n }\n\n return false;\n}\n\nfunction coerceValue(\n raw: string,\n options: Required<ParseRowOptions>,\n): ParsedValue {\n if (isMissingToken(raw, options.missingTokens)) {\n return options.missingValue;\n }\n\n if (options.coerceNumbers) {\n const numeric = Number(raw);\n if (!Number.isNaN(numeric)) {\n return numeric;\n }\n }\n\n return raw;\n}\n\nexport function parseRow(\n rawRow: string,\n options: ParseRowOptions = {},\n): ParsedValue[] {\n const resolved: Required<ParseRowOptions> = {\n coerceNumbers: options.coerceNumbers ?? true,\n missingValue: options.missingValue ?? null,\n missingTokens: options.missingTokens ?? DEFAULT_MISSING_TOKENS,\n };\n\n return rawRow\n .trim()\n .split(/\\s+/)\n .filter(Boolean)\n .map(value => coerceValue(value, resolved));\n}\n\nfunction isCommentLine(line: string, commentPrefix: string): boolean {\n return line.startsWith(`${commentPrefix} `);\n}\n\nfunction normalizeLines(rawText: string): string[] {\n return rawText\n .split(/\\r?\\n/)\n .map(line => line.trim())\n .filter(line => line.length > 0);\n}\n\nexport function parseRealtimeTable(\n rawText: string,\n options: ParseRealtimeTableOptions = {},\n): RealtimeTable {\n const commentPrefix = options.commentPrefix ?? '#';\n const lines = normalizeLines(rawText).filter(\n line => !isCommentLine(line, commentPrefix),\n );\n\n if (lines.length === 0) {\n return { headers: [], units: [], rows: [], rawRows: [] };\n }\n\n const headerLine = lines[0] ?? '';\n const headerOptions: ParseRowOptions = {\n coerceNumbers: false,\n missingValue: null,\n missingTokens: [],\n };\n const headers = parseRow(headerLine, headerOptions).map(String);\n\n let units: string[] = [];\n let dataStartIndex = 1;\n\n const unitLine = lines[1];\n if (unitLine && unitLine.startsWith(commentPrefix)) {\n units = parseRow(unitLine, headerOptions).map(token => {\n const text = String(token);\n return text.startsWith(commentPrefix)\n ? text.slice(commentPrefix.length)\n : text;\n });\n dataStartIndex = 2;\n }\n\n const rowOptions: ParseRowOptions = {\n coerceNumbers: options.coerceNumbers,\n missingValue: options.missingValue,\n missingTokens: options.missingTokens,\n };\n\n const dataRows = lines.slice(dataStartIndex);\n const rows = dataRows.map(row => parseRow(row, rowOptions));\n\n return {\n headers,\n units,\n rows,\n rawRows: dataRows,\n };\n}\n\nexport function objectifyTable(table: RealtimeTable): RealtimeRecord[] {\n const { headers, rows } = table;\n return rows.map(row => {\n const record: RealtimeRecord = {};\n headers.forEach((header, index) => {\n record[header] = row[index] ?? null;\n });\n return record;\n });\n}\n\nexport function createMeasurement(): Measurement {\n return {\n airTemperature: Number.NaN,\n day: Number.NaN,\n dewpointTemperature: Number.NaN,\n hour: Number.NaN,\n minute: Number.NaN,\n month: Number.NaN,\n pressureTendancy: Number.NaN,\n seaLevelPressure: Number.NaN,\n stationVisibility: Number.NaN,\n water: {\n averagePeriod: Number.NaN,\n dominantDirection: Number.NaN,\n dominantPeriod: Number.NaN,\n significantHeight: Number.NaN,\n surfaceTemperature: Number.NaN,\n tide: Number.NaN,\n },\n wind: {\n averageSpeed: Number.NaN,\n direction: Number.NaN,\n peakGustSpeed: Number.NaN,\n },\n year: Number.NaN,\n };\n}\n\nconst FIELD_MAPPINGS: Record<string, (m: Measurement, value: ParsedValue) => void> = {\n '#YY': (m, value) => {\n m.year = Number(value);\n },\n YY: (m, value) => {\n m.year = Number(value);\n },\n MM: (m, value) => {\n m.month = Number(value);\n },\n DD: (m, value) => {\n m.day = Number(value);\n },\n hh: (m, value) => {\n m.hour = Number(value);\n },\n mm: (m, value) => {\n m.minute = Number(value);\n },\n APD: (m, value) => {\n m.water.averagePeriod = Number(value);\n },\n ATMP: (m, value) => {\n m.airTemperature = Number(value);\n },\n DEWP: (m, value) => {\n m.dewpointTemperature = Number(value);\n },\n DPD: (m, value) => {\n m.water.dominantPeriod = Number(value);\n },\n GST: (m, value) => {\n m.wind.peakGustSpeed = Number(value);\n },\n MWD: (m, value) => {\n m.water.dominantDirection = Number(value);\n },\n PRES: (m, value) => {\n m.seaLevelPressure = Number(value);\n },\n PTDY: (m, value) => {\n m.pressureTendancy = Number(value);\n },\n TIDE: (m, value) => {\n m.water.tide = Number(value);\n },\n VIS: (m, value) => {\n m.stationVisibility = Number(value);\n },\n WDIR: (m, value) => {\n m.wind.direction = Number(value);\n },\n WSPD: (m, value) => {\n m.wind.averageSpeed = Number(value);\n },\n WTMP: (m, value) => {\n m.water.surfaceTemperature = Number(value);\n },\n WVHT: (m, value) => {\n m.water.significantHeight = Number(value);\n },\n};\n\nfunction toMeasurement(record: RealtimeRecord): Measurement {\n const measurement = createMeasurement();\n Object.entries(record).forEach(([field, value]) => {\n const mapper = FIELD_MAPPINGS[field];\n if (mapper) {\n mapper(measurement, value);\n }\n });\n return measurement;\n}\n\nexport function parseRealtimeData(\n buoyId: string,\n rawText: string,\n options: ParseRealtimeDataOptions = {},\n): BuoyData {\n const table = parseRealtimeTable(rawText, {\n ...options,\n missingValue: options.missingValue ?? Number.NaN,\n });\n const records = objectifyTable(table);\n\n const measurements = records.map(record => toMeasurement(record));\n\n if (options.includeUnknownFields) {\n const measurementsWithUnknowns = measurements.map((measurement, index) => {\n const record = records[index];\n return { ...measurement, ...record } as Measurement;\n });\n\n return {\n id: buoyId,\n measurements: measurementsWithUnknowns,\n };\n }\n\n return {\n id: buoyId,\n measurements,\n };\n}\n","import { Measurement } from '../models/measurement';\n\n/**\n * Returns a UTC Date instance for a buoy measurement.\n */\nexport function getMeasurementDate(measurement: Measurement): Date {\n const { year, month, day, hour, minute } = measurement;\n return new Date(Date.UTC(year, month - 1, day, hour, minute));\n}\n"],"names":["LruCache","maxSize","ttlMs","key","entry","now","value","oldestKey","formatQueryParams","params","searchParams","item","query","buildURL","base","path","normalizedBase","url","DEFAULT_BASE_URL","CACHE_TTL_MS","CACHE_MAX_SIZE","REALTIME_CACHE","buildRealtimeUrl","buoyId","type","baseUrl","filename","fetchRealtimeData","options","fetchImpl","requestInit","normalizedBuoyId","cacheKey","cached","response","body","DEFAULT_ACTIVE_STATIONS_URL","DEFAULT_STATION_TABLE_URL","ACTIVE_CACHE_TTL_MS","ACTIVE_CACHE_MAX_SIZE","STATION_TABLE_CACHE_TTL_MS","STATION_TABLE_CACHE_MAX_SIZE","ACTIVE_CACHE","STATION_TABLE_CACHE","parseActiveStationsXml","rawText","ids","set","regex","match","rawId","id","parseStationTable","line","pipeIndex","fetchActiveStations","activeUrl","parsed","fetchStationTable","stationTableUrl","fetchStationIndex","includeInactive","active","all","activeSet","fetchBuoyList","index","DEFAULT_MISSING_TOKENS","isMissingToken","missingTokens","coerceValue","raw","numeric","parseRow","rawRow","resolved","isCommentLine","commentPrefix","normalizeLines","parseRealtimeTable","lines","headerLine","headerOptions","headers","units","dataStartIndex","unitLine","token","text","rowOptions","dataRows","rows","row","objectifyTable","table","record","header","createMeasurement","FIELD_MAPPINGS","m","toMeasurement","measurement","field","mapper","parseRealtimeData","records","measurements","measurementsWithUnknowns","getMeasurementDate","year","month","day","hour","minute"],"mappings":"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"}
|
package/dist/stations/list.d.ts
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
export interface FetchBuoyListOptions {
|
|
2
2
|
fetch?: typeof fetch;
|
|
3
3
|
requestInit?: RequestInit;
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* URL for the active station XML feed. Defaults to NDBC.
|
|
6
|
+
*/
|
|
7
|
+
activeUrl?: string;
|
|
8
|
+
/**
|
|
9
|
+
* URL for the full station catalog. Defaults to NDBC.
|
|
10
|
+
*/
|
|
11
|
+
stationTableUrl?: string;
|
|
12
|
+
/**
|
|
13
|
+
* When true, includes inactive stations from the catalog.
|
|
14
|
+
*/
|
|
15
|
+
includeInactive?: boolean;
|
|
5
16
|
}
|
|
17
|
+
export interface FetchStationIndexOptions extends FetchBuoyListOptions {
|
|
18
|
+
}
|
|
19
|
+
export interface StationIndex {
|
|
20
|
+
/** Active station IDs from the XML feed. */
|
|
21
|
+
active: readonly string[];
|
|
22
|
+
/** All stations from the catalog (includes inactive when available). */
|
|
23
|
+
all: readonly string[];
|
|
24
|
+
/** Efficient membership check for active stations. */
|
|
25
|
+
isActive: (id: string) => boolean;
|
|
26
|
+
}
|
|
27
|
+
export declare function fetchStationIndex(options?: FetchStationIndexOptions): Promise<StationIndex>;
|
|
6
28
|
export declare function fetchBuoyList(options?: FetchBuoyListOptions): Promise<string[]>;
|