@freqhole/api-client 0.2.15 → 0.3.2

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.
@@ -7,9 +7,11 @@ import {
7
7
  ImageUploadResponseSchema,
8
8
  MusicImportResponseSchema,
9
9
  MusicUploadResponseSchema,
10
+ VideoUploadResponseSchema,
10
11
  } from "../codegen/schema.js";
11
12
  import type { Transport } from "../transport.js";
12
13
  import type { SafeParseResult } from "./types.js";
14
+ import { toZodError } from "../errors.js";
13
15
 
14
16
  // helper to parse response and validate with schema
15
17
  function parseResponse<T>(
@@ -18,21 +20,7 @@ function parseResponse<T>(
18
20
  schema: z.ZodType<T>,
19
21
  ): SafeParseResult<T> {
20
22
  if (status >= 400) {
21
- let errorMessage = `HTTP ${status}`;
22
- try {
23
- const errorBody = JSON.parse(responseBody);
24
- if (errorBody?.error) {
25
- errorMessage = `HTTP ${status}: ${errorBody.error}`;
26
- }
27
- } catch {
28
- // body wasn't JSON
29
- }
30
- return {
31
- success: false,
32
- error: new z.ZodError([
33
- { code: "custom", path: [], message: errorMessage },
34
- ]),
35
- };
23
+ return { success: false, error: toZodError(responseBody, status) };
36
24
  }
37
25
 
38
26
  try {
@@ -68,19 +56,17 @@ export function createUploadMethods(transport: Transport) {
68
56
  /**
69
57
  * upload a music file
70
58
  * returns job information for async processing
59
+ * @param onProgress - optional callback with (loaded, total) bytes sent so far
71
60
  */
72
61
  music: async (
73
62
  file: File | Blob,
63
+ onProgress?: (loaded: number, total: number) => void,
74
64
  ): Promise<SafeParseResult<s.MusicUploadResponse>> => {
75
65
  const formData = new FormData();
76
66
  formData.append("file", file);
77
67
 
78
- const response = await transport.upload("/api/upload/music", formData);
79
- return parseResponse(
80
- response.body,
81
- response.status,
82
- MusicUploadResponseSchema,
83
- );
68
+ const response = await transport.upload("/api/upload/music", formData, onProgress);
69
+ return parseResponse(response.body, response.status, MusicUploadResponseSchema);
84
70
  },
85
71
 
86
72
  /**
@@ -89,9 +75,7 @@ export function createUploadMethods(transport: Transport) {
89
75
  * peer to pull it via verified streaming.
90
76
  * requires transport.uploadByPath to be implemented (CharnelTransport).
91
77
  */
92
- musicByPath: async (
93
- filePath: string,
94
- ): Promise<SafeParseResult<s.MusicUploadResponse>> => {
78
+ musicByPath: async (filePath: string): Promise<SafeParseResult<s.MusicUploadResponse>> => {
95
79
  if (!transport.uploadByPath) {
96
80
  return {
97
81
  success: false,
@@ -105,15 +89,48 @@ export function createUploadMethods(transport: Transport) {
105
89
  };
106
90
  }
107
91
 
108
- const response = await transport.uploadByPath(
109
- "/api/upload/music",
110
- filePath,
111
- );
112
- return parseResponse(
113
- response.body,
114
- response.status,
115
- MusicUploadResponseSchema,
116
- );
92
+ const response = await transport.uploadByPath("/api/upload/music", filePath);
93
+ return parseResponse(response.body, response.status, MusicUploadResponseSchema);
94
+ },
95
+
96
+ /**
97
+ * upload a video file
98
+ * returns job information for async processing
99
+ * @param onProgress - optional callback with (loaded, total) bytes sent so far
100
+ */
101
+ video: async (
102
+ file: File | Blob,
103
+ onProgress?: (loaded: number, total: number) => void,
104
+ ): Promise<SafeParseResult<s.VideoUploadResponse>> => {
105
+ const formData = new FormData();
106
+ formData.append("file", file);
107
+
108
+ const response = await transport.upload("/api/upload/video", formData, onProgress);
109
+ return parseResponse(response.body, response.status, VideoUploadResponseSchema);
110
+ },
111
+
112
+ /**
113
+ * upload a video file by filesystem path (tauri P2P only).
114
+ * imports the file into the local iroh-blobs store, then tells the remote
115
+ * peer to pull it via verified streaming.
116
+ * requires transport.uploadByPath to be implemented (CharnelTransport).
117
+ */
118
+ videoByPath: async (filePath: string): Promise<SafeParseResult<s.VideoUploadResponse>> => {
119
+ if (!transport.uploadByPath) {
120
+ return {
121
+ success: false,
122
+ error: new z.ZodError([
123
+ {
124
+ code: "custom",
125
+ path: [],
126
+ message: "uploadByPath not supported by this transport",
127
+ },
128
+ ]),
129
+ };
130
+ }
131
+
132
+ const response = await transport.uploadByPath("/api/upload/video", filePath);
133
+ return parseResponse(response.body, response.status, VideoUploadResponseSchema);
117
134
  },
118
135
 
119
136
  /**
@@ -132,11 +149,7 @@ export function createUploadMethods(transport: Transport) {
132
149
  }
133
150
 
134
151
  const response = await transport.upload("/api/upload/image", formData);
135
- return parseResponse(
136
- response.body,
137
- response.status,
138
- ImageUploadResponseSchema,
139
- );
152
+ return parseResponse(response.body, response.status, ImageUploadResponseSchema);
140
153
  },
141
154
 
142
155
  /**
@@ -166,16 +179,8 @@ export function createUploadMethods(transport: Transport) {
166
179
  metadata.associate_with = options.associate;
167
180
  }
168
181
 
169
- const response = await transport.uploadByPath(
170
- "/api/upload/image",
171
- filePath,
172
- metadata,
173
- );
174
- return parseResponse(
175
- response.body,
176
- response.status,
177
- ImageUploadResponseSchema,
178
- );
182
+ const response = await transport.uploadByPath("/api/upload/image", filePath, metadata);
183
+ return parseResponse(response.body, response.status, ImageUploadResponseSchema);
179
184
  },
180
185
 
181
186
  /**
@@ -202,11 +207,7 @@ export function createUploadMethods(transport: Transport) {
202
207
  "/api/upload/music-paths",
203
208
  JSON.stringify(body),
204
209
  );
205
- return parseResponse(
206
- response.body,
207
- response.status,
208
- MusicImportResponseSchema,
209
- );
210
+ return parseResponse(response.body, response.status, MusicImportResponseSchema);
210
211
  },
211
212
  };
212
213
  }
@@ -0,0 +1,463 @@
1
+ // video domain methods for FreqholeClient
2
+
3
+ import { routes } from "../codegen/routes.js";
4
+ import type * as s from "../codegen/schema.js";
5
+ import {
6
+ UpdateVideoSeasonRequestSchema,
7
+ UpdateVideoSeriesRequestSchema,
8
+ UpdateVideosRequestSchema,
9
+ } from "../codegen/schema.js";
10
+ import type { CallFn } from "./types.js";
11
+
12
+ // partial schemas for update operations
13
+ const UpdateVideosRequestPartialSchema = UpdateVideosRequestSchema.partial().required({
14
+ video_ids: true,
15
+ });
16
+ const UpdateVideoSeriesRequestPartialSchema = UpdateVideoSeriesRequestSchema.partial().required({
17
+ series_id: true,
18
+ });
19
+ const UpdateVideoSeasonRequestPartialSchema = UpdateVideoSeasonRequestSchema.partial().required({
20
+ season_id: true,
21
+ });
22
+
23
+ export function createVideoMethods(call: CallFn) {
24
+ return {
25
+ // videos
26
+ queryVideos: (params: s.QueryVideosRequest) => {
27
+ return call(
28
+ "video",
29
+ "query_videos",
30
+ routes.video.query_videos.resp,
31
+ routes.video.query_videos.req,
32
+ routes.video.query_videos.method,
33
+ routes.video.query_videos.path,
34
+ params,
35
+ );
36
+ },
37
+
38
+ createVideo: (params: s.CreateVideoRequest) => {
39
+ return call(
40
+ "video",
41
+ "create_video",
42
+ routes.video.create_video.resp,
43
+ routes.video.create_video.req,
44
+ routes.video.create_video.method,
45
+ routes.video.create_video.path,
46
+ params,
47
+ );
48
+ },
49
+
50
+ getVideo: (params: s.GetVideoRequest) => {
51
+ return call(
52
+ "video",
53
+ "get_video",
54
+ routes.video.get_video.resp,
55
+ routes.video.get_video.req,
56
+ routes.video.get_video.method,
57
+ routes.video.get_video.path,
58
+ params,
59
+ );
60
+ },
61
+
62
+ getVideoWithMetadata: (params: s.GetVideoRequest) => {
63
+ return call(
64
+ "video",
65
+ "get_video_with_metadata",
66
+ routes.video.get_video_with_metadata.resp,
67
+ routes.video.get_video_with_metadata.req,
68
+ routes.video.get_video_with_metadata.method,
69
+ routes.video.get_video_with_metadata.path,
70
+ params,
71
+ );
72
+ },
73
+
74
+ listVideosBySeries: (params: s.ListVideosBySeriesRequest) => {
75
+ return call(
76
+ "video",
77
+ "list_videos_by_series",
78
+ routes.video.list_videos_by_series.resp,
79
+ routes.video.list_videos_by_series.req,
80
+ routes.video.list_videos_by_series.method,
81
+ routes.video.list_videos_by_series.path,
82
+ params,
83
+ );
84
+ },
85
+
86
+ listVideosBySeason: (params: s.ListVideosBySeasonRequest) => {
87
+ return call(
88
+ "video",
89
+ "list_videos_by_season",
90
+ routes.video.list_videos_by_season.resp,
91
+ routes.video.list_videos_by_season.req,
92
+ routes.video.list_videos_by_season.method,
93
+ routes.video.list_videos_by_season.path,
94
+ params,
95
+ );
96
+ },
97
+
98
+ listVideosUnattached: (params: s.ListVideosUnattachedRequest) => {
99
+ return call(
100
+ "video",
101
+ "list_videos_unattached",
102
+ routes.video.list_videos_unattached.resp,
103
+ routes.video.list_videos_unattached.req,
104
+ routes.video.list_videos_unattached.method,
105
+ routes.video.list_videos_unattached.path,
106
+ params,
107
+ );
108
+ },
109
+
110
+ updateVideos: (params: Partial<s.UpdateVideosRequest> & { video_ids: string[] }) => {
111
+ return call(
112
+ "video",
113
+ "update_videos",
114
+ routes.video.update_videos.resp,
115
+ UpdateVideosRequestPartialSchema,
116
+ routes.video.update_videos.method,
117
+ routes.video.update_videos.path,
118
+ params,
119
+ );
120
+ },
121
+
122
+ deleteVideo: (params: s.DeleteVideoRequest) => {
123
+ return call(
124
+ "video",
125
+ "delete_video",
126
+ routes.video.delete_video.resp,
127
+ routes.video.delete_video.req,
128
+ routes.video.delete_video.method,
129
+ routes.video.delete_video.path,
130
+ params,
131
+ );
132
+ },
133
+
134
+ bulkDeleteVideos: (params: s.BulkDeleteVideosRequest) => {
135
+ return call(
136
+ "video",
137
+ "bulk_delete_videos",
138
+ routes.video.bulk_delete_videos.resp,
139
+ routes.video.bulk_delete_videos.req,
140
+ routes.video.bulk_delete_videos.method,
141
+ routes.video.bulk_delete_videos.path,
142
+ params,
143
+ );
144
+ },
145
+
146
+ getVideoRenditions: (params: s.GetVideoRenditionsRequest) => {
147
+ return call(
148
+ "video",
149
+ "get_video_renditions",
150
+ routes.video.get_video_renditions.resp,
151
+ routes.video.get_video_renditions.req,
152
+ routes.video.get_video_renditions.method,
153
+ routes.video.get_video_renditions.path,
154
+ params,
155
+ );
156
+ },
157
+
158
+ deleteVideoRendition: (params: s.DeleteVideoRenditionRequest) => {
159
+ return call(
160
+ "video",
161
+ "delete_video_rendition",
162
+ routes.video.delete_video_rendition.resp,
163
+ routes.video.delete_video_rendition.req,
164
+ routes.video.delete_video_rendition.method,
165
+ routes.video.delete_video_rendition.path,
166
+ params,
167
+ );
168
+ },
169
+
170
+ // playback progress
171
+ upsertPlaybackProgress: (params: s.UpsertPlaybackProgressRequest) => {
172
+ return call(
173
+ "video",
174
+ "upsert_playback_progress",
175
+ routes.video.upsert_playback_progress.resp,
176
+ routes.video.upsert_playback_progress.req,
177
+ routes.video.upsert_playback_progress.method,
178
+ routes.video.upsert_playback_progress.path,
179
+ params,
180
+ );
181
+ },
182
+
183
+ getPlaybackProgress: (params: s.GetPlaybackProgressRequest) => {
184
+ return call(
185
+ "video",
186
+ "get_playback_progress",
187
+ routes.video.get_playback_progress.resp,
188
+ routes.video.get_playback_progress.req,
189
+ routes.video.get_playback_progress.method,
190
+ routes.video.get_playback_progress.path,
191
+ params,
192
+ );
193
+ },
194
+
195
+ listPlaybackProgress: (params: s.ListPlaybackProgressRequest) => {
196
+ return call(
197
+ "video",
198
+ "list_playback_progress",
199
+ routes.video.list_playback_progress.resp,
200
+ routes.video.list_playback_progress.req,
201
+ routes.video.list_playback_progress.method,
202
+ routes.video.list_playback_progress.path,
203
+ params,
204
+ );
205
+ },
206
+
207
+ // universal-domain relation hubs (era/recently_added/unassigned +
208
+ // generic categorical hubs like genre/mood/style), mirrors music's
209
+ // recentlyAddedAlbums/albumsByValue/unassignedAlbums.
210
+
211
+ // top-N most recently added videos, flat (not clustered by series)
212
+ // by created_at desc.
213
+ recentlyAddedVideos: (params: s.RecentlyAddedVideosRequest) => {
214
+ return call(
215
+ "video",
216
+ "recently_added_videos",
217
+ routes.video.recently_added_videos.resp,
218
+ routes.video.recently_added_videos.req,
219
+ routes.video.recently_added_videos.method,
220
+ routes.video.recently_added_videos.path,
221
+ params,
222
+ );
223
+ },
224
+
225
+ // fan out the synthesized "unassigned" hub to its member videos
226
+ // (videos with no entity_taxonz rows at all).
227
+ unassignedVideos: (params: s.UnassignedVideosRequest) => {
228
+ return call(
229
+ "video",
230
+ "unassigned_videos",
231
+ routes.video.unassigned_videos.resp,
232
+ routes.video.unassigned_videos.req,
233
+ routes.video.unassigned_videos.method,
234
+ routes.video.unassigned_videos.path,
235
+ params,
236
+ );
237
+ },
238
+
239
+ // fetch a (taxon_kind, taxon_value) hub's full video member set -
240
+ // used when the graph drills into a generic categorical relation
241
+ // hub (genre/mood/style/label/tag) with the video domain active.
242
+ videosByValue: (params: s.VideosByValueRequest) => {
243
+ return call(
244
+ "video",
245
+ "videos_by_value",
246
+ routes.video.videos_by_value.resp,
247
+ routes.video.videos_by_value.req,
248
+ routes.video.videos_by_value.method,
249
+ routes.video.videos_by_value.path,
250
+ params,
251
+ );
252
+ },
253
+
254
+ // video series
255
+ queryVideoSeries: (params: s.QueryParams) => {
256
+ return call(
257
+ "video",
258
+ "query_video_series",
259
+ routes.video.query_video_series.resp,
260
+ routes.video.query_video_series.req,
261
+ routes.video.query_video_series.method,
262
+ routes.video.query_video_series.path,
263
+ params,
264
+ );
265
+ },
266
+
267
+ createVideoSeries: (params: s.CreateVideoSeriesRequest) => {
268
+ return call(
269
+ "video",
270
+ "create_video_series",
271
+ routes.video.create_video_series.resp,
272
+ routes.video.create_video_series.req,
273
+ routes.video.create_video_series.method,
274
+ routes.video.create_video_series.path,
275
+ params,
276
+ );
277
+ },
278
+
279
+ listVideoSeries: (params: s.ListVideoSeriesRequest) => {
280
+ return call(
281
+ "video",
282
+ "list_video_series",
283
+ routes.video.list_video_series.resp,
284
+ routes.video.list_video_series.req,
285
+ routes.video.list_video_series.method,
286
+ routes.video.list_video_series.path,
287
+ params,
288
+ );
289
+ },
290
+
291
+ getVideoSeries: (params: s.GetVideoSeriesRequest) => {
292
+ return call(
293
+ "video",
294
+ "get_video_series",
295
+ routes.video.get_video_series.resp,
296
+ routes.video.get_video_series.req,
297
+ routes.video.get_video_series.method,
298
+ routes.video.get_video_series.path,
299
+ params,
300
+ );
301
+ },
302
+
303
+ getVideoSeriesDetail: (params: s.GetVideoSeriesRequest) => {
304
+ return call(
305
+ "video",
306
+ "get_video_series_detail",
307
+ routes.video.get_video_series_detail.resp,
308
+ routes.video.get_video_series_detail.req,
309
+ routes.video.get_video_series_detail.method,
310
+ routes.video.get_video_series_detail.path,
311
+ params,
312
+ );
313
+ },
314
+
315
+ updateVideoSeries: (params: Partial<s.UpdateVideoSeriesRequest> & { series_id: string }) => {
316
+ return call(
317
+ "video",
318
+ "update_video_series",
319
+ routes.video.update_video_series.resp,
320
+ UpdateVideoSeriesRequestPartialSchema,
321
+ routes.video.update_video_series.method,
322
+ routes.video.update_video_series.path,
323
+ params,
324
+ );
325
+ },
326
+
327
+ deleteVideoSeries: (params: s.DeleteVideoSeriesRequest) => {
328
+ return call(
329
+ "video",
330
+ "delete_video_series",
331
+ routes.video.delete_video_series.resp,
332
+ routes.video.delete_video_series.req,
333
+ routes.video.delete_video_series.method,
334
+ routes.video.delete_video_series.path,
335
+ params,
336
+ );
337
+ },
338
+
339
+ // video seasons
340
+ createVideoSeason: (params: s.CreateVideoSeasonRequest) => {
341
+ return call(
342
+ "video",
343
+ "create_video_season",
344
+ routes.video.create_video_season.resp,
345
+ routes.video.create_video_season.req,
346
+ routes.video.create_video_season.method,
347
+ routes.video.create_video_season.path,
348
+ params,
349
+ );
350
+ },
351
+
352
+ listVideoSeasons: (params: s.ListVideoSeasonsRequest) => {
353
+ return call(
354
+ "video",
355
+ "list_video_seasons",
356
+ routes.video.list_video_seasons.resp,
357
+ routes.video.list_video_seasons.req,
358
+ routes.video.list_video_seasons.method,
359
+ routes.video.list_video_seasons.path,
360
+ params,
361
+ );
362
+ },
363
+
364
+ getVideoSeason: (params: s.GetVideoSeasonRequest) => {
365
+ return call(
366
+ "video",
367
+ "get_video_season",
368
+ routes.video.get_video_season.resp,
369
+ routes.video.get_video_season.req,
370
+ routes.video.get_video_season.method,
371
+ routes.video.get_video_season.path,
372
+ params,
373
+ );
374
+ },
375
+
376
+ updateVideoSeason: (params: Partial<s.UpdateVideoSeasonRequest> & { season_id: string }) => {
377
+ return call(
378
+ "video",
379
+ "update_video_season",
380
+ routes.video.update_video_season.resp,
381
+ UpdateVideoSeasonRequestPartialSchema,
382
+ routes.video.update_video_season.method,
383
+ routes.video.update_video_season.path,
384
+ params,
385
+ );
386
+ },
387
+
388
+ deleteVideoSeason: (params: s.DeleteVideoSeasonRequest) => {
389
+ return call(
390
+ "video",
391
+ "delete_video_season",
392
+ routes.video.delete_video_season.resp,
393
+ routes.video.delete_video_season.req,
394
+ routes.video.delete_video_season.method,
395
+ routes.video.delete_video_season.path,
396
+ params,
397
+ );
398
+ },
399
+
400
+ // import review (grouped by detected series)
401
+ listPendingVideoImportReview: (params: s.ListPendingVideoReviewRequest) => {
402
+ return call(
403
+ "video",
404
+ "list_pending_video_import_review",
405
+ routes.video.list_pending_video_import_review.resp,
406
+ routes.video.list_pending_video_import_review.req,
407
+ routes.video.list_pending_video_import_review.method,
408
+ routes.video.list_pending_video_import_review.path,
409
+ params,
410
+ );
411
+ },
412
+
413
+ markVideoGroupReviewed: (params: s.MarkVideoGroupReviewedRequest) => {
414
+ return call(
415
+ "video",
416
+ "mark_video_group_reviewed",
417
+ routes.video.mark_video_group_reviewed.resp,
418
+ routes.video.mark_video_group_reviewed.req,
419
+ routes.video.mark_video_group_reviewed.method,
420
+ routes.video.mark_video_group_reviewed.path,
421
+ params,
422
+ );
423
+ },
424
+
425
+ patchVideoGroupReview: (params: s.PatchVideoGroupReviewRequest) => {
426
+ return call(
427
+ "video",
428
+ "patch_video_group_review",
429
+ routes.video.patch_video_group_review.resp,
430
+ routes.video.patch_video_group_review.req,
431
+ routes.video.patch_video_group_review.method,
432
+ routes.video.patch_video_group_review.path,
433
+ params,
434
+ );
435
+ },
436
+
437
+ moveVideoReview: (params: s.MoveVideoReviewRequest) => {
438
+ return call(
439
+ "video",
440
+ "move_video_review",
441
+ routes.video.move_video_review.resp,
442
+ routes.video.move_video_review.req,
443
+ routes.video.move_video_review.method,
444
+ routes.video.move_video_review.path,
445
+ params,
446
+ );
447
+ },
448
+
449
+ videoPending: (params: s.VideoPendingRequest) => {
450
+ return call(
451
+ "video",
452
+ "video_pending",
453
+ routes.video.video_pending.resp,
454
+ routes.video.video_pending.req,
455
+ routes.video.video_pending.method,
456
+ routes.video.video_pending.path,
457
+ params,
458
+ );
459
+ },
460
+ };
461
+ }
462
+
463
+ export type VideoMethods = ReturnType<typeof createVideoMethods>;