+6
-6
@@ -1027,3 +1027,3 @@ /// <reference types="node" /> | ||
| readonly paths: { | ||
| readonly "/extremes": { | ||
| readonly "/tides/extremes": { | ||
| readonly get: { | ||
@@ -1069,3 +1069,3 @@ readonly summary: "Get extremes prediction for a location"; | ||
| }; | ||
| readonly "/timeline": { | ||
| readonly "/tides/timeline": { | ||
| readonly get: { | ||
@@ -1111,3 +1111,3 @@ readonly summary: "Get timeline prediction for a location"; | ||
| }; | ||
| readonly "/stations": { | ||
| readonly "/tides/stations": { | ||
| readonly get: { | ||
@@ -1197,3 +1197,3 @@ readonly summary: "Find stations"; | ||
| }; | ||
| readonly "/stations/{id}/extremes": { | ||
| readonly "/tides/stations/{id}/extremes": { | ||
| readonly get: { | ||
@@ -1246,3 +1246,3 @@ readonly summary: "Get extremes prediction for a specific station"; | ||
| }; | ||
| readonly "/stations/{id}/timeline": { | ||
| readonly "/tides/stations/{id}/timeline": { | ||
| readonly get: { | ||
@@ -1295,3 +1295,3 @@ readonly summary: "Get timeline prediction for a specific station"; | ||
| }; | ||
| readonly "/openapi.json": { | ||
| readonly "/tides/openapi.json": { | ||
| readonly get: { | ||
@@ -1298,0 +1298,0 @@ readonly summary: "Get OpenAPI specification"; |
+13
-13
@@ -6,3 +6,3 @@ import express, { Router, json } from "express"; | ||
| //#region package.json | ||
| var version = "0.1.0"; | ||
| var version = "0.2.0"; | ||
@@ -20,3 +20,3 @@ //#endregion | ||
| paths: { | ||
| "/extremes": { get: { | ||
| "/tides/extremes": { get: { | ||
| summary: "Get extremes prediction for a location", | ||
@@ -43,3 +43,3 @@ description: "Returns high and low tide predictions for the nearest station to the given coordinates", | ||
| } }, | ||
| "/timeline": { get: { | ||
| "/tides/timeline": { get: { | ||
| summary: "Get timeline prediction for a location", | ||
@@ -66,3 +66,3 @@ description: "Returns water level predictions at regular intervals for the nearest station", | ||
| } }, | ||
| "/stations": { get: { | ||
| "/tides/stations": { get: { | ||
| summary: "Find stations", | ||
@@ -131,3 +131,3 @@ description: "Find stations by ID or near a location", | ||
| } }, | ||
| "/stations/{id}/extremes": { get: { | ||
| "/tides/stations/{id}/extremes": { get: { | ||
| summary: "Get extremes prediction for a specific station", | ||
@@ -156,3 +156,3 @@ parameters: [ | ||
| } }, | ||
| "/stations/{id}/timeline": { get: { | ||
| "/tides/stations/{id}/timeline": { get: { | ||
| summary: "Get timeline prediction for a specific station", | ||
@@ -181,3 +181,3 @@ parameters: [ | ||
| } }, | ||
| "/openapi.json": { get: { | ||
| "/tides/openapi.json": { get: { | ||
| summary: "Get OpenAPI specification", | ||
@@ -408,6 +408,6 @@ responses: { "200": { | ||
| })); | ||
| router.get("/openapi.json", (req, res) => { | ||
| router.get("/tides/openapi.json", (req, res) => { | ||
| res.json(openapi_default); | ||
| }); | ||
| router.get("/extremes", (req, res) => { | ||
| router.get("/tides/extremes", (req, res) => { | ||
| res.json(getExtremesPrediction({ | ||
@@ -418,3 +418,3 @@ ...positionOptions(req), | ||
| }); | ||
| router.get("/timeline", (req, res) => { | ||
| router.get("/tides/timeline", (req, res) => { | ||
| try { | ||
@@ -429,3 +429,3 @@ res.json(getTimelinePrediction({ | ||
| }); | ||
| router.get("/stations", (req, res) => { | ||
| router.get("/tides/stations", (req, res) => { | ||
| if (req.query.id) try { | ||
@@ -445,3 +445,3 @@ return res.json(findStation(req.query.id)); | ||
| }); | ||
| router.get("/stations/:id/extremes", (req, res) => { | ||
| router.get("/tides/stations/:id/extremes", (req, res) => { | ||
| let station; | ||
@@ -455,3 +455,3 @@ try { | ||
| }); | ||
| router.get("/stations/:id/timeline", (req, res) => { | ||
| router.get("/tides/stations/:id/timeline", (req, res) => { | ||
| try { | ||
@@ -458,0 +458,0 @@ const station = findStation(req.params.id); |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.mjs","names":["pkg.version","openapiValidator","openapi","routes"],"sources":["../package.json","../src/openapi.ts","../src/routes.ts","../src/index.ts"],"sourcesContent":["","import pkg from \"../package.json\" with { type: \"json\" };\n\nexport default {\n openapi: \"3.0.3\",\n info: {\n title: \"Neaps Tide Prediction API\",\n version: pkg.version,\n description: \"HTTP JSON API for tide predictions using harmonic constituents\",\n license: {\n name: \"MIT\",\n },\n },\n paths: {\n \"/extremes\": {\n get: {\n summary: \"Get extremes prediction for a location\",\n description:\n \"Returns high and low tide predictions for the nearest station to the given coordinates\",\n parameters: [\n { $ref: \"#/components/parameters/latitude\" },\n { $ref: \"#/components/parameters/longitude\" },\n { $ref: \"#/components/parameters/start\" },\n { $ref: \"#/components/parameters/end\" },\n { $ref: \"#/components/parameters/datum\" },\n { $ref: \"#/components/parameters/units\" },\n ],\n responses: {\n \"200\": {\n description: \"Successful prediction\",\n content: {\n \"application/json\": {\n schema: {\n $ref: \"#/components/schemas/ExtremesResponse\",\n },\n },\n },\n },\n \"400\": {\n description: \"Invalid parameters\",\n content: {\n \"application/json\": {\n schema: {\n $ref: \"#/components/schemas/Error\",\n },\n },\n },\n },\n },\n },\n },\n \"/timeline\": {\n get: {\n summary: \"Get timeline prediction for a location\",\n description: \"Returns water level predictions at regular intervals for the nearest station\",\n parameters: [\n { $ref: \"#/components/parameters/latitude\" },\n { $ref: \"#/components/parameters/longitude\" },\n { $ref: \"#/components/parameters/start\" },\n { $ref: \"#/components/parameters/end\" },\n { $ref: \"#/components/parameters/datum\" },\n { $ref: \"#/components/parameters/units\" },\n ],\n responses: {\n \"200\": {\n description: \"Successful prediction\",\n content: {\n \"application/json\": {\n schema: {\n $ref: \"#/components/schemas/TimelineResponse\",\n },\n },\n },\n },\n \"400\": {\n description: \"Invalid parameters\",\n content: {\n \"application/json\": {\n schema: {\n $ref: \"#/components/schemas/Error\",\n },\n },\n },\n },\n },\n },\n },\n \"/stations\": {\n get: {\n summary: \"Find stations\",\n description: \"Find stations by ID or near a location\",\n parameters: [\n {\n name: \"id\",\n in: \"query\",\n description: \"Station ID or source ID\",\n required: false,\n schema: {\n type: \"string\",\n },\n },\n {\n name: \"latitude\",\n in: \"query\",\n description: \"Latitude for proximity search\",\n required: false,\n schema: {\n type: \"number\",\n minimum: -90,\n maximum: 90,\n },\n },\n {\n name: \"longitude\",\n in: \"query\",\n description: \"Longitude for proximity search\",\n required: false,\n schema: {\n type: \"number\",\n minimum: -180,\n maximum: 180,\n },\n },\n {\n name: \"limit\",\n in: \"query\",\n description: \"Maximum number of stations to return (for proximity search)\",\n required: false,\n schema: {\n type: \"integer\",\n minimum: 1,\n maximum: 100,\n default: 10,\n },\n },\n ],\n responses: {\n \"200\": {\n description: \"Stations found\",\n content: {\n \"application/json\": {\n schema: {\n oneOf: [\n {\n $ref: \"#/components/schemas/Station\",\n },\n {\n type: \"array\",\n items: {\n $ref: \"#/components/schemas/Station\",\n },\n },\n ],\n },\n },\n },\n },\n \"400\": {\n description: \"Invalid parameters\",\n content: {\n \"application/json\": {\n schema: {\n $ref: \"#/components/schemas/Error\",\n },\n },\n },\n },\n \"404\": {\n description: \"Station not found\",\n content: {\n \"application/json\": {\n schema: {\n $ref: \"#/components/schemas/Error\",\n },\n },\n },\n },\n },\n },\n },\n \"/stations/{id}/extremes\": {\n get: {\n summary: \"Get extremes prediction for a specific station\",\n parameters: [\n { $ref: \"#/components/parameters/stationId\" },\n { $ref: \"#/components/parameters/start\" },\n { $ref: \"#/components/parameters/end\" },\n { $ref: \"#/components/parameters/datum\" },\n { $ref: \"#/components/parameters/units\" },\n ],\n responses: {\n \"200\": {\n description: \"Successful prediction\",\n content: {\n \"application/json\": {\n schema: {\n $ref: \"#/components/schemas/ExtremesResponse\",\n },\n },\n },\n },\n \"400\": {\n description: \"Invalid parameters\",\n content: {\n \"application/json\": {\n schema: {\n $ref: \"#/components/schemas/Error\",\n },\n },\n },\n },\n \"404\": {\n description: \"Station not found\",\n content: {\n \"application/json\": {\n schema: {\n $ref: \"#/components/schemas/Error\",\n },\n },\n },\n },\n },\n },\n },\n \"/stations/{id}/timeline\": {\n get: {\n summary: \"Get timeline prediction for a specific station\",\n parameters: [\n { $ref: \"#/components/parameters/stationId\" },\n { $ref: \"#/components/parameters/start\" },\n { $ref: \"#/components/parameters/end\" },\n { $ref: \"#/components/parameters/datum\" },\n { $ref: \"#/components/parameters/units\" },\n ],\n responses: {\n \"200\": {\n description: \"Successful prediction\",\n content: {\n \"application/json\": {\n schema: {\n $ref: \"#/components/schemas/TimelineResponse\",\n },\n },\n },\n },\n \"400\": {\n description: \"Invalid parameters\",\n content: {\n \"application/json\": {\n schema: {\n $ref: \"#/components/schemas/Error\",\n },\n },\n },\n },\n \"404\": {\n description: \"Station not found\",\n content: {\n \"application/json\": {\n schema: {\n $ref: \"#/components/schemas/Error\",\n },\n },\n },\n },\n },\n },\n },\n \"/openapi.json\": {\n get: {\n summary: \"Get OpenAPI specification\",\n responses: {\n \"200\": {\n description: \"OpenAPI specification\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n },\n },\n },\n },\n },\n },\n },\n },\n components: {\n parameters: {\n latitude: {\n name: \"latitude\",\n in: \"query\",\n description: \"Latitude\",\n required: true,\n schema: {\n type: \"number\",\n minimum: -90,\n maximum: 90,\n },\n },\n longitude: {\n name: \"longitude\",\n in: \"query\",\n description: \"Longitude\",\n required: true,\n schema: {\n type: \"number\",\n minimum: -180,\n maximum: 180,\n },\n },\n start: {\n name: \"start\",\n in: \"query\",\n required: false,\n description: \"Start date/time (ISO 8601 format, defaults to now)\",\n schema: {\n type: \"string\",\n format: \"date-time\",\n },\n },\n end: {\n name: \"end\",\n in: \"query\",\n required: false,\n description: \"End date/time (ISO 8601 format, defaults to 7 days from start)\",\n schema: {\n type: \"string\",\n format: \"date-time\",\n },\n },\n datum: {\n name: \"datum\",\n in: \"query\",\n required: false,\n description: \"Vertical datum (defaults to MLLW if available)\",\n schema: {\n type: \"string\",\n enum: [\"MLLW\", \"MLW\", \"MTL\", \"MSL\", \"MHW\", \"MHHW\"],\n },\n },\n units: {\n name: \"units\",\n in: \"query\",\n required: false,\n description: \"Units for water levels (defaults to meters)\",\n schema: {\n type: \"string\",\n enum: [\"meters\", \"feet\"],\n default: \"meters\",\n },\n },\n stationId: {\n name: \"id\",\n in: \"path\",\n required: true,\n description: \"Station ID or source ID\",\n schema: {\n type: \"string\",\n },\n },\n },\n schemas: {\n Station: {\n type: \"object\",\n properties: {\n id: {\n type: \"string\",\n },\n name: {\n type: \"string\",\n },\n latitude: {\n type: \"number\",\n },\n longitude: {\n type: \"number\",\n },\n region: {\n type: \"string\",\n },\n country: {\n type: \"string\",\n },\n continent: {\n type: \"string\",\n },\n timezone: {\n type: \"string\",\n },\n type: {\n type: \"string\",\n enum: [\"reference\", \"subordinate\"],\n },\n source: {\n type: \"object\",\n additionalProperties: true,\n },\n license: {\n type: \"object\",\n additionalProperties: true,\n },\n disclaimers: {\n type: \"string\",\n },\n distance: {\n type: \"number\",\n description: \"Distance from query point in meters (only for proximity searches)\",\n },\n datums: {\n type: \"object\",\n additionalProperties: {\n type: \"number\",\n },\n },\n harmonic_constituents: {\n type: \"array\",\n items: {\n type: \"object\",\n additionalProperties: true,\n },\n },\n defaultDatum: {\n type: \"string\",\n },\n offsets: {\n type: \"object\",\n additionalProperties: true,\n },\n },\n additionalProperties: true,\n },\n Extreme: {\n type: \"object\",\n properties: {\n time: {\n type: \"string\",\n format: \"date-time\",\n },\n level: {\n type: \"number\",\n },\n high: {\n type: \"boolean\",\n },\n low: {\n type: \"boolean\",\n },\n label: {\n type: \"string\",\n },\n },\n required: [\"time\", \"level\", \"high\", \"low\", \"label\"],\n },\n ExtremesResponse: {\n type: \"object\",\n properties: {\n datum: {\n type: \"string\",\n },\n units: {\n type: \"string\",\n enum: [\"meters\", \"feet\"],\n },\n station: {\n $ref: \"#/components/schemas/Station\",\n },\n distance: {\n type: \"number\",\n },\n extremes: {\n type: \"array\",\n items: {\n $ref: \"#/components/schemas/Extreme\",\n },\n },\n },\n },\n TimelineEntry: {\n type: \"object\",\n properties: {\n time: {\n type: \"string\",\n format: \"date-time\",\n },\n level: {\n type: \"number\",\n },\n },\n required: [\"time\", \"level\"],\n },\n TimelineResponse: {\n type: \"object\",\n properties: {\n datum: {\n type: \"string\",\n },\n units: {\n type: \"string\",\n enum: [\"meters\", \"feet\"],\n },\n station: {\n $ref: \"#/components/schemas/Station\",\n },\n distance: {\n type: \"number\",\n },\n timeline: {\n type: \"array\",\n items: {\n $ref: \"#/components/schemas/TimelineEntry\",\n },\n },\n },\n },\n Error: {\n type: \"object\",\n properties: {\n message: {\n type: \"string\",\n },\n errors: {\n type: \"array\",\n items: {\n type: \"object\",\n additionalProperties: true,\n },\n },\n },\n required: [\"message\"],\n },\n },\n },\n} as const;\n","import { json, Router, Request, Response, type ErrorRequestHandler } from \"express\";\nimport { getExtremesPrediction, getTimelinePrediction, findStation, stationsNear } from \"neaps\";\nimport { middleware as openapiValidator } from \"express-openapi-validator\";\nimport openapi from \"./openapi.js\";\n\nconst router = Router();\n\nrouter.use(json());\n\nrouter.use(\n openapiValidator({\n apiSpec: openapi,\n validateRequests: {\n coerceTypes: true,\n },\n validateResponses: import.meta.env?.VITEST,\n }),\n);\n\nrouter.get(\"/openapi.json\", (req, res) => {\n res.json(openapi);\n});\n\nrouter.get(\"/extremes\", (req: Request, res: Response) => {\n res.json(\n getExtremesPrediction({\n ...positionOptions(req),\n ...predictionOptions(req),\n }),\n );\n});\n\nrouter.get(\"/timeline\", (req: Request, res: Response) => {\n try {\n res.json(\n getTimelinePrediction({\n ...positionOptions(req),\n ...predictionOptions(req),\n }),\n );\n } catch (error) {\n res.status(400).json({ message: (error as Error).message });\n }\n});\n\nrouter.get(\"/stations\", (req: Request, res: Response) => {\n if (req.query.id) {\n try {\n return res.json(findStation(req.query.id as string));\n } catch (error) {\n return res.status(404).json({ message: (error as Error).message });\n }\n }\n\n const { latitude, longitude } = positionOptions(req);\n\n if (latitude === undefined || longitude === undefined) {\n return res.status(400).json({\n message: \"Either 'id' or coordinates (latitude and longitude) required\",\n });\n }\n\n const limit = req.query.limit ? parseInt(req.query.limit as string, 10) : 10;\n\n const stations = stationsNear({ latitude, longitude }, limit);\n res.json(stations);\n});\n\nrouter.get(\"/stations/:id/extremes\", (req: Request, res: Response) => {\n let station: ReturnType<typeof findStation>;\n\n try {\n station = findStation(req.params.id);\n } catch (error) {\n return res.status(404).json({ message: (error as Error).message });\n }\n\n res.json(station.getExtremesPrediction(predictionOptions(req)));\n});\n\nrouter.get(\"/stations/:id/timeline\", (req: Request, res: Response) => {\n try {\n const station = findStation(req.params.id);\n res.json(station.getTimelinePrediction(predictionOptions(req)));\n } catch (error) {\n if ((error as Error).message.includes(\"not found\")) {\n return res.status(404).json({ message: (error as Error).message });\n }\n // Subordinate station errors and other application errors\n return res.status(400).json({ message: (error as Error).message });\n }\n});\n\nrouter.use(((err, _req, res, next) => {\n if (!err) return next();\n\n const status = err.status ?? 500;\n const message = err.message ?? \"Unknown error\";\n\n res.status(status).json({ message, errors: err.errors });\n}) satisfies ErrorRequestHandler);\n\nfunction positionOptions(req: Request) {\n return {\n latitude: req.query.latitude as unknown as number,\n longitude: req.query.longitude as unknown as number,\n };\n}\n\nfunction predictionOptions(req: Request) {\n return {\n start: req.query.start ? new Date(req.query.start as string) : new Date(),\n end: req.query.end\n ? new Date(req.query.end as string)\n : new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),\n ...(req.query.datum && { datum: req.query.datum as string }),\n ...(req.query.units && { units: req.query.units as \"meters\" | \"feet\" }),\n };\n}\n\nexport default router;\n","import express from \"express\";\nimport routes from \"./routes.js\";\nimport openapi from \"./openapi.js\";\n\nexport function createApp() {\n return express().use(\"/\", routes);\n}\n\nexport { routes, openapi };\n"],"mappings":";;;;;;;;;ACEA,sBAAe;CACb,SAAS;CACT,MAAM;EACJ,OAAO;EACEA;EACT,aAAa;EACb,SAAS,EACP,MAAM,OACP;EACF;CACD,OAAO;EACL,aAAa,EACX,KAAK;GACH,SAAS;GACT,aACE;GACF,YAAY;IACV,EAAE,MAAM,oCAAoC;IAC5C,EAAE,MAAM,qCAAqC;IAC7C,EAAE,MAAM,iCAAiC;IACzC,EAAE,MAAM,+BAA+B;IACvC,EAAE,MAAM,iCAAiC;IACzC,EAAE,MAAM,iCAAiC;IAC1C;GACD,WAAW;IACT,OAAO;KACL,aAAa;KACb,SAAS,EACP,oBAAoB,EAClB,QAAQ,EACN,MAAM,yCACP,EACF,EACF;KACF;IACD,OAAO;KACL,aAAa;KACb,SAAS,EACP,oBAAoB,EAClB,QAAQ,EACN,MAAM,8BACP,EACF,EACF;KACF;IACF;GACF,EACF;EACD,aAAa,EACX,KAAK;GACH,SAAS;GACT,aAAa;GACb,YAAY;IACV,EAAE,MAAM,oCAAoC;IAC5C,EAAE,MAAM,qCAAqC;IAC7C,EAAE,MAAM,iCAAiC;IACzC,EAAE,MAAM,+BAA+B;IACvC,EAAE,MAAM,iCAAiC;IACzC,EAAE,MAAM,iCAAiC;IAC1C;GACD,WAAW;IACT,OAAO;KACL,aAAa;KACb,SAAS,EACP,oBAAoB,EAClB,QAAQ,EACN,MAAM,yCACP,EACF,EACF;KACF;IACD,OAAO;KACL,aAAa;KACb,SAAS,EACP,oBAAoB,EAClB,QAAQ,EACN,MAAM,8BACP,EACF,EACF;KACF;IACF;GACF,EACF;EACD,aAAa,EACX,KAAK;GACH,SAAS;GACT,aAAa;GACb,YAAY;IACV;KACE,MAAM;KACN,IAAI;KACJ,aAAa;KACb,UAAU;KACV,QAAQ,EACN,MAAM,UACP;KACF;IACD;KACE,MAAM;KACN,IAAI;KACJ,aAAa;KACb,UAAU;KACV,QAAQ;MACN,MAAM;MACN,SAAS;MACT,SAAS;MACV;KACF;IACD;KACE,MAAM;KACN,IAAI;KACJ,aAAa;KACb,UAAU;KACV,QAAQ;MACN,MAAM;MACN,SAAS;MACT,SAAS;MACV;KACF;IACD;KACE,MAAM;KACN,IAAI;KACJ,aAAa;KACb,UAAU;KACV,QAAQ;MACN,MAAM;MACN,SAAS;MACT,SAAS;MACT,SAAS;MACV;KACF;IACF;GACD,WAAW;IACT,OAAO;KACL,aAAa;KACb,SAAS,EACP,oBAAoB,EAClB,QAAQ,EACN,OAAO,CACL,EACE,MAAM,gCACP,EACD;MACE,MAAM;MACN,OAAO,EACL,MAAM,gCACP;MACF,CACF,EACF,EACF,EACF;KACF;IACD,OAAO;KACL,aAAa;KACb,SAAS,EACP,oBAAoB,EAClB,QAAQ,EACN,MAAM,8BACP,EACF,EACF;KACF;IACD,OAAO;KACL,aAAa;KACb,SAAS,EACP,oBAAoB,EAClB,QAAQ,EACN,MAAM,8BACP,EACF,EACF;KACF;IACF;GACF,EACF;EACD,2BAA2B,EACzB,KAAK;GACH,SAAS;GACT,YAAY;IACV,EAAE,MAAM,qCAAqC;IAC7C,EAAE,MAAM,iCAAiC;IACzC,EAAE,MAAM,+BAA+B;IACvC,EAAE,MAAM,iCAAiC;IACzC,EAAE,MAAM,iCAAiC;IAC1C;GACD,WAAW;IACT,OAAO;KACL,aAAa;KACb,SAAS,EACP,oBAAoB,EAClB,QAAQ,EACN,MAAM,yCACP,EACF,EACF;KACF;IACD,OAAO;KACL,aAAa;KACb,SAAS,EACP,oBAAoB,EAClB,QAAQ,EACN,MAAM,8BACP,EACF,EACF;KACF;IACD,OAAO;KACL,aAAa;KACb,SAAS,EACP,oBAAoB,EAClB,QAAQ,EACN,MAAM,8BACP,EACF,EACF;KACF;IACF;GACF,EACF;EACD,2BAA2B,EACzB,KAAK;GACH,SAAS;GACT,YAAY;IACV,EAAE,MAAM,qCAAqC;IAC7C,EAAE,MAAM,iCAAiC;IACzC,EAAE,MAAM,+BAA+B;IACvC,EAAE,MAAM,iCAAiC;IACzC,EAAE,MAAM,iCAAiC;IAC1C;GACD,WAAW;IACT,OAAO;KACL,aAAa;KACb,SAAS,EACP,oBAAoB,EAClB,QAAQ,EACN,MAAM,yCACP,EACF,EACF;KACF;IACD,OAAO;KACL,aAAa;KACb,SAAS,EACP,oBAAoB,EAClB,QAAQ,EACN,MAAM,8BACP,EACF,EACF;KACF;IACD,OAAO;KACL,aAAa;KACb,SAAS,EACP,oBAAoB,EAClB,QAAQ,EACN,MAAM,8BACP,EACF,EACF;KACF;IACF;GACF,EACF;EACD,iBAAiB,EACf,KAAK;GACH,SAAS;GACT,WAAW,EACT,OAAO;IACL,aAAa;IACb,SAAS,EACP,oBAAoB,EAClB,QAAQ,EACN,MAAM,UACP,EACF,EACF;IACF,EACF;GACF,EACF;EACF;CACD,YAAY;EACV,YAAY;GACV,UAAU;IACR,MAAM;IACN,IAAI;IACJ,aAAa;IACb,UAAU;IACV,QAAQ;KACN,MAAM;KACN,SAAS;KACT,SAAS;KACV;IACF;GACD,WAAW;IACT,MAAM;IACN,IAAI;IACJ,aAAa;IACb,UAAU;IACV,QAAQ;KACN,MAAM;KACN,SAAS;KACT,SAAS;KACV;IACF;GACD,OAAO;IACL,MAAM;IACN,IAAI;IACJ,UAAU;IACV,aAAa;IACb,QAAQ;KACN,MAAM;KACN,QAAQ;KACT;IACF;GACD,KAAK;IACH,MAAM;IACN,IAAI;IACJ,UAAU;IACV,aAAa;IACb,QAAQ;KACN,MAAM;KACN,QAAQ;KACT;IACF;GACD,OAAO;IACL,MAAM;IACN,IAAI;IACJ,UAAU;IACV,aAAa;IACb,QAAQ;KACN,MAAM;KACN,MAAM;MAAC;MAAQ;MAAO;MAAO;MAAO;MAAO;MAAO;KACnD;IACF;GACD,OAAO;IACL,MAAM;IACN,IAAI;IACJ,UAAU;IACV,aAAa;IACb,QAAQ;KACN,MAAM;KACN,MAAM,CAAC,UAAU,OAAO;KACxB,SAAS;KACV;IACF;GACD,WAAW;IACT,MAAM;IACN,IAAI;IACJ,UAAU;IACV,aAAa;IACb,QAAQ,EACN,MAAM,UACP;IACF;GACF;EACD,SAAS;GACP,SAAS;IACP,MAAM;IACN,YAAY;KACV,IAAI,EACF,MAAM,UACP;KACD,MAAM,EACJ,MAAM,UACP;KACD,UAAU,EACR,MAAM,UACP;KACD,WAAW,EACT,MAAM,UACP;KACD,QAAQ,EACN,MAAM,UACP;KACD,SAAS,EACP,MAAM,UACP;KACD,WAAW,EACT,MAAM,UACP;KACD,UAAU,EACR,MAAM,UACP;KACD,MAAM;MACJ,MAAM;MACN,MAAM,CAAC,aAAa,cAAc;MACnC;KACD,QAAQ;MACN,MAAM;MACN,sBAAsB;MACvB;KACD,SAAS;MACP,MAAM;MACN,sBAAsB;MACvB;KACD,aAAa,EACX,MAAM,UACP;KACD,UAAU;MACR,MAAM;MACN,aAAa;MACd;KACD,QAAQ;MACN,MAAM;MACN,sBAAsB,EACpB,MAAM,UACP;MACF;KACD,uBAAuB;MACrB,MAAM;MACN,OAAO;OACL,MAAM;OACN,sBAAsB;OACvB;MACF;KACD,cAAc,EACZ,MAAM,UACP;KACD,SAAS;MACP,MAAM;MACN,sBAAsB;MACvB;KACF;IACD,sBAAsB;IACvB;GACD,SAAS;IACP,MAAM;IACN,YAAY;KACV,MAAM;MACJ,MAAM;MACN,QAAQ;MACT;KACD,OAAO,EACL,MAAM,UACP;KACD,MAAM,EACJ,MAAM,WACP;KACD,KAAK,EACH,MAAM,WACP;KACD,OAAO,EACL,MAAM,UACP;KACF;IACD,UAAU;KAAC;KAAQ;KAAS;KAAQ;KAAO;KAAQ;IACpD;GACD,kBAAkB;IAChB,MAAM;IACN,YAAY;KACV,OAAO,EACL,MAAM,UACP;KACD,OAAO;MACL,MAAM;MACN,MAAM,CAAC,UAAU,OAAO;MACzB;KACD,SAAS,EACP,MAAM,gCACP;KACD,UAAU,EACR,MAAM,UACP;KACD,UAAU;MACR,MAAM;MACN,OAAO,EACL,MAAM,gCACP;MACF;KACF;IACF;GACD,eAAe;IACb,MAAM;IACN,YAAY;KACV,MAAM;MACJ,MAAM;MACN,QAAQ;MACT;KACD,OAAO,EACL,MAAM,UACP;KACF;IACD,UAAU,CAAC,QAAQ,QAAQ;IAC5B;GACD,kBAAkB;IAChB,MAAM;IACN,YAAY;KACV,OAAO,EACL,MAAM,UACP;KACD,OAAO;MACL,MAAM;MACN,MAAM,CAAC,UAAU,OAAO;MACzB;KACD,SAAS,EACP,MAAM,gCACP;KACD,UAAU,EACR,MAAM,UACP;KACD,UAAU;MACR,MAAM;MACN,OAAO,EACL,MAAM,sC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"} | ||
| {"version":3,"file":"index.mjs","names":["pkg.version","openapiValidator","openapi","routes"],"sources":["../package.json","../src/openapi.ts","../src/routes.ts","../src/index.ts"],"sourcesContent":["","import pkg from \"../package.json\" with { type: \"json\" };\n\nexport default {\n openapi: \"3.0.3\",\n info: {\n title: \"Neaps Tide Prediction API\",\n version: pkg.version,\n description: \"HTTP JSON API for tide predictions using harmonic constituents\",\n license: {\n name: \"MIT\",\n },\n },\n paths: {\n \"/tides/extremes\": {\n get: {\n summary: \"Get extremes prediction for a location\",\n description:\n \"Returns high and low tide predictions for the nearest station to the given coordinates\",\n parameters: [\n { $ref: \"#/components/parameters/latitude\" },\n { $ref: \"#/components/parameters/longitude\" },\n { $ref: \"#/components/parameters/start\" },\n { $ref: \"#/components/parameters/end\" },\n { $ref: \"#/components/parameters/datum\" },\n { $ref: \"#/components/parameters/units\" },\n ],\n responses: {\n \"200\": {\n description: \"Successful prediction\",\n content: {\n \"application/json\": {\n schema: {\n $ref: \"#/components/schemas/ExtremesResponse\",\n },\n },\n },\n },\n \"400\": {\n description: \"Invalid parameters\",\n content: {\n \"application/json\": {\n schema: {\n $ref: \"#/components/schemas/Error\",\n },\n },\n },\n },\n },\n },\n },\n \"/tides/timeline\": {\n get: {\n summary: \"Get timeline prediction for a location\",\n description: \"Returns water level predictions at regular intervals for the nearest station\",\n parameters: [\n { $ref: \"#/components/parameters/latitude\" },\n { $ref: \"#/components/parameters/longitude\" },\n { $ref: \"#/components/parameters/start\" },\n { $ref: \"#/components/parameters/end\" },\n { $ref: \"#/components/parameters/datum\" },\n { $ref: \"#/components/parameters/units\" },\n ],\n responses: {\n \"200\": {\n description: \"Successful prediction\",\n content: {\n \"application/json\": {\n schema: {\n $ref: \"#/components/schemas/TimelineResponse\",\n },\n },\n },\n },\n \"400\": {\n description: \"Invalid parameters\",\n content: {\n \"application/json\": {\n schema: {\n $ref: \"#/components/schemas/Error\",\n },\n },\n },\n },\n },\n },\n },\n \"/tides/stations\": {\n get: {\n summary: \"Find stations\",\n description: \"Find stations by ID or near a location\",\n parameters: [\n {\n name: \"id\",\n in: \"query\",\n description: \"Station ID or source ID\",\n required: false,\n schema: {\n type: \"string\",\n },\n },\n {\n name: \"latitude\",\n in: \"query\",\n description: \"Latitude for proximity search\",\n required: false,\n schema: {\n type: \"number\",\n minimum: -90,\n maximum: 90,\n },\n },\n {\n name: \"longitude\",\n in: \"query\",\n description: \"Longitude for proximity search\",\n required: false,\n schema: {\n type: \"number\",\n minimum: -180,\n maximum: 180,\n },\n },\n {\n name: \"limit\",\n in: \"query\",\n description: \"Maximum number of stations to return (for proximity search)\",\n required: false,\n schema: {\n type: \"integer\",\n minimum: 1,\n maximum: 100,\n default: 10,\n },\n },\n ],\n responses: {\n \"200\": {\n description: \"Stations found\",\n content: {\n \"application/json\": {\n schema: {\n oneOf: [\n {\n $ref: \"#/components/schemas/Station\",\n },\n {\n type: \"array\",\n items: {\n $ref: \"#/components/schemas/Station\",\n },\n },\n ],\n },\n },\n },\n },\n \"400\": {\n description: \"Invalid parameters\",\n content: {\n \"application/json\": {\n schema: {\n $ref: \"#/components/schemas/Error\",\n },\n },\n },\n },\n \"404\": {\n description: \"Station not found\",\n content: {\n \"application/json\": {\n schema: {\n $ref: \"#/components/schemas/Error\",\n },\n },\n },\n },\n },\n },\n },\n \"/tides/stations/{id}/extremes\": {\n get: {\n summary: \"Get extremes prediction for a specific station\",\n parameters: [\n { $ref: \"#/components/parameters/stationId\" },\n { $ref: \"#/components/parameters/start\" },\n { $ref: \"#/components/parameters/end\" },\n { $ref: \"#/components/parameters/datum\" },\n { $ref: \"#/components/parameters/units\" },\n ],\n responses: {\n \"200\": {\n description: \"Successful prediction\",\n content: {\n \"application/json\": {\n schema: {\n $ref: \"#/components/schemas/ExtremesResponse\",\n },\n },\n },\n },\n \"400\": {\n description: \"Invalid parameters\",\n content: {\n \"application/json\": {\n schema: {\n $ref: \"#/components/schemas/Error\",\n },\n },\n },\n },\n \"404\": {\n description: \"Station not found\",\n content: {\n \"application/json\": {\n schema: {\n $ref: \"#/components/schemas/Error\",\n },\n },\n },\n },\n },\n },\n },\n \"/tides/stations/{id}/timeline\": {\n get: {\n summary: \"Get timeline prediction for a specific station\",\n parameters: [\n { $ref: \"#/components/parameters/stationId\" },\n { $ref: \"#/components/parameters/start\" },\n { $ref: \"#/components/parameters/end\" },\n { $ref: \"#/components/parameters/datum\" },\n { $ref: \"#/components/parameters/units\" },\n ],\n responses: {\n \"200\": {\n description: \"Successful prediction\",\n content: {\n \"application/json\": {\n schema: {\n $ref: \"#/components/schemas/TimelineResponse\",\n },\n },\n },\n },\n \"400\": {\n description: \"Invalid parameters\",\n content: {\n \"application/json\": {\n schema: {\n $ref: \"#/components/schemas/Error\",\n },\n },\n },\n },\n \"404\": {\n description: \"Station not found\",\n content: {\n \"application/json\": {\n schema: {\n $ref: \"#/components/schemas/Error\",\n },\n },\n },\n },\n },\n },\n },\n \"/tides/openapi.json\": {\n get: {\n summary: \"Get OpenAPI specification\",\n responses: {\n \"200\": {\n description: \"OpenAPI specification\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n },\n },\n },\n },\n },\n },\n },\n },\n components: {\n parameters: {\n latitude: {\n name: \"latitude\",\n in: \"query\",\n description: \"Latitude\",\n required: true,\n schema: {\n type: \"number\",\n minimum: -90,\n maximum: 90,\n },\n },\n longitude: {\n name: \"longitude\",\n in: \"query\",\n description: \"Longitude\",\n required: true,\n schema: {\n type: \"number\",\n minimum: -180,\n maximum: 180,\n },\n },\n start: {\n name: \"start\",\n in: \"query\",\n required: false,\n description: \"Start date/time (ISO 8601 format, defaults to now)\",\n schema: {\n type: \"string\",\n format: \"date-time\",\n },\n },\n end: {\n name: \"end\",\n in: \"query\",\n required: false,\n description: \"End date/time (ISO 8601 format, defaults to 7 days from start)\",\n schema: {\n type: \"string\",\n format: \"date-time\",\n },\n },\n datum: {\n name: \"datum\",\n in: \"query\",\n required: false,\n description: \"Vertical datum (defaults to MLLW if available)\",\n schema: {\n type: \"string\",\n enum: [\"MLLW\", \"MLW\", \"MTL\", \"MSL\", \"MHW\", \"MHHW\"],\n },\n },\n units: {\n name: \"units\",\n in: \"query\",\n required: false,\n description: \"Units for water levels (defaults to meters)\",\n schema: {\n type: \"string\",\n enum: [\"meters\", \"feet\"],\n default: \"meters\",\n },\n },\n stationId: {\n name: \"id\",\n in: \"path\",\n required: true,\n description: \"Station ID or source ID\",\n schema: {\n type: \"string\",\n },\n },\n },\n schemas: {\n Station: {\n type: \"object\",\n properties: {\n id: {\n type: \"string\",\n },\n name: {\n type: \"string\",\n },\n latitude: {\n type: \"number\",\n },\n longitude: {\n type: \"number\",\n },\n region: {\n type: \"string\",\n },\n country: {\n type: \"string\",\n },\n continent: {\n type: \"string\",\n },\n timezone: {\n type: \"string\",\n },\n type: {\n type: \"string\",\n enum: [\"reference\", \"subordinate\"],\n },\n source: {\n type: \"object\",\n additionalProperties: true,\n },\n license: {\n type: \"object\",\n additionalProperties: true,\n },\n disclaimers: {\n type: \"string\",\n },\n distance: {\n type: \"number\",\n description: \"Distance from query point in meters (only for proximity searches)\",\n },\n datums: {\n type: \"object\",\n additionalProperties: {\n type: \"number\",\n },\n },\n harmonic_constituents: {\n type: \"array\",\n items: {\n type: \"object\",\n additionalProperties: true,\n },\n },\n defaultDatum: {\n type: \"string\",\n },\n offsets: {\n type: \"object\",\n additionalProperties: true,\n },\n },\n additionalProperties: true,\n },\n Extreme: {\n type: \"object\",\n properties: {\n time: {\n type: \"string\",\n format: \"date-time\",\n },\n level: {\n type: \"number\",\n },\n high: {\n type: \"boolean\",\n },\n low: {\n type: \"boolean\",\n },\n label: {\n type: \"string\",\n },\n },\n required: [\"time\", \"level\", \"high\", \"low\", \"label\"],\n },\n ExtremesResponse: {\n type: \"object\",\n properties: {\n datum: {\n type: \"string\",\n },\n units: {\n type: \"string\",\n enum: [\"meters\", \"feet\"],\n },\n station: {\n $ref: \"#/components/schemas/Station\",\n },\n distance: {\n type: \"number\",\n },\n extremes: {\n type: \"array\",\n items: {\n $ref: \"#/components/schemas/Extreme\",\n },\n },\n },\n },\n TimelineEntry: {\n type: \"object\",\n properties: {\n time: {\n type: \"string\",\n format: \"date-time\",\n },\n level: {\n type: \"number\",\n },\n },\n required: [\"time\", \"level\"],\n },\n TimelineResponse: {\n type: \"object\",\n properties: {\n datum: {\n type: \"string\",\n },\n units: {\n type: \"string\",\n enum: [\"meters\", \"feet\"],\n },\n station: {\n $ref: \"#/components/schemas/Station\",\n },\n distance: {\n type: \"number\",\n },\n timeline: {\n type: \"array\",\n items: {\n $ref: \"#/components/schemas/TimelineEntry\",\n },\n },\n },\n },\n Error: {\n type: \"object\",\n properties: {\n message: {\n type: \"string\",\n },\n errors: {\n type: \"array\",\n items: {\n type: \"object\",\n additionalProperties: true,\n },\n },\n },\n required: [\"message\"],\n },\n },\n },\n} as const;\n","import { json, Router, Request, Response, type ErrorRequestHandler } from \"express\";\nimport { getExtremesPrediction, getTimelinePrediction, findStation, stationsNear } from \"neaps\";\nimport { middleware as openapiValidator } from \"express-openapi-validator\";\nimport openapi from \"./openapi.js\";\n\nconst router = Router();\n\nrouter.use(json());\n\nrouter.use(\n openapiValidator({\n apiSpec: openapi,\n validateRequests: {\n coerceTypes: true,\n },\n validateResponses: import.meta.env?.VITEST,\n }),\n);\n\nrouter.get(\"/tides/openapi.json\", (req, res) => {\n res.json(openapi);\n});\n\nrouter.get(\"/tides/extremes\", (req: Request, res: Response) => {\n res.json(\n getExtremesPrediction({\n ...positionOptions(req),\n ...predictionOptions(req),\n }),\n );\n});\n\nrouter.get(\"/tides/timeline\", (req: Request, res: Response) => {\n try {\n res.json(\n getTimelinePrediction({\n ...positionOptions(req),\n ...predictionOptions(req),\n }),\n );\n } catch (error) {\n res.status(400).json({ message: (error as Error).message });\n }\n});\n\nrouter.get(\"/tides/stations\", (req: Request, res: Response) => {\n if (req.query.id) {\n try {\n return res.json(findStation(req.query.id as string));\n } catch (error) {\n return res.status(404).json({ message: (error as Error).message });\n }\n }\n\n const { latitude, longitude } = positionOptions(req);\n\n if (latitude === undefined || longitude === undefined) {\n return res.status(400).json({\n message: \"Either 'id' or coordinates (latitude and longitude) required\",\n });\n }\n\n const limit = req.query.limit ? parseInt(req.query.limit as string, 10) : 10;\n\n const stations = stationsNear({ latitude, longitude }, limit);\n res.json(stations);\n});\n\nrouter.get(\"/tides/stations/:id/extremes\", (req: Request, res: Response) => {\n let station: ReturnType<typeof findStation>;\n\n try {\n station = findStation(req.params.id);\n } catch (error) {\n return res.status(404).json({ message: (error as Error).message });\n }\n\n res.json(station.getExtremesPrediction(predictionOptions(req)));\n});\n\nrouter.get(\"/tides/stations/:id/timeline\", (req: Request, res: Response) => {\n try {\n const station = findStation(req.params.id);\n res.json(station.getTimelinePrediction(predictionOptions(req)));\n } catch (error) {\n if ((error as Error).message.includes(\"not found\")) {\n return res.status(404).json({ message: (error as Error).message });\n }\n // Subordinate station errors and other application errors\n return res.status(400).json({ message: (error as Error).message });\n }\n});\n\nrouter.use(((err, _req, res, next) => {\n if (!err) return next();\n\n const status = err.status ?? 500;\n const message = err.message ?? \"Unknown error\";\n\n res.status(status).json({ message, errors: err.errors });\n}) satisfies ErrorRequestHandler);\n\nfunction positionOptions(req: Request) {\n return {\n latitude: req.query.latitude as unknown as number,\n longitude: req.query.longitude as unknown as number,\n };\n}\n\nfunction predictionOptions(req: Request) {\n return {\n start: req.query.start ? new Date(req.query.start as string) : new Date(),\n end: req.query.end\n ? new Date(req.query.end as string)\n : new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),\n ...(req.query.datum && { datum: req.query.datum as string }),\n ...(req.query.units && { units: req.query.units as \"meters\" | \"feet\" }),\n };\n}\n\nexport default router;\n","import express from \"express\";\nimport routes from \"./routes.js\";\nimport openapi from \"./openapi.js\";\n\nexport function createApp() {\n return express().use(\"/\", routes);\n}\n\nexport { routes, openapi };\n"],"mappings":";;;;;;;;;ACEA,sBAAe;CACb,SAAS;CACT,MAAM;EACJ,OAAO;EACEA;EACT,aAAa;EACb,SAAS,EACP,MAAM,OACP;EACF;CACD,OAAO;EACL,mBAAmB,EACjB,KAAK;GACH,SAAS;GACT,aACE;GACF,YAAY;IACV,EAAE,MAAM,oCAAoC;IAC5C,EAAE,MAAM,qCAAqC;IAC7C,EAAE,MAAM,iCAAiC;IACzC,EAAE,MAAM,+BAA+B;IACvC,EAAE,MAAM,iCAAiC;IACzC,EAAE,MAAM,iCAAiC;IAC1C;GACD,WAAW;IACT,OAAO;KACL,aAAa;KACb,SAAS,EACP,oBAAoB,EAClB,QAAQ,EACN,MAAM,yCACP,EACF,EACF;KACF;IACD,OAAO;KACL,aAAa;KACb,SAAS,EACP,oBAAoB,EAClB,QAAQ,EACN,MAAM,8BACP,EACF,EACF;KACF;IACF;GACF,EACF;EACD,mBAAmB,EACjB,KAAK;GACH,SAAS;GACT,aAAa;GACb,YAAY;IACV,EAAE,MAAM,oCAAoC;IAC5C,EAAE,MAAM,qCAAqC;IAC7C,EAAE,MAAM,iCAAiC;IACzC,EAAE,MAAM,+BAA+B;IACvC,EAAE,MAAM,iCAAiC;IACzC,EAAE,MAAM,iCAAiC;IAC1C;GACD,WAAW;IACT,OAAO;KACL,aAAa;KACb,SAAS,EACP,oBAAoB,EAClB,QAAQ,EACN,MAAM,yCACP,EACF,EACF;KACF;IACD,OAAO;KACL,aAAa;KACb,SAAS,EACP,oBAAoB,EAClB,QAAQ,EACN,MAAM,8BACP,EACF,EACF;KACF;IACF;GACF,EACF;EACD,mBAAmB,EACjB,KAAK;GACH,SAAS;GACT,aAAa;GACb,YAAY;IACV;KACE,MAAM;KACN,IAAI;KACJ,aAAa;KACb,UAAU;KACV,QAAQ,EACN,MAAM,UACP;KACF;IACD;KACE,MAAM;KACN,IAAI;KACJ,aAAa;KACb,UAAU;KACV,QAAQ;MACN,MAAM;MACN,SAAS;MACT,SAAS;MACV;KACF;IACD;KACE,MAAM;KACN,IAAI;KACJ,aAAa;KACb,UAAU;KACV,QAAQ;MACN,MAAM;MACN,SAAS;MACT,SAAS;MACV;KACF;IACD;KACE,MAAM;KACN,IAAI;KACJ,aAAa;KACb,UAAU;KACV,QAAQ;MACN,MAAM;MACN,SAAS;MACT,SAAS;MACT,SAAS;MACV;KACF;IACF;GACD,WAAW;IACT,OAAO;KACL,aAAa;KACb,SAAS,EACP,oBAAoB,EAClB,QAAQ,EACN,OAAO,CACL,EACE,MAAM,gCACP,EACD;MACE,MAAM;MACN,OAAO,EACL,MAAM,gCACP;MACF,CACF,EACF,EACF,EACF;KACF;IACD,OAAO;KACL,aAAa;KACb,SAAS,EACP,oBAAoB,EAClB,QAAQ,EACN,MAAM,8BACP,EACF,EACF;KACF;IACD,OAAO;KACL,aAAa;KACb,SAAS,EACP,oBAAoB,EAClB,QAAQ,EACN,MAAM,8BACP,EACF,EACF;KACF;IACF;GACF,EACF;EACD,iCAAiC,EAC/B,KAAK;GACH,SAAS;GACT,YAAY;IACV,EAAE,MAAM,qCAAqC;IAC7C,EAAE,MAAM,iCAAiC;IACzC,EAAE,MAAM,+BAA+B;IACvC,EAAE,MAAM,iCAAiC;IACzC,EAAE,MAAM,iCAAiC;IAC1C;GACD,WAAW;IACT,OAAO;KACL,aAAa;KACb,SAAS,EACP,oBAAoB,EAClB,QAAQ,EACN,MAAM,yCACP,EACF,EACF;KACF;IACD,OAAO;KACL,aAAa;KACb,SAAS,EACP,oBAAoB,EAClB,QAAQ,EACN,MAAM,8BACP,EACF,EACF;KACF;IACD,OAAO;KACL,aAAa;KACb,SAAS,EACP,oBAAoB,EAClB,QAAQ,EACN,MAAM,8BACP,EACF,EACF;KACF;IACF;GACF,EACF;EACD,iCAAiC,EAC/B,KAAK;GACH,SAAS;GACT,YAAY;IACV,EAAE,MAAM,qCAAqC;IAC7C,EAAE,MAAM,iCAAiC;IACzC,EAAE,MAAM,+BAA+B;IACvC,EAAE,MAAM,iCAAiC;IACzC,EAAE,MAAM,iCAAiC;IAC1C;GACD,WAAW;IACT,OAAO;KACL,aAAa;KACb,SAAS,EACP,oBAAoB,EAClB,QAAQ,EACN,MAAM,yCACP,EACF,EACF;KACF;IACD,OAAO;KACL,aAAa;KACb,SAAS,EACP,oBAAoB,EAClB,QAAQ,EACN,MAAM,8BACP,EACF,EACF;KACF;IACD,OAAO;KACL,aAAa;KACb,SAAS,EACP,oBAAoB,EAClB,QAAQ,EACN,MAAM,8BACP,EACF,EACF;KACF;IACF;GACF,EACF;EACD,uBAAuB,EACrB,KAAK;GACH,SAAS;GACT,WAAW,EACT,OAAO;IACL,aAAa;IACb,SAAS,EACP,oBAAoB,EAClB,QAAQ,EACN,MAAM,UACP,EACF,EACF;IACF,EACF;GACF,EACF;EACF;CACD,YAAY;EACV,YAAY;GACV,UAAU;IACR,MAAM;IACN,IAAI;IACJ,aAAa;IACb,UAAU;IACV,QAAQ;KACN,MAAM;KACN,SAAS;KACT,SAAS;KACV;IACF;GACD,WAAW;IACT,MAAM;IACN,IAAI;IACJ,aAAa;IACb,UAAU;IACV,QAAQ;KACN,MAAM;KACN,SAAS;KACT,SAAS;KACV;IACF;GACD,OAAO;IACL,MAAM;IACN,IAAI;IACJ,UAAU;IACV,aAAa;IACb,QAAQ;KACN,MAAM;KACN,QAAQ;KACT;IACF;GACD,KAAK;IACH,MAAM;IACN,IAAI;IACJ,UAAU;IACV,aAAa;IACb,QAAQ;KACN,MAAM;KACN,QAAQ;KACT;IACF;GACD,OAAO;IACL,MAAM;IACN,IAAI;IACJ,UAAU;IACV,aAAa;IACb,QAAQ;KACN,MAAM;KACN,MAAM;MAAC;MAAQ;MAAO;MAAO;MAAO;MAAO;MAAO;KACnD;IACF;GACD,OAAO;IACL,MAAM;IACN,IAAI;IACJ,UAAU;IACV,aAAa;IACb,QAAQ;KACN,MAAM;KACN,MAAM,CAAC,UAAU,OAAO;KACxB,SAAS;KACV;IACF;GACD,WAAW;IACT,MAAM;IACN,IAAI;IACJ,UAAU;IACV,aAAa;IACb,QAAQ,EACN,MAAM,UACP;IACF;GACF;EACD,SAAS;GACP,SAAS;IACP,MAAM;IACN,YAAY;KACV,IAAI,EACF,MAAM,UACP;KACD,MAAM,EACJ,MAAM,UACP;KACD,UAAU,EACR,MAAM,UACP;KACD,WAAW,EACT,MAAM,UACP;KACD,QAAQ,EACN,MAAM,UACP;KACD,SAAS,EACP,MAAM,UACP;KACD,WAAW,EACT,MAAM,UACP;KACD,UAAU,EACR,MAAM,UACP;KACD,MAAM;MACJ,MAAM;MACN,MAAM,CAAC,aAAa,cAAc;MACnC;KACD,QAAQ;MACN,MAAM;MACN,sBAAsB;MACvB;KACD,SAAS;MACP,MAAM;MACN,sBAAsB;MACvB;KACD,aAAa,EACX,MAAM,UACP;KACD,UAAU;MACR,MAAM;MACN,aAAa;MACd;KACD,QAAQ;MACN,MAAM;MACN,sBAAsB,EACpB,MAAM,UACP;MACF;KACD,uBAAuB;MACrB,MAAM;MACN,OAAO;OACL,MAAM;OACN,sBAAsB;OACvB;MACF;KACD,cAAc,EACZ,MAAM,UACP;KACD,SAAS;MACP,MAAM;MACN,sBAAsB;MACvB;KACF;IACD,sBAAsB;IACvB;GACD,SAAS;IACP,MAAM;IACN,YAAY;KACV,MAAM;MACJ,MAAM;MACN,QAAQ;MACT;KACD,OAAO,EACL,MAAM,UACP;KACD,MAAM,EACJ,MAAM,WACP;KACD,KAAK,EACH,MAAM,WACP;KACD,OAAO,EACL,MAAM,UACP;KACF;IACD,UAAU;KAAC;KAAQ;KAAS;KAAQ;KAAO;KAAQ;IACpD;GACD,kBAAkB;IAChB,MAAM;IACN,YAAY;KACV,OAAO,EACL,MAAM,UACP;KACD,OAAO;MACL,MAAM;MACN,MAAM,CAAC,UAAU,OAAO;MACzB;KACD,SAAS,EACP,MAAM,gCACP;KACD,UAAU,EACR,MAAM,UACP;KACD,UAAU;MACR,MAAM;MACN,OAAO,EACL,MAAM,gCACP;MACF;KACF;IACF;GACD,eAAe;IACb,MAAM;IACN,YAAY;KACV,MAAM;MACJ,MAAM;MACN,QAAQ;MACT;KACD,OAAO,EACL,MAAM,UACP;KACF;IACD,UAAU,CAAC,QAAQ,QAAQ;IAC5B;GACD,kBAAkB;IAChB,MAAM;IACN,YAAY;KACV,OAAO,EACL,MAAM,UACP;KACD,OAAO;MACL,MAAM;MACN,MAAM,CAAC,UAAU,OAAO;MACzB;KACD,SAAS,EACP,MAAM,gCACP;KACD,UAAU,EACR,MAAM,UACP;KACD,UAAU;MACR,MAAM;MACN,OAAO,EA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"} |
+1
-1
| { | ||
| "name": "@neaps/api", | ||
| "version": "0.1.0", | ||
| "version": "0.2.0", | ||
| "description": "HTTP JSON API for tide predictions", | ||
@@ -5,0 +5,0 @@ "repository": { |
+17
-17
| # @neaps/api | ||
| HTTP JSON API for tide predictions using NOAA harmonic constituents. | ||
| HTTP JSON API for tide predictions using [neaps](https://github.com/neaps/neaps). | ||
@@ -8,3 +8,3 @@ ## Installation | ||
| ```bash | ||
| npm install @neaps/api express | ||
| npm install @neaps/api | ||
| ``` | ||
@@ -44,3 +44,3 @@ | ||
| ### GET /extremes | ||
| ### GET /tides/extremes | ||
@@ -51,4 +51,4 @@ Get high and low tide predictions for the nearest station to given coordinates. | ||
| - `lat` or `latitude` (required): Latitude (-90 to 90) | ||
| - `lon`, `lng`, or `longitude` (required): Longitude (-180 to 180) | ||
| - `latitude` (required): Latitude (-90 to 90) | ||
| - `longitude` (required): Longitude (-180 to 180) | ||
| - `start` (required): Start date/time in ISO 8601 format | ||
@@ -62,6 +62,6 @@ - `end` (required): End date/time in ISO 8601 format | ||
| ```bash | ||
| curl "http://localhost:3000/extremes?lat=26.772&lon=-80.05&start=2025-12-17T00:00:00Z&end=2025-12-18T00:00:00Z&datum=MLLW&units=feet" | ||
| curl "http://localhost:3000/tides/extremes?latitude=26.772&longitude=-80.05&start=2025-12-17T00:00:00Z&end=2025-12-18T00:00:00Z&datum=MLLW&units=feet" | ||
| ``` | ||
| ### GET /timeline | ||
| ### GET /tides/timeline | ||
@@ -75,6 +75,6 @@ Get water level predictions at regular intervals for the nearest station. | ||
| ```bash | ||
| curl "http://localhost:3000/timeline?lat=26.772&lon=-80.05&start=2025-12-17T00:00:00Z&end=2025-12-18T00:00:00Z" | ||
| curl "http://localhost:3000/tides/timeline?latitude=26.772&longitude=-80.05&start=2025-12-17T00:00:00Z&end=2025-12-18T00:00:00Z" | ||
| ``` | ||
| ### GET /stations | ||
| ### GET /tides/stations | ||
@@ -86,4 +86,4 @@ Find stations by ID or near a location. | ||
| - `id` (optional): Station ID or source ID | ||
| - `lat` or `latitude` (optional): Latitude for proximity search | ||
| - `lon`, `lng`, or `longitude` (optional): Longitude for proximity search | ||
| - `latitude` (optional): Latitude for proximity search | ||
| - `longitude` (optional): Longitude for proximity search | ||
| - `limit` (optional): Maximum number of stations to return (1-100, defaults to 10) | ||
@@ -95,9 +95,9 @@ | ||
| # Find a specific station | ||
| curl "http://localhost:3000/stations?id=noaa/8722588" | ||
| curl "http://localhost:3000/tides/stations?id=noaa/8722588" | ||
| # Find stations near coordinates | ||
| curl "http://localhost:3000/stations?lat=26.772&lon=-80.05&limit=5" | ||
| curl "http://localhost:3000/tides/stations?latitude=26.772&longitude=-80.05&limit=5" | ||
| ``` | ||
| ### GET /stations/:id/extremes | ||
| ### GET /tides/stations/:id/extremes | ||
@@ -120,6 +120,6 @@ Get extremes prediction for a specific station. | ||
| ```bash | ||
| curl "http://localhost:3000/stations/noaa%2F8722588/extremes?start=2025-12-17T00:00:00Z&end=2025-12-18T00:00:00Z" | ||
| curl "http://localhost:3000/tides/stations/noaa%2F8722588/extremes?start=2025-12-17T00:00:00Z&end=2025-12-18T00:00:00Z" | ||
| ``` | ||
| ### GET /stations/:id/timeline | ||
| ### GET /tides/stations/:id/timeline | ||
@@ -132,3 +132,3 @@ Get timeline prediction for a specific station. | ||
| ### GET /openapi.json | ||
| ### GET /tides/openapi.json | ||
@@ -135,0 +135,0 @@ Get the OpenAPI 3.0 specification for this API. |
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
98345
0.25%