Skip to content

Commit

Permalink
Merge pull request #11 from suzuki3jp/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
suzuki3jp authored Jan 28, 2025
2 parents 18154d0 + e3aa037 commit 9b0a0eb
Show file tree
Hide file tree
Showing 18 changed files with 166 additions and 143 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ main();
## Supported endpoints
We are striving to support more endpoints, but currently, there are many unsupported endpoints. If the endpoint you want to use is not supported, please open an [issue](https://github.com/suzuki3jp/youtubes.js/issues/new) to request it. We plan to prioritize adding support for the most requested endpoints.

-: Not available in YouTube Data API
-: Not available in YouTube Data API
×: Not supported
⚠️: Partially supported
✅: Fully supported
Expand Down
8 changes: 4 additions & 4 deletions docs/01-introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ main();
```

## Handling errors
All public methods of `youtubes.js` return `Result` of [`result4js`](https://github.com/suzuki3jp/result4js).
Using `Result` enables type-safe error handling.
> If you want to abandon type safety and perform dangerous error handling, you can use [`Result#throw`](https://github.com/suzuki3jp/result4js?tab=readme-ov-file#usage).
All public methods of `youtubes.js` return `Result` of [`neverthrow`](https://github.com/supermacro/neverthrow).
Using `Result` enables type-safe error handling.
While developers familiar with JavaScript might find `Result`-based error handling cumbersome at first, it will dramatically improve application reliability and substantially enhance the development experience through type safety.

```ts
import { ApiClient, StaticOAuthProvider } from "youtubes.js";
Expand All @@ -58,7 +58,7 @@ async function main() {
return;
}

const playlists = playlistsResult.data;
const playlists = playlistsResult.value;
}

main();
Expand Down
4 changes: 2 additions & 2 deletions docs/02-usecases.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Use Cases

## Note
All methods in `youtubes.js` return [`Result`](https://github.com/suzuki3jp/result4js) for safe error handling.
All methods in `youtubes.js` return [`Result`](https://github.com/supermacro/neverthrow) for safe error handling.
Learn more about error handling with `Result` [here](./01-introduction.md#handling-errors).

## Pagination
Expand Down Expand Up @@ -34,7 +34,7 @@ const allPages = await playlists.all() // Result<Playlist[], YouTubesJsErrors
if (allPages.isErr()) return; // Handling errors
// Returns all pages in an array. For multiple items per page, returns a 2D array.
// Use flat() to convert to a 1D array.
const data = allPages.data.flat();
const data = allPages.value.flat();
```

## `Playlists`
Expand Down
21 changes: 13 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,6 @@
},
"dependencies": {
"googleapis": "^144.0.0",
"result4js": "1.1.0"
"neverthrow": "^8.1.1"
}
}
1 change: 1 addition & 0 deletions src/OAuthProvider.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { google } from "googleapis";

import type { OAuth2Client } from "./types";

export type OAuthProviders = StaticOAuthProvider;
Expand Down
49 changes: 26 additions & 23 deletions src/Pagination.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Err, Ok, type Result } from "result4js";
import { type Result, err, ok } from "neverthrow";

import type { Logger } from "./Logger";
import { LIKELY_BUG } from "./constants";
Expand Down Expand Up @@ -91,12 +91,12 @@ export class Pagination<T> {
* });
* const client = new ApiClient({ oauth });
*
*
* // THIS IS UNSAFE ERROR HANDLING. See the safe error handling in the README.md Introduction.
* const playlists = (await client.playlists.getMine()).throw();
* console.log(playlists.data); // The first page of playlists
* const prevPage = (await playlists.prev()).throw();
* console.log(prevPage?.data); // The previous page of playlists or null if there is no previous page
* const playlists = await client.playlists.getMine();
* if (playlists.isErr()) return;
* console.log(playlists.value); // The first page of playlists
* const prevPage = await playlists.prev();
* if (prevPage?.isErr()) return;
* console.log(prevPage?.value); // The previous page of playlists or null if there is no previous page
* ```
*/
public async prev(): Promise<Result<
Expand Down Expand Up @@ -126,12 +126,12 @@ export class Pagination<T> {
* });
* const client = new ApiClient({ oauth });
*
*
* // THIS IS UNSAFE ERROR HANDLING. See the safe error handling in the README.md Introduction.
* const playlists = (await client.playlists.getMine()).throw();
* console.log(playlists.data); // The first page of playlists
* const nextPage = (await playlists.next()).throw();
* console.log(nextPage?.data); // The second page of playlists or null if there is no next page
* const playlists = await client.playlists.getMine();
* if (playlists.isErr()) return;
* console.log(playlists.value); // The first page of playlists
* const nextPage = await playlists.next();
* if (nextPage?.isErr()) return;
* console.log(nextPage?.value); // The second page of playlists or null if there is no next page
* ```
*/
public async next(): Promise<Result<
Expand Down Expand Up @@ -159,9 +159,12 @@ export class Pagination<T> {
* });
* const client = new ApiClient({ oauth });
*
* // THIS IS UNSAFE ERROR HANDLING. See the safe error handling in the README.md Introduction.
* const playlists = (await client.playlists.getMine()).throw();
* const allPlaylists = (await playlists.all()).throw().flat();
* const playlists = await client.playlists.getMine();
* if (playlists.isErr()) {
* // Handle the error
* return;
* }
* const allPlaylists = (await playlists.all()).flat();
* ```
*/
public async all(): Promise<Result<T[], YouTubesJsErrors>> {
Expand All @@ -170,19 +173,19 @@ export class Pagination<T> {

let prev = await this.prev();
while (prev) {
if (prev.isErr()) return Err(prev.data);
result.unshift(prev.data.data);
prev = await prev.data.prev();
if (prev.isErr()) return err(prev.error);
result.unshift(prev.value.data);
prev = await prev.value.prev();
}

let next = await this.next();
while (next) {
if (next.isErr()) return Err(next.data);
result.push(next.data.data);
next = await next.data.next();
if (next.isErr()) return err(next.error);
result.push(next.value.data);
next = await next.value.next();
}

return Ok(result);
return ok(result);
}
}

Expand Down
20 changes: 10 additions & 10 deletions src/entities/playlist-item.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { youtube_v3 } from "googleapis";
import { Err, Ok, type Result } from "result4js";
import { type Result, err, ok } from "neverthrow";

import type { Logger } from "../Logger";
import { LikelyBugError } from "../errors";
Expand Down Expand Up @@ -45,7 +45,7 @@ export function playlistItemFrom(
data.status?.privacyStatus === "private" &&
Object.keys(data.snippet?.thumbnails ?? {}).length === 0
)
return Ok(new UnavailablePlaylistItem());
return ok(new UnavailablePlaylistItem());

if (
isNullish(data.id) ||
Expand All @@ -65,7 +65,7 @@ export function playlistItemFrom(
currentLogger.debug("Raw data:");
currentLogger.debug(JSON.stringify(data, null, "\t"));

return Err(
return err(
new LikelyBugError(
"The raw data is missing required fields of a playlist data.",
),
Expand All @@ -74,16 +74,16 @@ export function playlistItemFrom(

const thumbnails = Thumbnails.from(data.snippet.thumbnails, currentLogger);
const privacy = convertToPrivacy(data.status.privacyStatus);
if (thumbnails.isErr()) return Err(thumbnails.data);
if (privacy.isErr()) return Err(privacy.data);
if (thumbnails.isErr()) return err(thumbnails.error);
if (privacy.isErr()) return err(privacy.error);

return Ok(
return ok(
new AvailablePlaylistItem({
id: data.id,
playlistId: data.snippet.playlistId,
title: data.snippet.title,
description: data.snippet.description,
thumbnails: thumbnails.data,
thumbnails: thumbnails.value,
channelId: data.snippet.channelId,
channelName: data.snippet.channelTitle,
videoId: data.snippet.resourceId.videoId,
Expand All @@ -101,12 +101,12 @@ export function playlistItemFromMany(
const playlistItems: PlaylistItem[] = [];
for (const item of data) {
const playlistItem = playlistItemFrom(item, logger);
if (playlistItem.isErr()) return Err(playlistItem.data);
if (playlistItem.isErr()) return err(playlistItem.error);

playlistItems.push(playlistItem.data);
playlistItems.push(playlistItem.value);
}

return Ok(playlistItems);
return ok(playlistItems);
}

/**
Expand Down
20 changes: 10 additions & 10 deletions src/entities/playlist.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { youtube_v3 } from "googleapis";
import { Err, Ok, type Result } from "result4js";
import { type Result, err, ok } from "neverthrow";

import type { Logger } from "../Logger";
import { LikelyBugError } from "../errors";
Expand Down Expand Up @@ -102,24 +102,24 @@ export class Playlist {
currentLogger.debug("Generating Playlist instance from raw data.");
currentLogger.debug("Raw data:");
currentLogger.debug(JSON.stringify(data, null, "\t"));
return Err(new LikelyBugError(message));
return err(new LikelyBugError(message));
}

const thumbnails = Thumbnails.from(
data.snippet.thumbnails,
currentLogger,
);
const privacy = convertToPrivacy(data.status.privacyStatus);
if (privacy.isErr()) return Err(privacy.data);
if (thumbnails.isErr()) return Err(thumbnails.data);
if (privacy.isErr()) return err(privacy.error);
if (thumbnails.isErr()) return err(thumbnails.error);

return Ok(
return ok(
new Playlist({
id: data.id,
title: data.snippet.title,
description: data.snippet.description,
thumbnails: thumbnails.data,
privacy: privacy.data,
thumbnails: thumbnails.value,
privacy: privacy.value,
count: data.contentDetails.itemCount,
publishedAt: new Date(data.snippet.publishedAt),
channelId: data.snippet.channelId,
Expand All @@ -138,12 +138,12 @@ export class Playlist {
for (const playlist of data) {
const result = Playlist.from(playlist, currentLogger);
if (result.isErr()) {
return Err(result.data);
return err(result.error);
}
playlists.push(result.data);
playlists.push(result.value);
}

return Ok(playlists);
return ok(playlists);
}
}

Expand Down
12 changes: 6 additions & 6 deletions src/entities/privacy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Err, Ok, type Result } from "result4js";
import { type Result, err, ok } from "neverthrow";

import { LikelyBugError } from "../errors";

Expand All @@ -11,17 +11,17 @@ export type Privacy = "public" | "unlisted" | "private";
export function convertToPrivacy(
data?: string,
): Result<Privacy, LikelyBugError> {
if (!data) return Err(new LikelyBugError("The raw data is undefined."));
if (!data) return err(new LikelyBugError("The raw data is undefined."));

switch (data) {
case "public":
return Ok("public" as Privacy);
return ok("public");
case "unlisted":
return Ok("unlisted" as Privacy);
return ok("unlisted");
case "private":
return Ok("private" as Privacy);
return ok("private");
default:
return Err(
return err(
new LikelyBugError(
`The raw data is unexpected format. Expected "public", "unlisted", or "private". Received: ${data}`,
),
Expand Down
6 changes: 3 additions & 3 deletions src/entities/thumbnails.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { youtube_v3 } from "googleapis";
import { Err, Ok, type Result } from "result4js";
import { type Result, err, ok } from "neverthrow";

import type { Logger } from "../Logger";
import { LikelyBugError } from "../errors";
Expand Down Expand Up @@ -53,14 +53,14 @@ export class Thumbnails {
currentLogger.debug("Raw data:");
currentLogger.debug(JSON.stringify(data, null, "\t"));

return Err(
return err(
new LikelyBugError(
"The raw data is missing required fields. Each thumbnail (default, medium, high, standard, maxres) must include url, width, and height.",
),
);
}

return Ok(new Thumbnails(data as ThumbnailsData));
return ok(new Thumbnails(data as ThumbnailsData));
}

/**
Expand Down
Loading

0 comments on commit 9b0a0eb

Please sign in to comment.