Skip to content

Commit a2889b3

Browse files
authored
feature: History API OpenApi description & api types (#1653)
This adds History API related types under @signalk/server-api/history/ and the OpenAPI description for the history API.
1 parent 9365030 commit a2889b3

6 files changed

Lines changed: 377 additions & 1 deletion

File tree

packages/server-api/package.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,16 @@
2525
},
2626
"peerDependencies": {
2727
"baconjs": "^1.0.1"
28+
},
29+
"exports": {
30+
".": "./dist/index.js",
31+
"./history": "./dist/history.js"
32+
},
33+
"typesVersions": {
34+
"*": {
35+
"history": [
36+
"dist/history.d.ts"
37+
]
38+
}
2839
}
2940
}

packages/server-api/src/history.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { Context, Path, Timestamp } from '.'
2+
3+
export type AggregateMethod =
4+
| 'average'
5+
| 'min'
6+
| 'max'
7+
| 'first'
8+
| 'last'
9+
| 'mid'
10+
| 'middle_index'
11+
12+
export type ValueList = {
13+
path: Path
14+
method: AggregateMethod
15+
}[]
16+
17+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
18+
export type DataRow = [Timestamp, ...any[]]
19+
20+
export interface ValuesResponse {
21+
context: Context
22+
range: {
23+
from: Timestamp
24+
to: Timestamp
25+
}
26+
values: ValueList
27+
28+
data: DataRow[]
29+
}
30+
31+
const _example: ValuesResponse = {
32+
context:
33+
'vessels.urn:mrn:signalk:uuid:2ffee4a6-52f6-4d4e-8179-0fc9aaf22c87' as Context,
34+
range: {
35+
from: '2025-08-11T05:26:04.888Z' as Timestamp,
36+
to: '2025-08-11T05:41:04.888Z' as Timestamp
37+
},
38+
values: [
39+
{
40+
path: 'navigation.speedOverGround' as Path,
41+
method: 'average' as AggregateMethod
42+
}
43+
],
44+
data: [
45+
['2025-08-11T05:26:05.000Z' as Timestamp, null],
46+
['2025-08-11T05:26:10.000Z' as Timestamp, 3.14]
47+
]
48+
}
49+
50+
export type TimeRangeQueryParams =
51+
| {
52+
// only duration, to defaults to now
53+
duration: number | string
54+
from?: never
55+
to?: never
56+
}
57+
| {
58+
// duration from
59+
duration: number | string
60+
from: string
61+
to?: never
62+
}
63+
| {
64+
// duration to
65+
duration: number | string
66+
from?: never
67+
to: string
68+
}
69+
| {
70+
// no duration, only from, to defaults to now
71+
duration?: never
72+
from: string
73+
to?: never
74+
}
75+
| {
76+
// from - to
77+
duration: never
78+
from: string
79+
to: string
80+
}
81+
82+
export type ValuesRequestQueryParams = TimeRangeQueryParams & {
83+
context?: string
84+
resolution?: number
85+
}
86+
87+
export type PathsRequestQueryParams = TimeRangeQueryParams
88+
export type PathsResponse = Path[]
89+
90+
export type ContextsRequestQueryParams = TimeRangeQueryParams
91+
export type ContextsResponse = Context[]

packages/server-api/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export * from './propertyvalues'
1212
export * from './brand'
1313
export * from './streambundle'
1414
export * from './subscriptionmanager'
15+
export * as history from './history'
1516

1617
export interface Position {
1718
latitude: number

src/api/history/openApi.json

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
{
2+
"openapi": "3.0.0",
3+
"info": {
4+
"version": "0.0.1",
5+
"title": "Signal K History API",
6+
"description": "API for querying historical data, typically stored in a database. The actual storage backend is not defined by this API and can be implemented in various ways, typically as a plugin like [signalk-parquet](https://www.npmjs.com/package/signalk-parquet) and [signalk-to-influxdb2](https://www.npmjs.com/package/signalk-to-influxdb2). The most common use case for the API is to show graphs of past values.\n\n The time range can be defined as a combination of **from**, **to** and **duration** parameters. Omitted from and to parameters default to current moment in time, so that for example specifying just **duration** refers to the length of duration up to this moment.",
7+
"license": {
8+
"name": "Apache 2.0",
9+
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
10+
}
11+
},
12+
"servers": [
13+
{
14+
"url": "/signalk/v2/api/history"
15+
}
16+
],
17+
"components": {
18+
"parameters": {
19+
"TimeRangeFrom": {
20+
"name": "from",
21+
"in": "query",
22+
"description": "Start of the time range, inclusive as ISO 8601 timestamp",
23+
"schema": {
24+
"type": "string",
25+
"format": "date-time",
26+
"example": "2018-03-20T09:13:28Z"
27+
}
28+
},
29+
"TimeRangeDuration": {
30+
"name": "duration",
31+
"in": "query",
32+
"description": "Duration of the time range in milliseconds (integer) or as an ISO8601 Duration string. Can be specified with either 'from' or 'to'. If they are both omitted is relative to 'now'. See https://datatracker.ietf.org/doc/html/rfc3339#appendix-A",
33+
"schema": {
34+
"oneOf": [
35+
{
36+
"type": "integer",
37+
"description": "Duration in milliseconds"
38+
},
39+
{
40+
"type": "string",
41+
"format": "duration",
42+
"description": "ISO8601 Duration string"
43+
}
44+
]
45+
}
46+
},
47+
"TimeRangeTo": {
48+
"name": "to",
49+
"in": "query",
50+
"description": "End of the time range, inclusive. 'Now' if omitted",
51+
"schema": {
52+
"type": "string",
53+
"format": "date-time",
54+
"example": "2018-03-20T09:13:28Z"
55+
}
56+
}
57+
}
58+
},
59+
"paths": {
60+
"/values": {
61+
"get": {
62+
"summary": "Retrieve historical data",
63+
"description": "Returns historical data series for the paths and time range specified in query parameters",
64+
"parameters": [
65+
{
66+
"$ref": "#/components/parameters/TimeRangeFrom"
67+
},
68+
{
69+
"$ref": "#/components/parameters/TimeRangeDuration"
70+
},
71+
{
72+
"$ref": "#/components/parameters/TimeRangeTo"
73+
},
74+
{
75+
"name": "paths",
76+
"in": "query",
77+
"description": "Comma separated ist of Signal K paths whose data should be retrieved, optional aggregation methods for each path as postfix separated by a colon. Aggregation methods: 'average' | 'min' | 'max' | 'first' | 'last' | 'mid' | 'middle_index'",
78+
"example": "navigation.speedOverGround,navigation.speedThroughWater:max",
79+
"schema": {
80+
"type": "string"
81+
},
82+
"required": true
83+
},
84+
{
85+
"name": "context",
86+
"in": "query",
87+
"description": "Signal K context that the data is about, defaults to 'vessels.self'",
88+
"example": "vessels.urn:mrn:imo:mmsi:123456789",
89+
"schema": {
90+
"type": "string"
91+
}
92+
},
93+
{
94+
"name": "resolution",
95+
"in": "query",
96+
"description": "Length of data sample time window in milliseconds or as a time expression ('1s', '1m', '1h', '1d'). If resolution is not specified the server should provide data in a reasonable time resolution, depending on the time range in the request.",
97+
"schema": {
98+
"type": "number",
99+
"format": "integer"
100+
}
101+
}
102+
],
103+
"responses": {
104+
"200": {
105+
"description": "Series data with header",
106+
"content": {
107+
"application/json": {
108+
"schema": {
109+
"type": "object",
110+
"required": ["context", "", "target"],
111+
"properties": {
112+
"context": {
113+
"type": "string",
114+
"description": "Signal K context that the data is about",
115+
"example": "vessels.urn:mrn:imo:mmsi:123456789"
116+
},
117+
"range": {
118+
"type": "object",
119+
"properties": {
120+
"from": {
121+
"type": "string",
122+
"format": "date-time",
123+
"description": "Start of the time range, inclusive, as UTC timestamp",
124+
"example": "2018-03-20T09:12:28Z"
125+
},
126+
"to": {
127+
"type": "string",
128+
"format": "date-time",
129+
"description": "End of the time range, inclusive, as UTC timestamp",
130+
"example": "2018-03-20T09:13:28Z"
131+
}
132+
}
133+
},
134+
"values": {
135+
"type": "array",
136+
"items": {
137+
"type": "object",
138+
"properties": {
139+
"path": {
140+
"type": "string",
141+
"description": "Signal K path"
142+
},
143+
"method": {
144+
"type": "string",
145+
"description": "Aggregation method"
146+
}
147+
}
148+
}
149+
},
150+
"data": {
151+
"type": "array",
152+
"items": {
153+
"type": "array",
154+
"items": {
155+
"description": "Data for a point in time. The first array element is the timestamp in ISO 8601 format. Missing data for a path is returned as null",
156+
"oneOf": [
157+
{
158+
"type": "string"
159+
},
160+
{
161+
"type": "number"
162+
},
163+
{
164+
"type": "null"
165+
},
166+
{
167+
"type": "array",
168+
"items": {
169+
"type": "number"
170+
}
171+
}
172+
]
173+
}
174+
},
175+
"example": [
176+
["2023-11-09T02:45:38.160Z", 13.2, null, [-120.5, 59.2]]
177+
]
178+
}
179+
}
180+
}
181+
}
182+
}
183+
}
184+
}
185+
}
186+
},
187+
"/contexts": {
188+
"get": {
189+
"summary": "Get contexts that have some historical data",
190+
"description": "Returns an array of contexts that have some historical data to query with /values for the specified time range",
191+
"parameters": [
192+
{
193+
"$ref": "#/components/parameters/TimeRangeFrom"
194+
},
195+
{
196+
"$ref": "#/components/parameters/TimeRangeDuration"
197+
},
198+
{
199+
"$ref": "#/components/parameters/TimeRangeTo"
200+
}
201+
],
202+
"responses": {
203+
"200": {
204+
"description": "Array of contexts",
205+
"content": {
206+
"application/json": {
207+
"schema": {
208+
"type": "array",
209+
"items": {
210+
"type": "string",
211+
"description": "Signal K Context",
212+
"example": "vessels.urn:mrn:imo:mmsi:123456789"
213+
}
214+
}
215+
}
216+
}
217+
}
218+
}
219+
}
220+
},
221+
"/paths": {
222+
"get": {
223+
"summary": "Get paths that have some historical data",
224+
"description": "Returns an array of path that have some historical data to query with /values for the specified time range",
225+
"parameters": [
226+
{
227+
"$ref": "#/components/parameters/TimeRangeFrom"
228+
},
229+
{
230+
"$ref": "#/components/parameters/TimeRangeDuration"
231+
},
232+
{
233+
"$ref": "#/components/parameters/TimeRangeTo"
234+
}
235+
],
236+
"responses": {
237+
"200": {
238+
"description": "Array of paths",
239+
"content": {
240+
"application/json": {
241+
"schema": {
242+
"type": "array",
243+
"items": {
244+
"type": "string",
245+
"description": "Signal K Path",
246+
"example": "navigation.speedOverGround"
247+
}
248+
}
249+
}
250+
}
251+
}
252+
}
253+
}
254+
}
255+
}
256+
}

src/api/history/openApi.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { OpenApiDescription } from '../swagger'
2+
import historyApiDoc from './openApi.json'
3+
4+
export const historyApiRecord = {
5+
name: 'history',
6+
path: '/signalk/v2/api/history',
7+
apiDoc: historyApiDoc as unknown as OpenApiDescription
8+
}
9+
10+
const yesterday = new Date()
11+
yesterday.setDate(yesterday.getDate() - 1)
12+
historyApiDoc.paths['/values'].get.parameters[0].example =
13+
yesterday.toISOString()
14+
historyApiDoc.paths['/values'].get.parameters[1].example =
15+
new Date().toISOString()

0 commit comments

Comments
 (0)