@rivi-software/radiotools-contract 0.2.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/graphql/schema.graphql +214 -0
- package/package.json +26 -0
- package/src/index.ts +24 -0
- package/src/manifest.ts +207 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
scalar JSON
|
|
2
|
+
|
|
3
|
+
type Query {
|
|
4
|
+
station: Station!
|
|
5
|
+
schemas(type: String, version: Int): [ContentSchema!]!
|
|
6
|
+
content(type: String!, cursor: ID, limit: Int = 25): ContentCollection!
|
|
7
|
+
contentRecord(id: ID!): PublishedRecord!
|
|
8
|
+
schedule(fromDate: String!, throughDate: String!): Schedule!
|
|
9
|
+
programs(fromDate: String!, throughDate: String!): Programs!
|
|
10
|
+
currentBroadcast: CurrentBroadcast!
|
|
11
|
+
nowPlaying: NowPlaying!
|
|
12
|
+
trackHistory(cursor: String, limit: Int = 25): TrackHistory!
|
|
13
|
+
weather: Weather
|
|
14
|
+
traffic: Traffic
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type Mutation {
|
|
18
|
+
previewEditorial(input: PreviewInput!): RevisionPreview!
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type Station {
|
|
22
|
+
contractVersion: Int!
|
|
23
|
+
id: ID!
|
|
24
|
+
slug: String!
|
|
25
|
+
name: String!
|
|
26
|
+
timezone: String!
|
|
27
|
+
publishingLanguage: String!
|
|
28
|
+
resources: StationResources!
|
|
29
|
+
delivery: StationDelivery!
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type StationResources {
|
|
33
|
+
schemas: String!
|
|
34
|
+
content: String
|
|
35
|
+
schedule: String
|
|
36
|
+
programs: String
|
|
37
|
+
broadcast: String
|
|
38
|
+
nowPlaying: String
|
|
39
|
+
trackHistory: String
|
|
40
|
+
socket: String
|
|
41
|
+
weather: String
|
|
42
|
+
traffic: String
|
|
43
|
+
media: String!
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type StationDelivery {
|
|
47
|
+
cache: String!
|
|
48
|
+
conditional: String!
|
|
49
|
+
pollIntervalSeconds: Int!
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
type ContentSchema {
|
|
53
|
+
key: String!
|
|
54
|
+
label: String!
|
|
55
|
+
version: Int!
|
|
56
|
+
archived: Boolean!
|
|
57
|
+
fields: JSON!
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
type ContentCollection {
|
|
61
|
+
records: [PublishedRecord!]!
|
|
62
|
+
nextCursor: ID
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
type PublishedRecord {
|
|
66
|
+
id: ID!
|
|
67
|
+
publicationId: ID!
|
|
68
|
+
revision: Int!
|
|
69
|
+
type: String!
|
|
70
|
+
schemaVersion: Int!
|
|
71
|
+
publishedAt: String!
|
|
72
|
+
values: JSON!
|
|
73
|
+
document: JSON!
|
|
74
|
+
media: [PublicMedia!]!
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
type PublicMedia {
|
|
78
|
+
assetId: ID!
|
|
79
|
+
versionId: ID!
|
|
80
|
+
kind: String!
|
|
81
|
+
url: String!
|
|
82
|
+
credits: String!
|
|
83
|
+
mime: String!
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
type Schedule {
|
|
87
|
+
contractVersion: Int!
|
|
88
|
+
timezone: String!
|
|
89
|
+
broadcasts: [Broadcast!]!
|
|
90
|
+
truncated: Boolean!
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
type Programs {
|
|
94
|
+
contractVersion: Int!
|
|
95
|
+
timezone: String!
|
|
96
|
+
fromDate: String!
|
|
97
|
+
throughDate: String!
|
|
98
|
+
programs: [ProgramSummary!]!
|
|
99
|
+
truncated: Boolean!
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
type ProgramSummary {
|
|
103
|
+
id: ID!
|
|
104
|
+
title: String!
|
|
105
|
+
profileId: ID
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
type CurrentBroadcast {
|
|
109
|
+
contractVersion: Int!
|
|
110
|
+
timezone: String!
|
|
111
|
+
asOf: String!
|
|
112
|
+
broadcast: Broadcast
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
type NowPlaying {
|
|
116
|
+
contractVersion: Int!
|
|
117
|
+
stationId: ID!
|
|
118
|
+
timezone: String!
|
|
119
|
+
track: PlaybackTrack
|
|
120
|
+
elapsedSeconds: Int
|
|
121
|
+
endsAt: String
|
|
122
|
+
freshness: PlaybackFreshness!
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
type PlaybackTrack {
|
|
126
|
+
artist: String!
|
|
127
|
+
title: String!
|
|
128
|
+
album: String
|
|
129
|
+
durationSeconds: Int
|
|
130
|
+
artworkUrl: String
|
|
131
|
+
isPlaying: Boolean!
|
|
132
|
+
observedAt: String!
|
|
133
|
+
receivedAt: String!
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
type PlaybackFreshness {
|
|
137
|
+
status: String!
|
|
138
|
+
thresholdSeconds: Int!
|
|
139
|
+
ageSeconds: Int
|
|
140
|
+
observedAt: String
|
|
141
|
+
receivedAt: String
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
type TrackHistory {
|
|
145
|
+
contractVersion: Int!
|
|
146
|
+
stationId: ID!
|
|
147
|
+
items: [HistoryEntry!]!
|
|
148
|
+
nextCursor: String
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
type HistoryEntry {
|
|
152
|
+
id: ID!
|
|
153
|
+
artist: String!
|
|
154
|
+
title: String!
|
|
155
|
+
album: String
|
|
156
|
+
durationSeconds: Int
|
|
157
|
+
artworkUrl: String
|
|
158
|
+
isPlaying: Boolean!
|
|
159
|
+
observedAt: String!
|
|
160
|
+
receivedAt: String!
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
type Broadcast {
|
|
164
|
+
id: ID!
|
|
165
|
+
title: String!
|
|
166
|
+
startsAt: String!
|
|
167
|
+
endsAt: String!
|
|
168
|
+
timezone: String!
|
|
169
|
+
localDate: String!
|
|
170
|
+
resolution: String!
|
|
171
|
+
status: String!
|
|
172
|
+
cancellationReason: String!
|
|
173
|
+
repeatOfId: ID
|
|
174
|
+
program: BroadcastProgram!
|
|
175
|
+
presenters: [BroadcastPresenter!]!
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
type BroadcastProgram {
|
|
179
|
+
id: ID!
|
|
180
|
+
title: String!
|
|
181
|
+
profileId: ID
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
type BroadcastPresenter {
|
|
185
|
+
id: ID!
|
|
186
|
+
title: String!
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
type Weather {
|
|
190
|
+
stationId: ID!
|
|
191
|
+
timezone: String!
|
|
192
|
+
snapshots: JSON!
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
type Traffic {
|
|
196
|
+
stationId: ID!
|
|
197
|
+
snapshot: JSON!
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
input PreviewInput {
|
|
201
|
+
stationId: ID!
|
|
202
|
+
recordId: ID!
|
|
203
|
+
revision: Int!
|
|
204
|
+
audience: String!
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
type RevisionPreview {
|
|
208
|
+
id: ID!
|
|
209
|
+
revision: Int!
|
|
210
|
+
schemaVersion: Int!
|
|
211
|
+
values: JSON!
|
|
212
|
+
document: JSON!
|
|
213
|
+
media: [PublicMedia!]!
|
|
214
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rivi-software/radiotools-contract",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "RadioTools listener API contract (manifest and GraphQL schema)",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.ts",
|
|
9
|
+
"./graphql": "./graphql/schema.graphql"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"src",
|
|
13
|
+
"graphql"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"check": "tsc --noEmit"
|
|
17
|
+
},
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/RIVI-Software/radiotools-sdk.git",
|
|
24
|
+
"directory": "packages/contract"
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
export {
|
|
6
|
+
LISTENER_CONTRACT_VERSION,
|
|
7
|
+
PUBLIC_SOCKET_PROTOCOL,
|
|
8
|
+
listenerSdkManifest,
|
|
9
|
+
type AuthScope,
|
|
10
|
+
type SdkClientMethod,
|
|
11
|
+
type SdkEnvVar,
|
|
12
|
+
type SdkManifest,
|
|
13
|
+
} from "./manifest";
|
|
14
|
+
|
|
15
|
+
const packageDir = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
|
|
17
|
+
/** Absolute path to the checked-in GraphQL schema for this contract version. */
|
|
18
|
+
export function graphqlSchemaPath(): string {
|
|
19
|
+
return join(packageDir, "..", "graphql", "schema.graphql");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function readGraphqlSchema(): string {
|
|
23
|
+
return readFileSync(graphqlSchemaPath(), "utf8");
|
|
24
|
+
}
|
package/src/manifest.ts
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/** Canonical listener SDK surface — used by @rivi-software/radiotools-sdk-generator targets. */
|
|
2
|
+
|
|
3
|
+
export const LISTENER_CONTRACT_VERSION = 1 as const;
|
|
4
|
+
export const PUBLIC_SOCKET_PROTOCOL = "radiotools.public.v1" as const;
|
|
5
|
+
|
|
6
|
+
export type AuthScope = "listener:read" | "ingest:write";
|
|
7
|
+
|
|
8
|
+
export type SdkEnvVar = {
|
|
9
|
+
name: string;
|
|
10
|
+
required: boolean;
|
|
11
|
+
description: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type SdkClientMethod = {
|
|
15
|
+
name: string;
|
|
16
|
+
scope: AuthScope | "either";
|
|
17
|
+
description: string;
|
|
18
|
+
graphqlField?: string;
|
|
19
|
+
rest?: {
|
|
20
|
+
method: "GET" | "POST";
|
|
21
|
+
pathPattern: string;
|
|
22
|
+
resourceKey?: string;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type SdkManifest = {
|
|
27
|
+
contractVersion: typeof LISTENER_CONTRACT_VERSION;
|
|
28
|
+
socketProtocol: typeof PUBLIC_SOCKET_PROTOCOL;
|
|
29
|
+
packageNames: {
|
|
30
|
+
typescript: "@rivi-software/radiotools-sdk";
|
|
31
|
+
python: "radiotools_sdk";
|
|
32
|
+
react: "@rivi-software/radiotools-sdk-react";
|
|
33
|
+
vue: "@rivi-software/radiotools-sdk-vue";
|
|
34
|
+
svelte: "@rivi-software/radiotools-sdk-svelte";
|
|
35
|
+
go: "github.com/radiotools/radiotools-sdk-go";
|
|
36
|
+
csharp: "RadioTools.Sdk";
|
|
37
|
+
ruby: "radiotools_sdk";
|
|
38
|
+
php: "radiotools/sdk";
|
|
39
|
+
openapi: "@rivi-software/radiotools-openapi";
|
|
40
|
+
kotlin: "com.radiotools.sdk";
|
|
41
|
+
swift: "RadioToolsSDK";
|
|
42
|
+
next: "@rivi-software/radiotools-sdk-next";
|
|
43
|
+
};
|
|
44
|
+
env: SdkEnvVar[];
|
|
45
|
+
clientMethods: SdkClientMethod[];
|
|
46
|
+
integrations: {
|
|
47
|
+
playback: {
|
|
48
|
+
method: "POST";
|
|
49
|
+
path: "/api/v1/integrations/playback";
|
|
50
|
+
scope: "ingest:write";
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const listenerSdkManifest: SdkManifest = {
|
|
56
|
+
contractVersion: LISTENER_CONTRACT_VERSION,
|
|
57
|
+
socketProtocol: PUBLIC_SOCKET_PROTOCOL,
|
|
58
|
+
packageNames: {
|
|
59
|
+
typescript: "@rivi-software/radiotools-sdk",
|
|
60
|
+
python: "radiotools_sdk",
|
|
61
|
+
react: "@rivi-software/radiotools-sdk-react",
|
|
62
|
+
vue: "@rivi-software/radiotools-sdk-vue",
|
|
63
|
+
svelte: "@rivi-software/radiotools-sdk-svelte",
|
|
64
|
+
go: "github.com/radiotools/radiotools-sdk-go",
|
|
65
|
+
csharp: "RadioTools.Sdk",
|
|
66
|
+
ruby: "radiotools_sdk",
|
|
67
|
+
php: "radiotools/sdk",
|
|
68
|
+
openapi: "@rivi-software/radiotools-openapi",
|
|
69
|
+
kotlin: "com.radiotools.sdk",
|
|
70
|
+
swift: "RadioToolsSDK",
|
|
71
|
+
next: "@rivi-software/radiotools-sdk-next",
|
|
72
|
+
},
|
|
73
|
+
env: [
|
|
74
|
+
{
|
|
75
|
+
name: "RADIOTOOLS_BASE_URL",
|
|
76
|
+
required: true,
|
|
77
|
+
description: "RadioTools origin (no trailing slash).",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: "RADIOTOOLS_STATION_SLUG",
|
|
81
|
+
required: true,
|
|
82
|
+
description: "Station slug for published reads and default playback ingest.",
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: "RADIOTOOLS_API_TOKEN",
|
|
86
|
+
required: false,
|
|
87
|
+
description: "Station key with listener:read for published JSON and the public socket.",
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: "RADIOTOOLS_INGEST_TOKEN",
|
|
91
|
+
required: false,
|
|
92
|
+
description: "Integration key named playback with ingest:write for encoder observations.",
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
clientMethods: [
|
|
96
|
+
{
|
|
97
|
+
name: "getStation",
|
|
98
|
+
scope: "listener:read",
|
|
99
|
+
description: "Published station metadata and resource hrefs.",
|
|
100
|
+
graphqlField: "station",
|
|
101
|
+
rest: { method: "GET", pathPattern: "/api/v1/stations/{stationSlug}" },
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: "getSchemas",
|
|
105
|
+
scope: "listener:read",
|
|
106
|
+
description: "Content type schemas for published editorial.",
|
|
107
|
+
graphqlField: "schemas",
|
|
108
|
+
rest: { method: "GET", pathPattern: "{resources.schemas}", resourceKey: "schemas" },
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: "getContent",
|
|
112
|
+
scope: "listener:read",
|
|
113
|
+
description: "Paginated published content records.",
|
|
114
|
+
graphqlField: "content",
|
|
115
|
+
rest: { method: "GET", pathPattern: "{resources.content}", resourceKey: "content" },
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: "getContentRecord",
|
|
119
|
+
scope: "listener:read",
|
|
120
|
+
description: "One published content record by id.",
|
|
121
|
+
graphqlField: "contentRecord",
|
|
122
|
+
rest: { method: "GET", pathPattern: "{resources.content}/{id}", resourceKey: "content" },
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: "getSchedule",
|
|
126
|
+
scope: "listener:read",
|
|
127
|
+
description: "Broadcast schedule for a date window (1–31 inclusive days).",
|
|
128
|
+
graphqlField: "schedule",
|
|
129
|
+
rest: { method: "GET", pathPattern: "{resources.schedule}", resourceKey: "schedule" },
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: "getPrograms",
|
|
133
|
+
scope: "listener:read",
|
|
134
|
+
description: "Program summaries for a date window (1–31 inclusive days).",
|
|
135
|
+
graphqlField: "programs",
|
|
136
|
+
rest: { method: "GET", pathPattern: "{resources.programs}", resourceKey: "programs" },
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: "getCurrentBroadcast",
|
|
140
|
+
scope: "listener:read",
|
|
141
|
+
description: "Current on-air broadcast state.",
|
|
142
|
+
graphqlField: "currentBroadcast",
|
|
143
|
+
rest: { method: "GET", pathPattern: "{resources.broadcast}", resourceKey: "broadcast" },
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: "getNowPlaying",
|
|
147
|
+
scope: "listener:read",
|
|
148
|
+
description: "Now playing track and freshness.",
|
|
149
|
+
graphqlField: "nowPlaying",
|
|
150
|
+
rest: { method: "GET", pathPattern: "{resources.nowPlaying}", resourceKey: "nowPlaying" },
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: "getTrackHistory",
|
|
154
|
+
scope: "listener:read",
|
|
155
|
+
description: "Paginated recently played tracks.",
|
|
156
|
+
graphqlField: "trackHistory",
|
|
157
|
+
rest: {
|
|
158
|
+
method: "GET",
|
|
159
|
+
pathPattern: "{resources.trackHistory}",
|
|
160
|
+
resourceKey: "trackHistory",
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: "getWeather",
|
|
165
|
+
scope: "listener:read",
|
|
166
|
+
description: "Weather snapshot when enabled; otherwise null without a second request.",
|
|
167
|
+
graphqlField: "weather",
|
|
168
|
+
rest: { method: "GET", pathPattern: "{resources.weather}", resourceKey: "weather" },
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
name: "getTraffic",
|
|
172
|
+
scope: "listener:read",
|
|
173
|
+
description: "Traffic snapshot when enabled; otherwise null without a second request.",
|
|
174
|
+
graphqlField: "traffic",
|
|
175
|
+
rest: { method: "GET", pathPattern: "{resources.traffic}", resourceKey: "traffic" },
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: "recommendedPollIntervalMs",
|
|
179
|
+
scope: "listener:read",
|
|
180
|
+
description: "Suggested poll interval from station.delivery.pollIntervalSeconds.",
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
name: "previewEditorial",
|
|
184
|
+
scope: "listener:read",
|
|
185
|
+
description: "Preview unpublished editorial with a grant (not from query strings).",
|
|
186
|
+
graphqlField: "previewEditorial",
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: "reportPlayback",
|
|
190
|
+
scope: "ingest:write",
|
|
191
|
+
description: "Send one encoder playback observation.",
|
|
192
|
+
rest: { method: "POST", pathPattern: "/api/v1/integrations/playback" },
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: "subscribe",
|
|
196
|
+
scope: "listener:read",
|
|
197
|
+
description: "Public WebSocket subscription (server adapter required for Authorization).",
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
integrations: {
|
|
201
|
+
playback: {
|
|
202
|
+
method: "POST",
|
|
203
|
+
path: "/api/v1/integrations/playback",
|
|
204
|
+
scope: "ingest:write",
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
};
|