@boldvideo/bold-js 1.7.0 → 1.9.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/CHANGELOG.md +41 -0
- package/README.md +27 -1
- package/dist/index.cjs +23 -3
- package/dist/index.d.ts +71 -71
- package/dist/index.js +23 -3
- package/llms.txt +2 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# @boldvideo/bold-js
|
|
2
2
|
|
|
3
|
+
## 1.9.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- ff2f021: Transform all API responses to use camelCase (TypeScript/JavaScript convention)
|
|
8
|
+
|
|
9
|
+
**Breaking Changes:**
|
|
10
|
+
|
|
11
|
+
All response types now use camelCase consistently across the entire SDK:
|
|
12
|
+
|
|
13
|
+
- **Videos**: `playback_id` → `playbackId`, `published_at` → `publishedAt`, `stream_url` → `streamUrl`, `meta_data` → `metaData`, `captions_label` → `captionsLabel`, `download_urls` → `downloadUrls`, etc.
|
|
14
|
+
- **Playlists**: `is_private` → `isPrivate`
|
|
15
|
+
- **Settings**: `featured_playlists` → `featuredPlaylists`, `menu_items` → `menuItems`, `theme_config` → `themeConfig`, `ai_avatar` → `aiAvatar`, `logo_url` → `logoUrl`, etc.
|
|
16
|
+
- **Portal/Theme**: `show_chapters` → `showChapters`, `font_body` → `fontBody`, `color_scheme` → `colorScheme`, `css_overrides` → `cssOverrides`, etc.
|
|
17
|
+
- **AI responses**: (already camelCase in v1.8.0)
|
|
18
|
+
|
|
19
|
+
**Internal:**
|
|
20
|
+
|
|
21
|
+
- Applied `camelizeKeys()` transformation in `fetchers.ts` at the transport boundary
|
|
22
|
+
- Updated all type definitions in `types.ts` to use camelCase
|
|
23
|
+
|
|
24
|
+
## 1.8.0
|
|
25
|
+
|
|
26
|
+
### Minor Changes
|
|
27
|
+
|
|
28
|
+
- 065d7ea: Transform all API responses to use camelCase (TypeScript/JavaScript convention)
|
|
29
|
+
|
|
30
|
+
**Breaking Changes:**
|
|
31
|
+
|
|
32
|
+
- **All response types now use camelCase** (was snake_case). API responses are transformed at the SDK boundary.
|
|
33
|
+
- `video_id` → `videoId`
|
|
34
|
+
- `timestamp_end` → `timestampEnd`
|
|
35
|
+
- `playback_id` → `playbackId`
|
|
36
|
+
- `input_tokens` → `inputTokens`
|
|
37
|
+
- `conversation_id` → `conversationId`
|
|
38
|
+
|
|
39
|
+
**Internal:**
|
|
40
|
+
|
|
41
|
+
- Added `camelizeKeys` utility for deep snake_case → camelCase transformation
|
|
42
|
+
- Applied transformation in `jsonRequest()` and `parseSSE()` at the transport boundary
|
|
43
|
+
|
|
3
44
|
## 1.7.0
|
|
4
45
|
|
|
5
46
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -269,7 +269,33 @@ import type {
|
|
|
269
269
|
|
|
270
270
|
---
|
|
271
271
|
|
|
272
|
-
## Migration from v1.
|
|
272
|
+
## Migration from v1.7.x
|
|
273
|
+
|
|
274
|
+
### Breaking: All response types now use camelCase
|
|
275
|
+
|
|
276
|
+
**All API responses** (videos, playlists, settings, AI) are now transformed to use idiomatic TypeScript/JavaScript naming:
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
// Before (v1.7.x and earlier)
|
|
280
|
+
video.playback_id
|
|
281
|
+
video.published_at
|
|
282
|
+
video.stream_url
|
|
283
|
+
video.meta_data
|
|
284
|
+
settings.featured_playlists
|
|
285
|
+
settings.menu_items
|
|
286
|
+
settings.theme_config
|
|
287
|
+
playlist.is_private
|
|
288
|
+
|
|
289
|
+
// After (v1.8.0)
|
|
290
|
+
video.playbackId
|
|
291
|
+
video.publishedAt
|
|
292
|
+
video.streamUrl
|
|
293
|
+
video.metaData
|
|
294
|
+
settings.featuredPlaylists
|
|
295
|
+
settings.menuItems
|
|
296
|
+
settings.themeConfig
|
|
297
|
+
playlist.isPrivate
|
|
298
|
+
```
|
|
273
299
|
|
|
274
300
|
### Method Changes
|
|
275
301
|
|
package/dist/index.cjs
CHANGED
|
@@ -39,6 +39,24 @@ module.exports = __toCommonJS(src_exports);
|
|
|
39
39
|
// src/lib/client.ts
|
|
40
40
|
var import_axios = __toESM(require("axios"), 1);
|
|
41
41
|
|
|
42
|
+
// src/util/camelize.ts
|
|
43
|
+
var isPlainObject = (value) => value !== null && typeof value === "object" && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
|
|
44
|
+
var snakeToCamel = (key) => key.replace(/_([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
45
|
+
function camelizeKeys(input) {
|
|
46
|
+
if (Array.isArray(input)) {
|
|
47
|
+
return input.map((item) => camelizeKeys(item));
|
|
48
|
+
}
|
|
49
|
+
if (!isPlainObject(input)) {
|
|
50
|
+
return input;
|
|
51
|
+
}
|
|
52
|
+
const out = {};
|
|
53
|
+
for (const [rawKey, value] of Object.entries(input)) {
|
|
54
|
+
const key = snakeToCamel(rawKey);
|
|
55
|
+
out[key] = camelizeKeys(value);
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
|
|
42
60
|
// src/lib/fetchers.ts
|
|
43
61
|
async function get(client, url) {
|
|
44
62
|
try {
|
|
@@ -46,7 +64,7 @@ async function get(client, url) {
|
|
|
46
64
|
if (res.status !== 200) {
|
|
47
65
|
throw new Error(`Unexpected response status: ${res.status}`);
|
|
48
66
|
}
|
|
49
|
-
return res.data;
|
|
67
|
+
return camelizeKeys(res.data);
|
|
50
68
|
} catch (error) {
|
|
51
69
|
console.error(`Error fetching data from URL: ${url}`, error);
|
|
52
70
|
throw error;
|
|
@@ -248,7 +266,8 @@ async function* parseSSE(response) {
|
|
|
248
266
|
if (!json)
|
|
249
267
|
continue;
|
|
250
268
|
try {
|
|
251
|
-
const
|
|
269
|
+
const raw = JSON.parse(json);
|
|
270
|
+
const event = camelizeKeys(raw);
|
|
252
271
|
yield event;
|
|
253
272
|
if (event.type === "message_complete" || event.type === "error") {
|
|
254
273
|
await reader.cancel();
|
|
@@ -298,7 +317,8 @@ async function jsonRequest(path, body, config) {
|
|
|
298
317
|
if (!response.ok) {
|
|
299
318
|
throw new Error(`AI request failed: ${response.status} ${response.statusText}`);
|
|
300
319
|
}
|
|
301
|
-
|
|
320
|
+
const raw = await response.json();
|
|
321
|
+
return camelizeKeys(raw);
|
|
302
322
|
}
|
|
303
323
|
function createAI(config) {
|
|
304
324
|
async function chat(options) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
type VideoAttachment = {
|
|
2
2
|
id: string;
|
|
3
3
|
title: string;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
fileUrl: string;
|
|
5
|
+
fileSize?: number;
|
|
6
|
+
fileType?: string;
|
|
7
7
|
};
|
|
8
8
|
type VideoDownloadUrls = {
|
|
9
9
|
mp4?: string;
|
|
10
10
|
audio?: string;
|
|
11
|
-
|
|
11
|
+
legacyMp4?: string;
|
|
12
12
|
};
|
|
13
13
|
type VideoSubtitles = {
|
|
14
14
|
label: string;
|
|
@@ -27,27 +27,27 @@ type VideoMetadata = {
|
|
|
27
27
|
};
|
|
28
28
|
type Video = {
|
|
29
29
|
captions: string;
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
captionsLabel: string;
|
|
31
|
+
captionsLang: string;
|
|
32
32
|
description: string | null;
|
|
33
33
|
duration: number;
|
|
34
34
|
id: string;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
importedFrom: string | null;
|
|
36
|
+
legacyVideoUrl: string | null;
|
|
37
|
+
playbackId: string;
|
|
38
|
+
publishedAt: string;
|
|
39
|
+
streamUrl: string;
|
|
40
40
|
teaser: string | null;
|
|
41
41
|
thumbnail: string;
|
|
42
42
|
title: string;
|
|
43
43
|
type: string;
|
|
44
|
-
|
|
44
|
+
metaData: VideoMetadata;
|
|
45
45
|
chapters?: string;
|
|
46
46
|
attachments?: VideoAttachment[];
|
|
47
47
|
cta?: any | null;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
downloadUrls?: VideoDownloadUrls;
|
|
49
|
+
internalId: string;
|
|
50
|
+
playbackSpeed?: number;
|
|
51
51
|
subtitles?: VideoSubtitles;
|
|
52
52
|
tags?: string[];
|
|
53
53
|
transcript?: VideoTranscript;
|
|
@@ -55,20 +55,20 @@ type Video = {
|
|
|
55
55
|
type Playlist = {
|
|
56
56
|
description?: string;
|
|
57
57
|
id: string;
|
|
58
|
-
|
|
58
|
+
isPrivate: boolean;
|
|
59
59
|
title: string;
|
|
60
60
|
type: string;
|
|
61
61
|
videos: Video[];
|
|
62
62
|
};
|
|
63
63
|
type MenuItem = {
|
|
64
64
|
icon: string;
|
|
65
|
-
|
|
65
|
+
isExt: boolean;
|
|
66
66
|
label: string;
|
|
67
67
|
url: string;
|
|
68
68
|
};
|
|
69
69
|
type PortalDisplay = {
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
showChapters: boolean;
|
|
71
|
+
showTranscripts: boolean;
|
|
72
72
|
};
|
|
73
73
|
type AssistantConfig = {
|
|
74
74
|
headline: string;
|
|
@@ -76,36 +76,36 @@ type AssistantConfig = {
|
|
|
76
76
|
suggestions: string[];
|
|
77
77
|
};
|
|
78
78
|
type PortalLayout = {
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
assistantConfig: AssistantConfig | null;
|
|
80
|
+
showPlaylists: boolean;
|
|
81
81
|
type: string;
|
|
82
|
-
|
|
82
|
+
videosLimit: number;
|
|
83
83
|
};
|
|
84
84
|
type PortalNavigation = {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
showAiSearch: boolean;
|
|
86
|
+
showHeader: boolean;
|
|
87
|
+
showSearch: boolean;
|
|
88
88
|
};
|
|
89
89
|
type PortalTheme = {
|
|
90
90
|
background: string;
|
|
91
91
|
foreground: string;
|
|
92
92
|
primary: string;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
93
|
+
fontBody: string;
|
|
94
|
+
fontHeader: string;
|
|
95
|
+
logoUrl: string;
|
|
96
|
+
logoDarkUrl: string;
|
|
97
|
+
logoWidth: number;
|
|
98
|
+
logoHeight: number;
|
|
99
|
+
headerSize: string;
|
|
100
100
|
layout: string;
|
|
101
101
|
radius: string;
|
|
102
|
-
|
|
102
|
+
colorScheme: "toggle" | "light" | "dark";
|
|
103
103
|
light: ThemeColors;
|
|
104
104
|
dark: ThemeColors;
|
|
105
|
-
|
|
105
|
+
cssOverrides: string | null;
|
|
106
106
|
};
|
|
107
107
|
type Portal = {
|
|
108
|
-
|
|
108
|
+
colorScheme?: 'toggle' | 'light' | 'dark';
|
|
109
109
|
display: PortalDisplay;
|
|
110
110
|
layout: PortalLayout;
|
|
111
111
|
navigation: PortalNavigation;
|
|
@@ -115,21 +115,21 @@ type ThemeColors = {
|
|
|
115
115
|
accent: string;
|
|
116
116
|
background: string;
|
|
117
117
|
foreground: string;
|
|
118
|
-
|
|
118
|
+
accentForeground: string;
|
|
119
119
|
muted: string;
|
|
120
|
-
|
|
120
|
+
mutedForeground: string;
|
|
121
121
|
border: string;
|
|
122
122
|
ring: string;
|
|
123
123
|
surface: string;
|
|
124
124
|
};
|
|
125
125
|
type ThemeConfig = {
|
|
126
126
|
radius: string;
|
|
127
|
-
|
|
127
|
+
colorScheme: "toggle" | "light" | "dark";
|
|
128
128
|
light: ThemeColors;
|
|
129
129
|
dark: ThemeColors;
|
|
130
130
|
};
|
|
131
131
|
type AccountAI = {
|
|
132
|
-
|
|
132
|
+
avatarUrl: string;
|
|
133
133
|
enabled: boolean;
|
|
134
134
|
greeting: string;
|
|
135
135
|
name: string;
|
|
@@ -141,7 +141,7 @@ type PersonaEnabled = {
|
|
|
141
141
|
enabled: true;
|
|
142
142
|
name: string;
|
|
143
143
|
greeting: string;
|
|
144
|
-
|
|
144
|
+
conversationStarters: string[];
|
|
145
145
|
};
|
|
146
146
|
type PersonaDisabled = {
|
|
147
147
|
enabled: false;
|
|
@@ -149,33 +149,33 @@ type PersonaDisabled = {
|
|
|
149
149
|
type Persona = PersonaEnabled | PersonaDisabled;
|
|
150
150
|
type Account = {
|
|
151
151
|
ai: AccountAI;
|
|
152
|
-
|
|
152
|
+
aiSearch: AccountAISearch;
|
|
153
153
|
name: string;
|
|
154
154
|
persona: Persona;
|
|
155
155
|
slug: string;
|
|
156
156
|
};
|
|
157
157
|
type Settings = {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
158
|
+
featuredPlaylists: Playlist[];
|
|
159
|
+
menuItems: MenuItem[];
|
|
160
|
+
aiAvatar: string;
|
|
161
|
+
aiName: string;
|
|
162
|
+
aiGreeting?: string;
|
|
163
|
+
hasAi: boolean;
|
|
164
164
|
account: Account;
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
165
|
+
faviconUrl?: string;
|
|
166
|
+
logoDarkUrl?: string;
|
|
167
|
+
logoUrl?: string;
|
|
168
|
+
metaData: {
|
|
169
|
+
channelName: string;
|
|
170
170
|
description: string;
|
|
171
171
|
image: string | null;
|
|
172
|
-
|
|
173
|
-
|
|
172
|
+
noSeo: boolean;
|
|
173
|
+
socialGraphImageUrl?: string;
|
|
174
174
|
title: string;
|
|
175
|
-
|
|
175
|
+
titleSuffix: string;
|
|
176
176
|
};
|
|
177
177
|
portal: Portal;
|
|
178
|
-
|
|
178
|
+
themeConfig: ThemeConfig;
|
|
179
179
|
version: string;
|
|
180
180
|
};
|
|
181
181
|
/**
|
|
@@ -183,29 +183,28 @@ type Settings = {
|
|
|
183
183
|
*/
|
|
184
184
|
interface Source {
|
|
185
185
|
id: string;
|
|
186
|
-
|
|
186
|
+
videoId: string;
|
|
187
187
|
title: string;
|
|
188
188
|
text: string;
|
|
189
189
|
timestamp: number;
|
|
190
|
-
|
|
191
|
-
|
|
190
|
+
timestampEnd: number;
|
|
191
|
+
playbackId: string;
|
|
192
192
|
speaker?: string;
|
|
193
193
|
}
|
|
194
194
|
/**
|
|
195
195
|
* Token usage statistics
|
|
196
196
|
*/
|
|
197
197
|
interface AIUsage {
|
|
198
|
-
|
|
199
|
-
|
|
198
|
+
inputTokens: number;
|
|
199
|
+
outputTokens: number;
|
|
200
200
|
}
|
|
201
201
|
/**
|
|
202
202
|
* SSE event types for AI streaming responses
|
|
203
203
|
*/
|
|
204
204
|
type AIEvent = {
|
|
205
205
|
type: "message_start";
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
video_id?: string;
|
|
206
|
+
conversationId?: string;
|
|
207
|
+
videoId?: string;
|
|
209
208
|
} | {
|
|
210
209
|
type: "sources";
|
|
211
210
|
sources: Source[];
|
|
@@ -221,8 +220,7 @@ type AIEvent = {
|
|
|
221
220
|
recommendations: Recommendation[];
|
|
222
221
|
} | {
|
|
223
222
|
type: "message_complete";
|
|
224
|
-
|
|
225
|
-
conversation_id?: string;
|
|
223
|
+
conversationId?: string;
|
|
226
224
|
content: string;
|
|
227
225
|
sources: Source[];
|
|
228
226
|
usage?: AIUsage;
|
|
@@ -236,10 +234,13 @@ type AIEvent = {
|
|
|
236
234
|
retryable: boolean;
|
|
237
235
|
};
|
|
238
236
|
/**
|
|
239
|
-
* Non-streaming AI response
|
|
237
|
+
* Non-streaming AI response for /ai/chat, /ai/videos/:id/chat, and /ai/search
|
|
240
238
|
*/
|
|
241
239
|
interface AIResponse {
|
|
242
|
-
|
|
240
|
+
conversationId?: string;
|
|
241
|
+
videoId?: string;
|
|
242
|
+
/** @deprecated Use conversationId instead. Will be removed in v2. */
|
|
243
|
+
id?: string;
|
|
243
244
|
content: string;
|
|
244
245
|
sources: Source[];
|
|
245
246
|
usage: AIUsage;
|
|
@@ -295,9 +296,9 @@ interface SearchOptions {
|
|
|
295
296
|
* A recommended video with relevance score
|
|
296
297
|
*/
|
|
297
298
|
interface RecommendationVideo {
|
|
298
|
-
|
|
299
|
+
videoId: string;
|
|
299
300
|
title: string;
|
|
300
|
-
|
|
301
|
+
playbackId: string;
|
|
301
302
|
relevance: number;
|
|
302
303
|
reason: string;
|
|
303
304
|
}
|
|
@@ -328,7 +329,6 @@ type RecommendOptions = RecommendationsOptions;
|
|
|
328
329
|
* Non-streaming response for recommendations endpoint
|
|
329
330
|
*/
|
|
330
331
|
interface RecommendationsResponse {
|
|
331
|
-
id: string;
|
|
332
332
|
recommendations: Recommendation[];
|
|
333
333
|
guidance: string;
|
|
334
334
|
sources: Source[];
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
// src/lib/client.ts
|
|
2
2
|
import axios from "axios";
|
|
3
3
|
|
|
4
|
+
// src/util/camelize.ts
|
|
5
|
+
var isPlainObject = (value) => value !== null && typeof value === "object" && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
|
|
6
|
+
var snakeToCamel = (key) => key.replace(/_([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
7
|
+
function camelizeKeys(input) {
|
|
8
|
+
if (Array.isArray(input)) {
|
|
9
|
+
return input.map((item) => camelizeKeys(item));
|
|
10
|
+
}
|
|
11
|
+
if (!isPlainObject(input)) {
|
|
12
|
+
return input;
|
|
13
|
+
}
|
|
14
|
+
const out = {};
|
|
15
|
+
for (const [rawKey, value] of Object.entries(input)) {
|
|
16
|
+
const key = snakeToCamel(rawKey);
|
|
17
|
+
out[key] = camelizeKeys(value);
|
|
18
|
+
}
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
21
|
+
|
|
4
22
|
// src/lib/fetchers.ts
|
|
5
23
|
async function get(client, url) {
|
|
6
24
|
try {
|
|
@@ -8,7 +26,7 @@ async function get(client, url) {
|
|
|
8
26
|
if (res.status !== 200) {
|
|
9
27
|
throw new Error(`Unexpected response status: ${res.status}`);
|
|
10
28
|
}
|
|
11
|
-
return res.data;
|
|
29
|
+
return camelizeKeys(res.data);
|
|
12
30
|
} catch (error) {
|
|
13
31
|
console.error(`Error fetching data from URL: ${url}`, error);
|
|
14
32
|
throw error;
|
|
@@ -210,7 +228,8 @@ async function* parseSSE(response) {
|
|
|
210
228
|
if (!json)
|
|
211
229
|
continue;
|
|
212
230
|
try {
|
|
213
|
-
const
|
|
231
|
+
const raw = JSON.parse(json);
|
|
232
|
+
const event = camelizeKeys(raw);
|
|
214
233
|
yield event;
|
|
215
234
|
if (event.type === "message_complete" || event.type === "error") {
|
|
216
235
|
await reader.cancel();
|
|
@@ -260,7 +279,8 @@ async function jsonRequest(path, body, config) {
|
|
|
260
279
|
if (!response.ok) {
|
|
261
280
|
throw new Error(`AI request failed: ${response.status} ${response.statusText}`);
|
|
262
281
|
}
|
|
263
|
-
|
|
282
|
+
const raw = await response.json();
|
|
283
|
+
return camelizeKeys(raw);
|
|
264
284
|
}
|
|
265
285
|
function createAI(config) {
|
|
266
286
|
async function chat(options) {
|
package/llms.txt
CHANGED
|
@@ -110,6 +110,8 @@ Key types exported:
|
|
|
110
110
|
- `ChatOptions`, `SearchOptions`
|
|
111
111
|
- `RecommendationsOptions`, `RecommendationsResponse`, `Recommendation`, `RecommendationVideo`
|
|
112
112
|
|
|
113
|
+
All response types use camelCase (e.g., `video.playbackId`, `video.publishedAt`, `settings.featuredPlaylists`, `source.videoId`).
|
|
114
|
+
|
|
113
115
|
## Links
|
|
114
116
|
|
|
115
117
|
- [GitHub](https://github.com/boldvideo/bold-js)
|