yoto-nodejs-client 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +736 -0
  3. package/bin/auth.d.ts +3 -0
  4. package/bin/auth.d.ts.map +1 -0
  5. package/bin/auth.js +130 -0
  6. package/bin/content.d.ts +3 -0
  7. package/bin/content.d.ts.map +1 -0
  8. package/bin/content.js +117 -0
  9. package/bin/devices.d.ts +3 -0
  10. package/bin/devices.d.ts.map +1 -0
  11. package/bin/devices.js +239 -0
  12. package/bin/groups.d.ts +3 -0
  13. package/bin/groups.d.ts.map +1 -0
  14. package/bin/groups.js +80 -0
  15. package/bin/icons.d.ts +3 -0
  16. package/bin/icons.d.ts.map +1 -0
  17. package/bin/icons.js +100 -0
  18. package/bin/lib/cli-helpers.d.ts +21 -0
  19. package/bin/lib/cli-helpers.d.ts.map +1 -0
  20. package/bin/lib/cli-helpers.js +140 -0
  21. package/bin/lib/token-helpers.d.ts +14 -0
  22. package/bin/lib/token-helpers.d.ts.map +1 -0
  23. package/bin/lib/token-helpers.js +151 -0
  24. package/bin/refresh-token.d.ts +3 -0
  25. package/bin/refresh-token.d.ts.map +1 -0
  26. package/bin/refresh-token.js +168 -0
  27. package/bin/token-info.d.ts +3 -0
  28. package/bin/token-info.d.ts.map +1 -0
  29. package/bin/token-info.js +351 -0
  30. package/index.d.ts +218 -0
  31. package/index.d.ts.map +1 -0
  32. package/index.js +689 -0
  33. package/lib/api-endpoints/auth.d.ts +56 -0
  34. package/lib/api-endpoints/auth.d.ts.map +1 -0
  35. package/lib/api-endpoints/auth.js +209 -0
  36. package/lib/api-endpoints/auth.test.js +27 -0
  37. package/lib/api-endpoints/constants.d.ts +6 -0
  38. package/lib/api-endpoints/constants.d.ts.map +1 -0
  39. package/lib/api-endpoints/constants.js +31 -0
  40. package/lib/api-endpoints/content.d.ts +275 -0
  41. package/lib/api-endpoints/content.d.ts.map +1 -0
  42. package/lib/api-endpoints/content.js +518 -0
  43. package/lib/api-endpoints/content.test.js +250 -0
  44. package/lib/api-endpoints/devices.d.ts +202 -0
  45. package/lib/api-endpoints/devices.d.ts.map +1 -0
  46. package/lib/api-endpoints/devices.js +404 -0
  47. package/lib/api-endpoints/devices.test.js +483 -0
  48. package/lib/api-endpoints/family-library-groups.d.ts +75 -0
  49. package/lib/api-endpoints/family-library-groups.d.ts.map +1 -0
  50. package/lib/api-endpoints/family-library-groups.js +247 -0
  51. package/lib/api-endpoints/family-library-groups.test.js +272 -0
  52. package/lib/api-endpoints/family.d.ts +39 -0
  53. package/lib/api-endpoints/family.d.ts.map +1 -0
  54. package/lib/api-endpoints/family.js +166 -0
  55. package/lib/api-endpoints/family.test.js +184 -0
  56. package/lib/api-endpoints/helpers.d.ts +29 -0
  57. package/lib/api-endpoints/helpers.d.ts.map +1 -0
  58. package/lib/api-endpoints/helpers.js +104 -0
  59. package/lib/api-endpoints/icons.d.ts +62 -0
  60. package/lib/api-endpoints/icons.d.ts.map +1 -0
  61. package/lib/api-endpoints/icons.js +201 -0
  62. package/lib/api-endpoints/icons.test.js +118 -0
  63. package/lib/api-endpoints/media.d.ts +37 -0
  64. package/lib/api-endpoints/media.d.ts.map +1 -0
  65. package/lib/api-endpoints/media.js +155 -0
  66. package/lib/api-endpoints/test-helpers.d.ts +7 -0
  67. package/lib/api-endpoints/test-helpers.d.ts.map +1 -0
  68. package/lib/api-endpoints/test-helpers.js +64 -0
  69. package/lib/mqtt/client.d.ts +124 -0
  70. package/lib/mqtt/client.d.ts.map +1 -0
  71. package/lib/mqtt/client.js +558 -0
  72. package/lib/mqtt/commands.d.ts +69 -0
  73. package/lib/mqtt/commands.d.ts.map +1 -0
  74. package/lib/mqtt/commands.js +238 -0
  75. package/lib/mqtt/factory.d.ts +12 -0
  76. package/lib/mqtt/factory.d.ts.map +1 -0
  77. package/lib/mqtt/factory.js +107 -0
  78. package/lib/mqtt/index.d.ts +5 -0
  79. package/lib/mqtt/index.d.ts.map +1 -0
  80. package/lib/mqtt/index.js +81 -0
  81. package/lib/mqtt/mqtt.test.js +168 -0
  82. package/lib/mqtt/topics.d.ts +34 -0
  83. package/lib/mqtt/topics.d.ts.map +1 -0
  84. package/lib/mqtt/topics.js +295 -0
  85. package/lib/pkg.cjs +3 -0
  86. package/lib/pkg.d.cts +70 -0
  87. package/lib/pkg.d.cts.map +1 -0
  88. package/lib/token.d.ts +29 -0
  89. package/lib/token.d.ts.map +1 -0
  90. package/lib/token.js +240 -0
  91. package/package.json +91 -0
  92. package/yoto.png +0 -0
@@ -0,0 +1,518 @@
1
+ /**
2
+ * @import { RequestOptions } from './helpers.js'
3
+ */
4
+ import { request } from 'undici'
5
+ import { defaultAuthHeaders, handleBadResponse, mergeRequestOptions } from './helpers.js'
6
+ import { YOTO_API_URL } from './constants.js'
7
+
8
+ // ============================================================================
9
+ // Content: Content endpoints for managing playlists and cards
10
+ // ============================================================================
11
+
12
+ // Shared types used across multiple content endpoints
13
+
14
+ /**
15
+ * @see https://yoto.dev/reference/card-content-schema/
16
+ * @typedef {'none' | 'stories' | 'music' | 'radio' | 'podcast' | 'sfx' | 'activities' | 'alarms'} YotoCategory
17
+ */
18
+
19
+ /**
20
+ * @see https://yoto.dev/reference/card-content-schema/
21
+ * @typedef {'mp3' | 'aac' | 'opus' | 'ogg'} YotoAudioFormat
22
+ */
23
+
24
+ /**
25
+ * @see https://yoto.dev/reference/card-content-schema/
26
+ * @typedef {'en' | 'en-gb' | 'en-us' | 'fr' | 'fr-fr' | 'es' | 'es-es' | 'es-419' | 'de' | 'it'} YotoLanguage
27
+ */
28
+
29
+ /**
30
+ * @see https://yoto.dev/reference/card-content-schema/
31
+ * @typedef {'new' | 'inprogress' | 'complete' | 'live' | 'archived'} YotoStatusName
32
+ */
33
+
34
+ /**
35
+ * @see https://yoto.dev/reference/card-content-schema/
36
+ * @typedef {'DESC' | 'ASC'} YotoPlaybackDirection
37
+ */
38
+
39
+ /**
40
+ * @see https://yoto.dev/reference/card-content-schema/
41
+ * @typedef {'linear' | 'interactive'} YotoPlaybackType
42
+ */
43
+
44
+ /**
45
+ * @see https://yoto.dev/myo/how-playlists-work/
46
+ * @typedef {Object} YotoTrackDisplay
47
+ * @property {string} icon16x16 - Track icon (16x16px) in format "yoto:#<sha256-hash>"
48
+ */
49
+
50
+ /**
51
+ * @see https://yoto.dev/myo/how-playlists-work/
52
+ * @typedef {'audio' | 'stream'} YotoTrackType
53
+ */
54
+
55
+ /**
56
+ * @see https://yoto.dev/myo/how-playlists-work/
57
+ * @typedef {'stereo' | 'mono'} YotoChannels
58
+ */
59
+
60
+ /**
61
+ * A track represents a single audio file within a chapter.
62
+ * Tracks within a chapter play in sequence.
63
+ * @see https://yoto.dev/myo/how-playlists-work/
64
+ * @see https://yoto.dev/api/getcontent/
65
+ * @see https://yoto.dev/api/getusersmyocontent/
66
+ * @see https://yoto.dev/reference/card-content-schema/
67
+ * @typedef {Object} YotoTrack
68
+ * @property {string} key - Track identifier
69
+ * @property {string} title - Track title
70
+ * @property {string} trackUrl - Track URL in format "yoto:#<sha256-hash>"
71
+ * @property {YotoAudioFormat} format - Audio format
72
+ * @property {YotoTrackType} type - Track type ("audio" or "stream")
73
+ * @property {string} overlayLabel - Display label shown on player
74
+ * @property {number} duration - Track duration in seconds
75
+ * @property {number} fileSize - File size in bytes
76
+ * @property {YotoChannels} channels - Audio channels (stereo or mono)
77
+ * @property {any | null} ambient - Ambient setting for track
78
+ * @property {YotoTrackDisplay} display - Display configuration with icon
79
+ * @property {string | null} [uid] - Optional unique identifier
80
+ * @property {string | null} [overlayLabelOverride] - Optional override for overlay label
81
+ */
82
+
83
+ /**
84
+ * @see https://yoto.dev/myo/how-playlists-work/
85
+ * @typedef {Object} YotoChapterDisplay
86
+ * @property {string} icon16x16 - Chapter icon (16x16px) in format "yoto:#<sha256-hash>"
87
+ */
88
+
89
+ /**
90
+ * A chapter contains one or more tracks and has its own title and icon.
91
+ * A playlist is made up of one or more chapters.
92
+ * @see https://yoto.dev/myo/how-playlists-work/
93
+ * @see https://yoto.dev/api/getcontent/
94
+ * @see https://yoto.dev/api/getusersmyocontent/
95
+ * @typedef {Object} YotoChapter
96
+ * @property {string} key - Chapter identifier
97
+ * @property {string} title - Chapter title
98
+ * @property {YotoTrack[]} tracks - Array of tracks in this chapter
99
+ * @property {YotoChapterDisplay} display - Display configuration with icon
100
+ * @property {string} overlayLabel - Overlay label shown on player
101
+ * @property {number} duration - Total chapter duration in seconds
102
+ * @property {number} fileSize - Total chapter file size in bytes
103
+ * @property {any | null} availableFrom - Availability date/time
104
+ * @property {any | null} ambient - Ambient setting for chapter
105
+ * @property {string | null} defaultTrackDisplay - Default track display setting
106
+ * @property {string | null} defaultTrackAmbient - Default track ambient setting
107
+ * @property {string | null} [overlayLabelOverride] - Optional override for overlay label
108
+ * @property {number} [startTime] - Chapter start time (for API compatibility)
109
+ */
110
+
111
+ /**
112
+ * @see https://yoto.dev/api/getcontent/
113
+ * @typedef {Object} YotoEditSettings
114
+ * @property {string} autoOverlayLabels
115
+ * @property {boolean} editKeys
116
+ * @property {boolean} transcodeAudioUploads
117
+ */
118
+
119
+ /**
120
+ * @see https://yoto.dev/api/getcontent/
121
+ * @see https://yoto.dev/reference/card-content-schema/
122
+ * @typedef {Object} YotoMetadata
123
+ * @property {YotoCategory} category - Content category
124
+ * @property {Object} cover - Cover image
125
+ * @property {string | null} cover.imageL - Large cover image URL
126
+ * @property {YotoMedia} media - Media information
127
+ * @property {string} [accent] - Accent (e.g., "British")
128
+ * @property {boolean} [addToFamilyLibrary] - Whether to add to family library
129
+ * @property {string} [author] - Author name
130
+ * @property {string} [copyright] - Copyright information
131
+ * @property {string} [description] - Content description
132
+ * @property {string[]} [genre] - Genre tags (e.g., ["Adventure", "Fantasy"])
133
+ * @property {YotoLanguage[]} [languages] - Language codes
134
+ * @property {number} [maxAge] - Maximum recommended age
135
+ * @property {number} [minAge] - Minimum recommended age
136
+ * @property {string[]} [musicType] - Music types (e.g., ["Classical", "Instrumental"])
137
+ * @property {string} [note] - Additional notes
138
+ * @property {string} [order] - Order (e.g., "featured")
139
+ * @property {string} [audioPreviewUrl] - Preview audio URL
140
+ * @property {string} [readBy] - Narrator name
141
+ * @property {boolean} [share] - Whether sharing is enabled
142
+ * @property {YotoStatus} [status] - Content status
143
+ * @property {string[]} [tags] - Content tags
144
+ * @property {string} [feedUrl] - Podcast feed URL
145
+ * @property {number} [numEpisodes] - Number of episodes (for podcasts)
146
+ * @property {YotoPlaybackDirection} [playbackDirection] - Playback direction for podcasts
147
+ */
148
+
149
+ /**
150
+ * @see https://yoto.dev/api/getcontent/
151
+ * @see https://yoto.dev/api/getusersmyocontent/
152
+ * @typedef {Object} YotoMedia
153
+ * @property {number} duration
154
+ * @property {number} fileSize
155
+ * @property {boolean} hasStreams
156
+ */
157
+
158
+ /**
159
+ * @see https://yoto.dev/api/getusersmyocontent/
160
+ * @see https://yoto.dev/reference/card-content-schema/
161
+ * @typedef {Object} YotoContentConfig
162
+ * @property {string} [autoadvance] - Auto-advance setting
163
+ * @property {boolean} [onlineOnly] - Whether content requires online access
164
+ * @property {YotoShuffle[]} [shuffle] - Shuffle configuration
165
+ * @property {number} [trackNumberOverlayTimeout] - Track number overlay timeout in seconds
166
+ * @property {number} [resumeTimeout] - Resume timeout in seconds
167
+ * @property {boolean} [systemActivity] - System activity setting
168
+ */
169
+
170
+ /**
171
+ * @see https://yoto.dev/api/getusersmyocontent/
172
+ * @typedef {Object} YotoShuffle
173
+ * @property {number} end
174
+ * @property {number} limit
175
+ * @property {number} start
176
+ */
177
+
178
+ /**
179
+ * @see https://yoto.dev/api/getusersmyocontent/
180
+ * @typedef {Object} YotoContentCover
181
+ * @property {string | null} imageL
182
+ */
183
+
184
+ /**
185
+ * @see https://yoto.dev/api/getusersmyocontent/
186
+ * @typedef {Object} YotoMyoEditSettings
187
+ * @property {string} autoOverlayLabels
188
+ * @property {boolean} editKeys
189
+ * @property {YotoPodcastTrackDisplay} [podcastTrackDisplay]
190
+ * @property {string} [podcastType]
191
+ */
192
+
193
+ /**
194
+ * @see https://yoto.dev/api/getusersmyocontent/
195
+ * @typedef {Object} YotoPodcastTrackDisplay
196
+ * @property {string} icon16x16
197
+ */
198
+
199
+ /**
200
+ * @see https://yoto.dev/api/getusersmyocontent/
201
+ * @see https://yoto.dev/reference/card-content-schema/
202
+ * @typedef {Object} YotoStatus
203
+ * @property {YotoStatusName} name - Status name
204
+ * @property {string} updatedAt - Last update timestamp
205
+ */
206
+
207
+ /**
208
+ * @see https://yoto.dev/api/getusersmyocontent/
209
+ * @typedef {Object} YotoSharing
210
+ * @property {string} linkCreatedAt
211
+ * @property {string} linkUrl
212
+ * @property {number} shareCount
213
+ * @property {number} shareLimit
214
+ */
215
+
216
+ /**
217
+ * @see https://yoto.dev/api/getusersmyocontent/
218
+ * @typedef {Object} YotoClubAvailability
219
+ * @property {string} store
220
+ */
221
+
222
+ /**
223
+ * @see https://yoto.dev/api/getcontent/
224
+ * @typedef {Object} YotoContentResponse
225
+ * @property {YotoCard} card
226
+ */
227
+
228
+ /**
229
+ * @see https://yoto.dev/api/getcontent/
230
+ * @see https://yoto.dev/reference/card-content-schema/
231
+ * @typedef {Object} YotoCard
232
+ * @property {string} cardId - Unique card identifier
233
+ * @property {YotoContent} content - Card content and chapters
234
+ * @property {string} createdAt - Creation timestamp
235
+ * @property {string} creatorEmail - Creator email address
236
+ * @property {boolean} deleted - Whether card is deleted
237
+ * @property {YotoMetadata} metadata - Card metadata
238
+ * @property {number} shareLimit - Share limit count
239
+ * @property {string} shareLinkCreatedAt - Share link creation timestamp
240
+ * @property {string} slug - URL-friendly slug
241
+ * @property {string} title - Card title
242
+ * @property {string} updatedAt - Last update timestamp
243
+ * @property {string} userId - Owner user ID
244
+ * @property {string[]} [tags] - Top-level tags
245
+ */
246
+
247
+ /**
248
+ * @see https://yoto.dev/api/getcontent/
249
+ * @see https://yoto.dev/reference/card-content-schema/
250
+ * @typedef {Object} YotoContent
251
+ * @property {string} activity - Activity type
252
+ * @property {YotoChapter[]} chapters - Array of chapters
253
+ * @property {YotoContentConfig} config - Content configuration
254
+ * @property {YotoEditSettings} editSettings - Edit settings
255
+ * @property {string} version - Content version
256
+ * @property {YotoPlaybackType} [playbackType] - Playback type (linear or interactive)
257
+ */
258
+
259
+ /**
260
+ * Returns a piece of content from the Yoto API.
261
+ * @see https://yoto.dev/api/getcontent/
262
+ * @param {object} options
263
+ * @param {string} options.accessToken The API token to request with
264
+ * @param {string} options.cardId The card ID to fetch content for
265
+ * @param {string} [options.timezone] Timezone identifier (e.g., 'Pacific/Auckland'). See https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
266
+ * @param {string} [options.signingType] The type of playable signed URLs returned. Use with `playable`. Example: 's3'
267
+ * @param {boolean} [options.playable] Return playable signed URLs
268
+ * @param {string} [options.userAgent] Optional user agent string
269
+ * @param {RequestOptions} [options.requestOptions] Additional undici request options
270
+ * @return {Promise<YotoContentResponse>} The fetched content
271
+ */
272
+ export async function getContent ({
273
+ accessToken,
274
+ userAgent,
275
+ requestOptions,
276
+ cardId,
277
+ timezone,
278
+ signingType,
279
+ playable
280
+ }) {
281
+ const requestUrl = new URL(`/content/${cardId}`, YOTO_API_URL)
282
+
283
+ if (timezone) requestUrl.searchParams.set('timezone', timezone)
284
+ if (signingType) requestUrl.searchParams.set('signingType', signingType)
285
+ if (playable) requestUrl.searchParams.set('playable', playable.toString())
286
+
287
+ const response = await request(requestUrl, mergeRequestOptions({
288
+ method: 'GET',
289
+ headers: defaultAuthHeaders({ accessToken, userAgent })
290
+ }, requestOptions))
291
+
292
+ await handleBadResponse(response, { cardId })
293
+
294
+ const responseBody = /** @type {YotoContentResponse} */ (await response.body.json())
295
+ return responseBody
296
+ }
297
+
298
+ /**
299
+ * @see https://yoto.dev/api/getusersmyocontent/
300
+ * @typedef {Object} YotoMyoContentResponse
301
+ * @property {YotoMyoCard[]} cards
302
+ */
303
+
304
+ /**
305
+ * @see https://yoto.dev/api/getusersmyocontent/
306
+ * @typedef {Object} YotoMyoCard
307
+ * @property {string} availability
308
+ * @property {string} cardId
309
+ * @property {YotoClubAvailability[]} [clubAvailability]
310
+ * @property {YotoMyoContent} content
311
+ * @property {string} createdAt
312
+ * @property {boolean} deleted
313
+ * @property {YotoMyoMetadata} metadata
314
+ * @property {string} [shareLinkCreatedAt]
315
+ * @property {string} [shareLinkUrl]
316
+ * @property {YotoSharing} [sharing]
317
+ * @property {string} slug
318
+ * @property {string} [sortkey]
319
+ * @property {string} title
320
+ * @property {string} updatedAt
321
+ * @property {string} userId
322
+ */
323
+
324
+ /**
325
+ * @see https://yoto.dev/api/getusersmyocontent/
326
+ * @see https://yoto.dev/reference/card-content-schema/
327
+ * @typedef {Object} YotoMyoContent
328
+ * @property {string} activity - Activity type
329
+ * @property {YotoContentConfig} config - Content configuration
330
+ * @property {YotoContentCover} [cover] - Cover image
331
+ * @property {YotoMyoEditSettings} editSettings - Edit settings
332
+ * @property {boolean} [hidden] - Whether content is hidden
333
+ * @property {boolean} [restricted] - Whether content is restricted
334
+ * @property {string} version - Content version
335
+ * @property {YotoPlaybackType} [playbackType] - Playback type (linear or interactive)
336
+ */
337
+
338
+ /**
339
+ * @see https://yoto.dev/api/getusersmyocontent/
340
+ * @see https://yoto.dev/reference/card-content-schema/
341
+ * @typedef {Object} YotoMyoMetadata
342
+ * @property {YotoCategory} category - Content category
343
+ * @property {YotoContentCover} [cover] - Cover image
344
+ * @property {YotoMedia} media - Media information
345
+ * @property {string} [accent] - Accent (e.g., "British")
346
+ * @property {boolean} [addToFamilyLibrary] - Whether to add to family library
347
+ * @property {string} [author] - Author name
348
+ * @property {string} [copyright] - Copyright information
349
+ * @property {string} [description] - Content description
350
+ * @property {string[]} [genre] - Genre tags (e.g., ["Adventure", "Fantasy"])
351
+ * @property {YotoLanguage[]} [languages] - Language codes
352
+ * @property {number} [maxAge] - Maximum recommended age
353
+ * @property {number} [minAge] - Minimum recommended age
354
+ * @property {string[]} [musicType] - Music types (e.g., ["Classical", "Instrumental"])
355
+ * @property {string} [note] - Additional notes
356
+ * @property {any} [order] - Order
357
+ * @property {string} [previewAudio] - Preview audio URL
358
+ * @property {string} [audioPreviewUrl] - Audio preview URL (alternative field name)
359
+ * @property {string} [readBy] - Narrator name
360
+ * @property {boolean} [share] - Whether sharing is enabled
361
+ * @property {YotoStatus} [status] - Content status
362
+ * @property {string[]} [tags] - Content tags
363
+ * @property {string | null} [feedUrl] - Podcast feed URL
364
+ * @property {number} [numEpisodes] - Number of episodes (for podcasts)
365
+ * @property {YotoPlaybackDirection} [playbackDirection] - Playback direction for podcasts
366
+ * @property {boolean} [hidden] - Whether content is hidden
367
+ * @property {any[]} [list] - List data
368
+ */
369
+
370
+ /**
371
+ * Returns the MYO cards belonging to the authenticated user.
372
+ * @see https://yoto.dev/api/getusersmyocontent/
373
+ * @param {object} options
374
+ * @param {string} options.accessToken The API token to request with
375
+ * @param {boolean} [options.showDeleted=false] Show owned cards that have been deleted
376
+ * @param {string} [options.userAgent] Optional user agent string
377
+ * @param {RequestOptions} [options.requestOptions] Additional undici request options
378
+ * @return {Promise<YotoMyoContentResponse>} The user's MYO content
379
+ */
380
+ export async function getUserMyoContent ({
381
+ accessToken,
382
+ userAgent,
383
+ requestOptions,
384
+ showDeleted = false
385
+ }) {
386
+ const requestUrl = new URL('/content/mine', YOTO_API_URL)
387
+
388
+ if (showDeleted) requestUrl.searchParams.set('showdeleted', showDeleted.toString())
389
+
390
+ const response = await request(requestUrl, mergeRequestOptions({
391
+ method: 'GET',
392
+ headers: defaultAuthHeaders({ accessToken, userAgent })
393
+ }, requestOptions))
394
+
395
+ await handleBadResponse(response)
396
+
397
+ const responseBody = /** @type {YotoMyoContentResponse} */ (await response.body.json())
398
+ return responseBody
399
+ }
400
+
401
+ /**
402
+ * @see https://yoto.dev/api/createorupdatecontent/
403
+ * @typedef {Object} YotoCreateOrUpdateContentRequest
404
+ * @property {string} [cardId] - Card ID for updating existing content (omit for creating new)
405
+ * @property {string} title - Card title
406
+ * @property {YotoContentInput} content - Content configuration
407
+ * @property {YotoMetadataInput} [metadata] - Card metadata
408
+ */
409
+
410
+ /**
411
+ * @see https://yoto.dev/api/createorupdatecontent/
412
+ * @typedef {Object} YotoContentInput
413
+ * @property {YotoChapter[]} [chapters] - Array of chapters
414
+ * @property {YotoContentConfig} [config] - Content configuration
415
+ * @property {string} [playbackType] - Playback type (e.g., 'linear')
416
+ */
417
+
418
+ /**
419
+ * @see https://yoto.dev/api/createorupdatecontent/
420
+ * @typedef {Object} YotoMetadataInput
421
+ * @property {string} [description] - Content description
422
+ * @property {string} [title] - Metadata title
423
+ */
424
+
425
+ /**
426
+ * @see https://yoto.dev/api/createorupdatecontent/
427
+ * @typedef {Object} YotoCreateOrUpdateContentResponse
428
+ * @property {YotoCreatedCard} card
429
+ */
430
+
431
+ /**
432
+ * @see https://yoto.dev/api/createorupdatecontent/
433
+ * @typedef {Object} YotoCreatedCard
434
+ * @property {string} _id - MongoDB document ID
435
+ * @property {string} cardId - Card ID
436
+ * @property {YotoCreatedContent} content
437
+ * @property {string} createdAt
438
+ * @property {Object} metadata
439
+ * @property {string} title
440
+ * @property {string} updatedAt
441
+ * @property {string} userId
442
+ */
443
+
444
+ /**
445
+ * @see https://yoto.dev/api/createorupdatecontent/
446
+ * @typedef {Object} YotoCreatedContent
447
+ * @property {YotoChapter[]} chapters
448
+ * @property {YotoContentConfig} config
449
+ * @property {string} playbackType
450
+ */
451
+
452
+ /**
453
+ * Create new content or update existing content.
454
+ * @see https://yoto.dev/api/createorupdatecontent/
455
+ * @param {object} options
456
+ * @param {string} options.accessToken The API token to request with
457
+ * @param {YotoCreateOrUpdateContentRequest} options.content The content to create or update
458
+ * @param {string} [options.userAgent] Optional user agent string
459
+ * @param {RequestOptions} [options.requestOptions] Additional undici request options
460
+ * @return {Promise<YotoCreateOrUpdateContentResponse>} The created or updated content
461
+ */
462
+ export async function createOrUpdateContent ({
463
+ accessToken,
464
+ userAgent,
465
+ requestOptions,
466
+ content
467
+ }) {
468
+ const requestUrl = new URL('/content', YOTO_API_URL)
469
+
470
+ const response = await request(requestUrl, mergeRequestOptions({
471
+ method: 'POST',
472
+ headers: {
473
+ ...defaultAuthHeaders({ accessToken, userAgent }),
474
+ 'Content-Type': 'application/json'
475
+ },
476
+ body: JSON.stringify(content)
477
+ }, requestOptions))
478
+
479
+ await handleBadResponse(response, { cardId: content.cardId })
480
+
481
+ const responseBody = /** @type {YotoCreateOrUpdateContentResponse} */ (await response.body.json())
482
+ return responseBody
483
+ }
484
+
485
+ /**
486
+ * @see https://yoto.dev/api/deletecontent/
487
+ * @typedef {Object} YotoDeleteContentResponse
488
+ * @property {string} status - Status of the delete operation (e.g., 'ok')
489
+ */
490
+
491
+ /**
492
+ * Delete a piece of content.
493
+ * @see https://yoto.dev/api/deletecontent/
494
+ * @param {object} options
495
+ * @param {string} options.accessToken The API token to request with
496
+ * @param {string} options.cardId The card ID to delete
497
+ * @param {string} [options.userAgent] Optional user agent string
498
+ * @param {RequestOptions} [options.requestOptions] Additional undici request options
499
+ * @return {Promise<YotoDeleteContentResponse>} The delete response
500
+ */
501
+ export async function deleteContent ({
502
+ accessToken,
503
+ userAgent,
504
+ requestOptions,
505
+ cardId
506
+ }) {
507
+ const requestUrl = new URL(`/content/${cardId}`, YOTO_API_URL)
508
+
509
+ const response = await request(requestUrl, mergeRequestOptions({
510
+ method: 'DELETE',
511
+ headers: defaultAuthHeaders({ accessToken, userAgent })
512
+ }, requestOptions))
513
+
514
+ await handleBadResponse(response, { cardId })
515
+
516
+ const responseBody = /** @type {YotoDeleteContentResponse} */ (await response.body.json())
517
+ return responseBody
518
+ }