@neaps/api 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +146 -0
- package/bin/server.js +10 -0
- package/dist/index.d.mts +1560 -0
- package/dist/index.mjs +486 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +39 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
import express, { Router, json } from "express";
|
|
2
|
+
import { findStation, getExtremesPrediction, getTimelinePrediction, stationsNear } from "neaps";
|
|
3
|
+
import { middleware } from "express-openapi-validator";
|
|
4
|
+
|
|
5
|
+
//#region package.json
|
|
6
|
+
var version = "0.1.0";
|
|
7
|
+
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/openapi.ts
|
|
10
|
+
var openapi_default = {
|
|
11
|
+
openapi: "3.0.3",
|
|
12
|
+
info: {
|
|
13
|
+
title: "Neaps Tide Prediction API",
|
|
14
|
+
version,
|
|
15
|
+
description: "HTTP JSON API for tide predictions using harmonic constituents",
|
|
16
|
+
license: { name: "MIT" }
|
|
17
|
+
},
|
|
18
|
+
paths: {
|
|
19
|
+
"/extremes": { get: {
|
|
20
|
+
summary: "Get extremes prediction for a location",
|
|
21
|
+
description: "Returns high and low tide predictions for the nearest station to the given coordinates",
|
|
22
|
+
parameters: [
|
|
23
|
+
{ $ref: "#/components/parameters/latitude" },
|
|
24
|
+
{ $ref: "#/components/parameters/longitude" },
|
|
25
|
+
{ $ref: "#/components/parameters/start" },
|
|
26
|
+
{ $ref: "#/components/parameters/end" },
|
|
27
|
+
{ $ref: "#/components/parameters/datum" },
|
|
28
|
+
{ $ref: "#/components/parameters/units" }
|
|
29
|
+
],
|
|
30
|
+
responses: {
|
|
31
|
+
"200": {
|
|
32
|
+
description: "Successful prediction",
|
|
33
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/ExtremesResponse" } } }
|
|
34
|
+
},
|
|
35
|
+
"400": {
|
|
36
|
+
description: "Invalid parameters",
|
|
37
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
} },
|
|
41
|
+
"/timeline": { get: {
|
|
42
|
+
summary: "Get timeline prediction for a location",
|
|
43
|
+
description: "Returns water level predictions at regular intervals for the nearest station",
|
|
44
|
+
parameters: [
|
|
45
|
+
{ $ref: "#/components/parameters/latitude" },
|
|
46
|
+
{ $ref: "#/components/parameters/longitude" },
|
|
47
|
+
{ $ref: "#/components/parameters/start" },
|
|
48
|
+
{ $ref: "#/components/parameters/end" },
|
|
49
|
+
{ $ref: "#/components/parameters/datum" },
|
|
50
|
+
{ $ref: "#/components/parameters/units" }
|
|
51
|
+
],
|
|
52
|
+
responses: {
|
|
53
|
+
"200": {
|
|
54
|
+
description: "Successful prediction",
|
|
55
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/TimelineResponse" } } }
|
|
56
|
+
},
|
|
57
|
+
"400": {
|
|
58
|
+
description: "Invalid parameters",
|
|
59
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } }
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
} },
|
|
63
|
+
"/stations": { get: {
|
|
64
|
+
summary: "Find stations",
|
|
65
|
+
description: "Find stations by ID or near a location",
|
|
66
|
+
parameters: [
|
|
67
|
+
{
|
|
68
|
+
name: "id",
|
|
69
|
+
in: "query",
|
|
70
|
+
description: "Station ID or source ID",
|
|
71
|
+
required: false,
|
|
72
|
+
schema: { type: "string" }
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: "latitude",
|
|
76
|
+
in: "query",
|
|
77
|
+
description: "Latitude for proximity search",
|
|
78
|
+
required: false,
|
|
79
|
+
schema: {
|
|
80
|
+
type: "number",
|
|
81
|
+
minimum: -90,
|
|
82
|
+
maximum: 90
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: "longitude",
|
|
87
|
+
in: "query",
|
|
88
|
+
description: "Longitude for proximity search",
|
|
89
|
+
required: false,
|
|
90
|
+
schema: {
|
|
91
|
+
type: "number",
|
|
92
|
+
minimum: -180,
|
|
93
|
+
maximum: 180
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: "limit",
|
|
98
|
+
in: "query",
|
|
99
|
+
description: "Maximum number of stations to return (for proximity search)",
|
|
100
|
+
required: false,
|
|
101
|
+
schema: {
|
|
102
|
+
type: "integer",
|
|
103
|
+
minimum: 1,
|
|
104
|
+
maximum: 100,
|
|
105
|
+
default: 10
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
],
|
|
109
|
+
responses: {
|
|
110
|
+
"200": {
|
|
111
|
+
description: "Stations found",
|
|
112
|
+
content: { "application/json": { schema: { oneOf: [{ $ref: "#/components/schemas/Station" }, {
|
|
113
|
+
type: "array",
|
|
114
|
+
items: { $ref: "#/components/schemas/Station" }
|
|
115
|
+
}] } } }
|
|
116
|
+
},
|
|
117
|
+
"400": {
|
|
118
|
+
description: "Invalid parameters",
|
|
119
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } }
|
|
120
|
+
},
|
|
121
|
+
"404": {
|
|
122
|
+
description: "Station not found",
|
|
123
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } }
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
} },
|
|
127
|
+
"/stations/{id}/extremes": { get: {
|
|
128
|
+
summary: "Get extremes prediction for a specific station",
|
|
129
|
+
parameters: [
|
|
130
|
+
{ $ref: "#/components/parameters/stationId" },
|
|
131
|
+
{ $ref: "#/components/parameters/start" },
|
|
132
|
+
{ $ref: "#/components/parameters/end" },
|
|
133
|
+
{ $ref: "#/components/parameters/datum" },
|
|
134
|
+
{ $ref: "#/components/parameters/units" }
|
|
135
|
+
],
|
|
136
|
+
responses: {
|
|
137
|
+
"200": {
|
|
138
|
+
description: "Successful prediction",
|
|
139
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/ExtremesResponse" } } }
|
|
140
|
+
},
|
|
141
|
+
"400": {
|
|
142
|
+
description: "Invalid parameters",
|
|
143
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } }
|
|
144
|
+
},
|
|
145
|
+
"404": {
|
|
146
|
+
description: "Station not found",
|
|
147
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } }
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
} },
|
|
151
|
+
"/stations/{id}/timeline": { get: {
|
|
152
|
+
summary: "Get timeline prediction for a specific station",
|
|
153
|
+
parameters: [
|
|
154
|
+
{ $ref: "#/components/parameters/stationId" },
|
|
155
|
+
{ $ref: "#/components/parameters/start" },
|
|
156
|
+
{ $ref: "#/components/parameters/end" },
|
|
157
|
+
{ $ref: "#/components/parameters/datum" },
|
|
158
|
+
{ $ref: "#/components/parameters/units" }
|
|
159
|
+
],
|
|
160
|
+
responses: {
|
|
161
|
+
"200": {
|
|
162
|
+
description: "Successful prediction",
|
|
163
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/TimelineResponse" } } }
|
|
164
|
+
},
|
|
165
|
+
"400": {
|
|
166
|
+
description: "Invalid parameters",
|
|
167
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } }
|
|
168
|
+
},
|
|
169
|
+
"404": {
|
|
170
|
+
description: "Station not found",
|
|
171
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } }
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
} },
|
|
175
|
+
"/openapi.json": { get: {
|
|
176
|
+
summary: "Get OpenAPI specification",
|
|
177
|
+
responses: { "200": {
|
|
178
|
+
description: "OpenAPI specification",
|
|
179
|
+
content: { "application/json": { schema: { type: "object" } } }
|
|
180
|
+
} }
|
|
181
|
+
} }
|
|
182
|
+
},
|
|
183
|
+
components: {
|
|
184
|
+
parameters: {
|
|
185
|
+
latitude: {
|
|
186
|
+
name: "latitude",
|
|
187
|
+
in: "query",
|
|
188
|
+
description: "Latitude",
|
|
189
|
+
required: true,
|
|
190
|
+
schema: {
|
|
191
|
+
type: "number",
|
|
192
|
+
minimum: -90,
|
|
193
|
+
maximum: 90
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
longitude: {
|
|
197
|
+
name: "longitude",
|
|
198
|
+
in: "query",
|
|
199
|
+
description: "Longitude",
|
|
200
|
+
required: true,
|
|
201
|
+
schema: {
|
|
202
|
+
type: "number",
|
|
203
|
+
minimum: -180,
|
|
204
|
+
maximum: 180
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
start: {
|
|
208
|
+
name: "start",
|
|
209
|
+
in: "query",
|
|
210
|
+
required: false,
|
|
211
|
+
description: "Start date/time (ISO 8601 format, defaults to now)",
|
|
212
|
+
schema: {
|
|
213
|
+
type: "string",
|
|
214
|
+
format: "date-time"
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
end: {
|
|
218
|
+
name: "end",
|
|
219
|
+
in: "query",
|
|
220
|
+
required: false,
|
|
221
|
+
description: "End date/time (ISO 8601 format, defaults to 7 days from start)",
|
|
222
|
+
schema: {
|
|
223
|
+
type: "string",
|
|
224
|
+
format: "date-time"
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
datum: {
|
|
228
|
+
name: "datum",
|
|
229
|
+
in: "query",
|
|
230
|
+
required: false,
|
|
231
|
+
description: "Vertical datum (defaults to MLLW if available)",
|
|
232
|
+
schema: {
|
|
233
|
+
type: "string",
|
|
234
|
+
enum: [
|
|
235
|
+
"MLLW",
|
|
236
|
+
"MLW",
|
|
237
|
+
"MTL",
|
|
238
|
+
"MSL",
|
|
239
|
+
"MHW",
|
|
240
|
+
"MHHW"
|
|
241
|
+
]
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
units: {
|
|
245
|
+
name: "units",
|
|
246
|
+
in: "query",
|
|
247
|
+
required: false,
|
|
248
|
+
description: "Units for water levels (defaults to meters)",
|
|
249
|
+
schema: {
|
|
250
|
+
type: "string",
|
|
251
|
+
enum: ["meters", "feet"],
|
|
252
|
+
default: "meters"
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
stationId: {
|
|
256
|
+
name: "id",
|
|
257
|
+
in: "path",
|
|
258
|
+
required: true,
|
|
259
|
+
description: "Station ID or source ID",
|
|
260
|
+
schema: { type: "string" }
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
schemas: {
|
|
264
|
+
Station: {
|
|
265
|
+
type: "object",
|
|
266
|
+
properties: {
|
|
267
|
+
id: { type: "string" },
|
|
268
|
+
name: { type: "string" },
|
|
269
|
+
latitude: { type: "number" },
|
|
270
|
+
longitude: { type: "number" },
|
|
271
|
+
region: { type: "string" },
|
|
272
|
+
country: { type: "string" },
|
|
273
|
+
continent: { type: "string" },
|
|
274
|
+
timezone: { type: "string" },
|
|
275
|
+
type: {
|
|
276
|
+
type: "string",
|
|
277
|
+
enum: ["reference", "subordinate"]
|
|
278
|
+
},
|
|
279
|
+
source: {
|
|
280
|
+
type: "object",
|
|
281
|
+
additionalProperties: true
|
|
282
|
+
},
|
|
283
|
+
license: {
|
|
284
|
+
type: "object",
|
|
285
|
+
additionalProperties: true
|
|
286
|
+
},
|
|
287
|
+
disclaimers: { type: "string" },
|
|
288
|
+
distance: {
|
|
289
|
+
type: "number",
|
|
290
|
+
description: "Distance from query point in meters (only for proximity searches)"
|
|
291
|
+
},
|
|
292
|
+
datums: {
|
|
293
|
+
type: "object",
|
|
294
|
+
additionalProperties: { type: "number" }
|
|
295
|
+
},
|
|
296
|
+
harmonic_constituents: {
|
|
297
|
+
type: "array",
|
|
298
|
+
items: {
|
|
299
|
+
type: "object",
|
|
300
|
+
additionalProperties: true
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
defaultDatum: { type: "string" },
|
|
304
|
+
offsets: {
|
|
305
|
+
type: "object",
|
|
306
|
+
additionalProperties: true
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
additionalProperties: true
|
|
310
|
+
},
|
|
311
|
+
Extreme: {
|
|
312
|
+
type: "object",
|
|
313
|
+
properties: {
|
|
314
|
+
time: {
|
|
315
|
+
type: "string",
|
|
316
|
+
format: "date-time"
|
|
317
|
+
},
|
|
318
|
+
level: { type: "number" },
|
|
319
|
+
high: { type: "boolean" },
|
|
320
|
+
low: { type: "boolean" },
|
|
321
|
+
label: { type: "string" }
|
|
322
|
+
},
|
|
323
|
+
required: [
|
|
324
|
+
"time",
|
|
325
|
+
"level",
|
|
326
|
+
"high",
|
|
327
|
+
"low",
|
|
328
|
+
"label"
|
|
329
|
+
]
|
|
330
|
+
},
|
|
331
|
+
ExtremesResponse: {
|
|
332
|
+
type: "object",
|
|
333
|
+
properties: {
|
|
334
|
+
datum: { type: "string" },
|
|
335
|
+
units: {
|
|
336
|
+
type: "string",
|
|
337
|
+
enum: ["meters", "feet"]
|
|
338
|
+
},
|
|
339
|
+
station: { $ref: "#/components/schemas/Station" },
|
|
340
|
+
distance: { type: "number" },
|
|
341
|
+
extremes: {
|
|
342
|
+
type: "array",
|
|
343
|
+
items: { $ref: "#/components/schemas/Extreme" }
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
TimelineEntry: {
|
|
348
|
+
type: "object",
|
|
349
|
+
properties: {
|
|
350
|
+
time: {
|
|
351
|
+
type: "string",
|
|
352
|
+
format: "date-time"
|
|
353
|
+
},
|
|
354
|
+
level: { type: "number" }
|
|
355
|
+
},
|
|
356
|
+
required: ["time", "level"]
|
|
357
|
+
},
|
|
358
|
+
TimelineResponse: {
|
|
359
|
+
type: "object",
|
|
360
|
+
properties: {
|
|
361
|
+
datum: { type: "string" },
|
|
362
|
+
units: {
|
|
363
|
+
type: "string",
|
|
364
|
+
enum: ["meters", "feet"]
|
|
365
|
+
},
|
|
366
|
+
station: { $ref: "#/components/schemas/Station" },
|
|
367
|
+
distance: { type: "number" },
|
|
368
|
+
timeline: {
|
|
369
|
+
type: "array",
|
|
370
|
+
items: { $ref: "#/components/schemas/TimelineEntry" }
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
},
|
|
374
|
+
Error: {
|
|
375
|
+
type: "object",
|
|
376
|
+
properties: {
|
|
377
|
+
message: { type: "string" },
|
|
378
|
+
errors: {
|
|
379
|
+
type: "array",
|
|
380
|
+
items: {
|
|
381
|
+
type: "object",
|
|
382
|
+
additionalProperties: true
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
required: ["message"]
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
//#endregion
|
|
393
|
+
//#region src/routes.ts
|
|
394
|
+
const router = Router();
|
|
395
|
+
router.use(json());
|
|
396
|
+
router.use(middleware({
|
|
397
|
+
apiSpec: openapi_default,
|
|
398
|
+
validateRequests: { coerceTypes: true },
|
|
399
|
+
validateResponses: import.meta.env?.VITEST
|
|
400
|
+
}));
|
|
401
|
+
router.get("/openapi.json", (req, res) => {
|
|
402
|
+
res.json(openapi_default);
|
|
403
|
+
});
|
|
404
|
+
router.get("/extremes", (req, res) => {
|
|
405
|
+
res.json(getExtremesPrediction({
|
|
406
|
+
...positionOptions(req),
|
|
407
|
+
...predictionOptions(req)
|
|
408
|
+
}));
|
|
409
|
+
});
|
|
410
|
+
router.get("/timeline", (req, res) => {
|
|
411
|
+
try {
|
|
412
|
+
res.json(getTimelinePrediction({
|
|
413
|
+
...positionOptions(req),
|
|
414
|
+
...predictionOptions(req)
|
|
415
|
+
}));
|
|
416
|
+
} catch (error) {
|
|
417
|
+
res.status(400).json({ message: error.message });
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
router.get("/stations", (req, res) => {
|
|
421
|
+
if (req.query.id) try {
|
|
422
|
+
return res.json(findStation(req.query.id));
|
|
423
|
+
} catch (error) {
|
|
424
|
+
return res.status(404).json({ message: error.message });
|
|
425
|
+
}
|
|
426
|
+
const { latitude, longitude } = positionOptions(req);
|
|
427
|
+
if (latitude === void 0 || longitude === void 0) return res.status(400).json({ message: "Either 'id' or coordinates (latitude and longitude) required" });
|
|
428
|
+
const limit = req.query.limit ? parseInt(req.query.limit, 10) : 10;
|
|
429
|
+
const stations = stationsNear({
|
|
430
|
+
latitude,
|
|
431
|
+
longitude
|
|
432
|
+
}, limit);
|
|
433
|
+
res.json(stations);
|
|
434
|
+
});
|
|
435
|
+
router.get("/stations/:id/extremes", (req, res) => {
|
|
436
|
+
let station;
|
|
437
|
+
try {
|
|
438
|
+
station = findStation(req.params.id);
|
|
439
|
+
} catch (error) {
|
|
440
|
+
return res.status(404).json({ message: error.message });
|
|
441
|
+
}
|
|
442
|
+
res.json(station.getExtremesPrediction(predictionOptions(req)));
|
|
443
|
+
});
|
|
444
|
+
router.get("/stations/:id/timeline", (req, res) => {
|
|
445
|
+
try {
|
|
446
|
+
const station = findStation(req.params.id);
|
|
447
|
+
res.json(station.getTimelinePrediction(predictionOptions(req)));
|
|
448
|
+
} catch (error) {
|
|
449
|
+
if (error.message.includes("not found")) return res.status(404).json({ message: error.message });
|
|
450
|
+
return res.status(400).json({ message: error.message });
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
router.use(((err, _req, res, next) => {
|
|
454
|
+
if (!err) return next();
|
|
455
|
+
const status = err.status ?? 500;
|
|
456
|
+
const message = err.message ?? "Unknown error";
|
|
457
|
+
res.status(status).json({
|
|
458
|
+
message,
|
|
459
|
+
errors: err.errors
|
|
460
|
+
});
|
|
461
|
+
}));
|
|
462
|
+
function positionOptions(req) {
|
|
463
|
+
return {
|
|
464
|
+
latitude: req.query.latitude,
|
|
465
|
+
longitude: req.query.longitude
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
function predictionOptions(req) {
|
|
469
|
+
return {
|
|
470
|
+
start: req.query.start ? new Date(req.query.start) : /* @__PURE__ */ new Date(),
|
|
471
|
+
end: req.query.end ? new Date(req.query.end) : new Date(Date.now() + 10080 * 60 * 1e3),
|
|
472
|
+
...req.query.datum && { datum: req.query.datum },
|
|
473
|
+
...req.query.units && { units: req.query.units }
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
var routes_default = router;
|
|
477
|
+
|
|
478
|
+
//#endregion
|
|
479
|
+
//#region src/index.ts
|
|
480
|
+
function createApp() {
|
|
481
|
+
return express().use("/", routes_default);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
//#endregion
|
|
485
|
+
export { createApp, openapi_default as openapi, routes_default as routes };
|
|
486
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
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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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@neaps/api",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "HTTP JSON API for tide predictions",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/neaps/neaps.git",
|
|
8
|
+
"directory": "packages/api"
|
|
9
|
+
},
|
|
10
|
+
"author": "Brandon Keepers <brandon@openwaters.io>",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"types": "./dist/index.d.mts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": "./dist/index.mjs",
|
|
16
|
+
"./package.json": "./package.json"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"bin": {
|
|
22
|
+
"neaps-server": "./bin/server.js"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc && tsdown",
|
|
26
|
+
"prepack": "npm run build",
|
|
27
|
+
"start": "node bin/server.js"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"express": "^4.18.2",
|
|
31
|
+
"express-openapi-validator": "^5.1.6",
|
|
32
|
+
"neaps": "^0.2.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/express": "^4.17.21",
|
|
36
|
+
"@types/supertest": "^6.0.2",
|
|
37
|
+
"supertest": "^6.3.3"
|
|
38
|
+
}
|
|
39
|
+
}
|