katanakit-js 5.0.0 → 5.1.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.
Files changed (93) hide show
  1. package/README.md +35 -1
  2. package/dist/adapters/insforge/index.d.ts +3 -0
  3. package/dist/adapters/insforge/index.d.ts.map +1 -0
  4. package/dist/adapters/insforge/index.js +2 -0
  5. package/dist/adapters/insforge/index.js.map +1 -0
  6. package/dist/adapters/insforge/insforge.service.d.ts +116 -0
  7. package/dist/adapters/insforge/insforge.service.d.ts.map +1 -0
  8. package/dist/adapters/insforge/insforge.service.js +449 -0
  9. package/dist/adapters/insforge/insforge.service.js.map +1 -0
  10. package/dist/adapters/notion/notion.service.d.ts.map +1 -1
  11. package/dist/adapters/notion/notion.service.js +58 -19
  12. package/dist/adapters/notion/notion.service.js.map +1 -1
  13. package/dist/adapters/wordpress/wordpress.service.d.ts.map +1 -1
  14. package/dist/adapters/wordpress/wordpress.service.js +161 -44
  15. package/dist/adapters/wordpress/wordpress.service.js.map +1 -1
  16. package/dist/config/seo.service.d.ts.map +1 -1
  17. package/dist/config/seo.service.js +42 -4
  18. package/dist/config/seo.service.js.map +1 -1
  19. package/dist/core/index.d.ts +3 -0
  20. package/dist/core/index.d.ts.map +1 -1
  21. package/dist/core/index.js +3 -0
  22. package/dist/core/index.js.map +1 -1
  23. package/dist/core/services/media.service.d.ts +97 -0
  24. package/dist/core/services/media.service.d.ts.map +1 -0
  25. package/dist/core/services/media.service.js +239 -0
  26. package/dist/core/services/media.service.js.map +1 -0
  27. package/dist/core/services/validation.service.d.ts +22 -0
  28. package/dist/core/services/validation.service.d.ts.map +1 -0
  29. package/dist/core/services/validation.service.js +41 -0
  30. package/dist/core/services/validation.service.js.map +1 -0
  31. package/dist/core/services/youtube.service.d.ts +79 -0
  32. package/dist/core/services/youtube.service.d.ts.map +1 -0
  33. package/dist/core/services/youtube.service.js +388 -0
  34. package/dist/core/services/youtube.service.js.map +1 -0
  35. package/dist/index.d.ts +1 -0
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +1 -0
  38. package/dist/index.js.map +1 -1
  39. package/dist/infrastructure/index.d.ts +1 -0
  40. package/dist/infrastructure/index.d.ts.map +1 -1
  41. package/dist/infrastructure/index.js +1 -0
  42. package/dist/infrastructure/index.js.map +1 -1
  43. package/dist/infrastructure/media/media.service.d.ts +15 -0
  44. package/dist/infrastructure/media/media.service.d.ts.map +1 -0
  45. package/dist/infrastructure/media/media.service.js +44 -0
  46. package/dist/infrastructure/media/media.service.js.map +1 -0
  47. package/dist/schemas/access.schema.d.ts +69 -0
  48. package/dist/schemas/access.schema.d.ts.map +1 -0
  49. package/dist/schemas/access.schema.js +36 -0
  50. package/dist/schemas/access.schema.js.map +1 -0
  51. package/dist/schemas/ai.schema.d.ts +53 -0
  52. package/dist/schemas/ai.schema.d.ts.map +1 -0
  53. package/dist/schemas/ai.schema.js +34 -0
  54. package/dist/schemas/ai.schema.js.map +1 -0
  55. package/dist/schemas/common.schema.d.ts +49 -0
  56. package/dist/schemas/common.schema.d.ts.map +1 -0
  57. package/dist/schemas/common.schema.js +23 -0
  58. package/dist/schemas/common.schema.js.map +1 -0
  59. package/dist/schemas/core.schema.d.ts +164 -0
  60. package/dist/schemas/core.schema.d.ts.map +1 -0
  61. package/dist/schemas/core.schema.js +109 -0
  62. package/dist/schemas/core.schema.js.map +1 -0
  63. package/dist/schemas/index.d.ts +11 -0
  64. package/dist/schemas/index.d.ts.map +1 -0
  65. package/dist/schemas/index.js +11 -0
  66. package/dist/schemas/index.js.map +1 -0
  67. package/dist/schemas/insforge.schema.d.ts +69 -0
  68. package/dist/schemas/insforge.schema.d.ts.map +1 -0
  69. package/dist/schemas/insforge.schema.js +82 -0
  70. package/dist/schemas/insforge.schema.js.map +1 -0
  71. package/dist/schemas/media.schema.d.ts +51 -0
  72. package/dist/schemas/media.schema.d.ts.map +1 -0
  73. package/dist/schemas/media.schema.js +50 -0
  74. package/dist/schemas/media.schema.js.map +1 -0
  75. package/dist/schemas/notion.schema.d.ts +1058 -0
  76. package/dist/schemas/notion.schema.d.ts.map +1 -0
  77. package/dist/schemas/notion.schema.js +248 -0
  78. package/dist/schemas/notion.schema.js.map +1 -0
  79. package/dist/schemas/rss.schema.d.ts +35 -0
  80. package/dist/schemas/rss.schema.d.ts.map +1 -0
  81. package/dist/schemas/rss.schema.js +44 -0
  82. package/dist/schemas/rss.schema.js.map +1 -0
  83. package/dist/schemas/wordpress.schema.d.ts +1304 -0
  84. package/dist/schemas/wordpress.schema.d.ts.map +1 -0
  85. package/dist/schemas/wordpress.schema.js +404 -0
  86. package/dist/schemas/wordpress.schema.js.map +1 -0
  87. package/dist/schemas/youtube.schema.d.ts +122 -0
  88. package/dist/schemas/youtube.schema.d.ts.map +1 -0
  89. package/dist/schemas/youtube.schema.js +113 -0
  90. package/dist/schemas/youtube.schema.js.map +1 -0
  91. package/dist/types/index.d.ts +150 -770
  92. package/dist/types/index.d.ts.map +1 -1
  93. package/package.json +23 -3
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Validates unknown data (API responses, configs, user input) against a
3
+ * Zod schema and returns a Safe Result instead of throwing.
4
+ * Framework-agnostic and pure: no I/O, SSR-safe.
5
+ *
6
+ * @typeParam T - The validated output type (`z.infer` of the schema).
7
+ * @param schema - Any Zod schema.
8
+ * @param data - Unknown input to validate.
9
+ * @returns `FetchResult<T>` with typed data or a `Validation Error`.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import { SmartVideoOptionsSchema } from "katanakit-js/schemas";
14
+ *
15
+ * const result = useValidate(SmartVideoOptionsSchema, JSON.parse(raw));
16
+ * if (result.ok) useBuildVideoEmbed(result.data);
17
+ * ```
18
+ */
19
+ export function useValidate(schema, data) {
20
+ const parsed = schema.safeParse(data);
21
+ if (parsed.success) {
22
+ return { data: parsed.data, error: null, url: "", status: 200, ok: true };
23
+ }
24
+ const details = parsed.error.issues.map((issue) => ({
25
+ path: issue.path.join("."),
26
+ message: issue.message,
27
+ }));
28
+ const summary = details.map((d) => (d.path ? `${d.path}: ${d.message}` : d.message)).join("; ");
29
+ return {
30
+ data: null,
31
+ error: {
32
+ message: `Validation Error: ${summary || "invalid data"}`,
33
+ status: 400,
34
+ details,
35
+ },
36
+ url: "",
37
+ status: 400,
38
+ ok: false,
39
+ };
40
+ }
41
+ //# sourceMappingURL=validation.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.service.js","sourceRoot":"","sources":["../../../src/core/services/validation.service.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,WAAW,CAAI,MAAoB,EAAE,IAAa;IACjE,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACtC,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IAC3E,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACnD,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;QAC1B,OAAO,EAAE,KAAK,CAAC,OAAO;KACtB,CAAC,CAAC,CAAC;IACJ,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChG,OAAO;QACN,IAAI,EAAE,IAAI;QACV,KAAK,EAAE;YACN,OAAO,EAAE,qBAAqB,OAAO,IAAI,cAAc,EAAE;YACzD,MAAM,EAAE,GAAG;YACX,OAAO;SACP;QACD,GAAG,EAAE,EAAE;QACP,MAAM,EAAE,GAAG;QACX,EAAE,EAAE,KAAK;KACT,CAAC;AACH,CAAC"}
@@ -0,0 +1,79 @@
1
+ import type { FetchResult, YoutubeApiConfig, YoutubeListParams, YoutubeVideoNormalized, YoutubeVideoPage } from "../../types/index.js";
2
+ /**
3
+ * Registers the YouTube APIs (`youtube` + keyless `youtubeFeed`) and
4
+ * stores the default channel. Call once per app, server/build-time —
5
+ * the API key must never ship to the browser.
6
+ *
7
+ * @param config - API key + default channel id.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * useInitYoutube({ apiKey: process.env.YOUTUBE_API_KEY!, channelId: "UC…" });
12
+ * const page = await useGetChannelVideos({ maxResults: 6 });
13
+ * ```
14
+ */
15
+ export declare function useInitYoutube(config: YoutubeApiConfig): void;
16
+ /**
17
+ * Returns a shallow copy of the YouTube config (safe to mutate locally).
18
+ *
19
+ * @returns The current `{ apiKey, channelId }`.
20
+ */
21
+ export declare function useGetYoutubeConfig(): YoutubeApiConfig;
22
+ /**
23
+ * Builds the public channel RSS feed URL. Needs no API key and
24
+ * costs zero quota — ideal for "latest videos" widgets.
25
+ *
26
+ * @param channelId - Channel id (`UC…`).
27
+ * @returns Feed URL.
28
+ */
29
+ export declare function useBuildYoutubeRssUrl(channelId: string): string;
30
+ /**
31
+ * Parses an ISO 8601 duration (`PT1H2M3S`) into seconds.
32
+ * Pure and SSR-safe.
33
+ *
34
+ * @param input - Duration string from `contentDetails.duration`.
35
+ * @returns Total seconds (`0` for invalid input).
36
+ */
37
+ export declare function useParseIso8601Duration(input: string): number;
38
+ /**
39
+ * Resolves a channel id to its `uploads` playlist id (quota cost: 1 unit).
40
+ * Prefer passing `playlistId` to `useGetChannelVideos` to skip this call.
41
+ *
42
+ * @param channelId - Channel id (defaults to the configured one).
43
+ * @returns Safe Result with the uploads playlist id.
44
+ */
45
+ export declare function useGetUploadsPlaylistId(channelId?: string): Promise<FetchResult<string>>;
46
+ /**
47
+ * Lists channel videos via the quota-cheap path
48
+ * (`channels` → `playlistItems`, never `search.list` which costs 100 units).
49
+ * Call server/build-time and cache the response (1h) to share quota
50
+ * across every repo using the same GCP project.
51
+ *
52
+ * @param params - Channel/playlist, page size (1–50) and page token.
53
+ * @returns Safe Result with normalized videos + pagination.
54
+ */
55
+ export declare function useGetChannelVideos(params?: YoutubeListParams): Promise<FetchResult<YoutubeVideoPage>>;
56
+ /**
57
+ * Enriches videos with duration and view count (`videos.list`, 1 unit).
58
+ *
59
+ * @param ids - Up to 50 video ids.
60
+ * @returns Safe Result with enriched videos.
61
+ */
62
+ export declare function useGetVideoDetails(ids: string[]): Promise<FetchResult<YoutubeVideoNormalized[]>>;
63
+ /**
64
+ * Parses a YouTube channel RSS feed (`feeds/videos.xml`) into normalized
65
+ * videos. Pure and SSR-safe — no network, no DOM parser.
66
+ *
67
+ * @param xml - Raw feed XML.
68
+ * @returns Normalized videos (newest first, as served by the feed).
69
+ */
70
+ export declare function useParseYoutubeRss(xml: string): YoutubeVideoNormalized[];
71
+ /**
72
+ * Fetches the keyless channel RSS feed and parses it. Zero quota,
73
+ * limited to the latest ~15 videos with basic metadata.
74
+ *
75
+ * @param channelId - Channel id (defaults to the configured one).
76
+ * @returns Safe Result with normalized videos.
77
+ */
78
+ export declare function useGetChannelVideosRss(channelId?: string): Promise<FetchResult<YoutubeVideoNormalized[]>>;
79
+ //# sourceMappingURL=youtube.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"youtube.service.d.ts","sourceRoot":"","sources":["../../../src/core/services/youtube.service.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACX,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,sBAAsB,EACtB,gBAAgB,EAChB,MAAM,sBAAsB,CAAC;AAe9B;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAqB7D;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,IAAI,gBAAgB,CAEtD;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAI/D;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAyB7D;AAgED;;;;;;GAMG;AACH,wBAAsB,uBAAuB,CAC5C,SAAS,SAA0B,GACjC,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CA0C9B;AAED;;;;;;;;GAQG;AACH,wBAAsB,mBAAmB,CACxC,MAAM,GAAE,iBAAsB,GAC5B,OAAO,CAAC,WAAW,CAAC,gBAAgB,CAAC,CAAC,CA8ExC;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACvC,GAAG,EAAE,MAAM,EAAE,GACX,OAAO,CAAC,WAAW,CAAC,sBAAsB,EAAE,CAAC,CAAC,CAmDhD;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,sBAAsB,EAAE,CAuBxE;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC3C,SAAS,SAA0B,GACjC,OAAO,CAAC,WAAW,CAAC,sBAAsB,EAAE,CAAC,CAAC,CA6BhD"}
@@ -0,0 +1,388 @@
1
+ import { YoutubeChannelsResponseSchema, YoutubePlaylistItemsResponseSchema, YoutubeVideosResponseSchema, } from "../../schemas/youtube.schema.js";
2
+ import { useGet, useInitApis } from "./http.service.js";
3
+ import { useBuildYoutubeEmbedUrl, useBuildYoutubeThumbnail } from "./media.service.js";
4
+ import { useValidate } from "./validation.service.js";
5
+ /** Registry key for the YouTube Data API v3. */
6
+ const YOUTUBE_API_NAME = "youtube";
7
+ /** Registry key for the public channel RSS feed (no key needed). */
8
+ const YOUTUBE_FEED_API_NAME = "youtubeFeed";
9
+ const YOUTUBE_BASE_URI = "https://www.googleapis.com/youtube/v3";
10
+ const YOUTUBE_FEED_BASE_URI = "https://www.youtube.com";
11
+ /** Module-level config. One GCP project serves every repo. */
12
+ let youtubeConfig = { apiKey: "", channelId: "" };
13
+ /**
14
+ * Registers the YouTube APIs (`youtube` + keyless `youtubeFeed`) and
15
+ * stores the default channel. Call once per app, server/build-time —
16
+ * the API key must never ship to the browser.
17
+ *
18
+ * @param config - API key + default channel id.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * useInitYoutube({ apiKey: process.env.YOUTUBE_API_KEY!, channelId: "UC…" });
23
+ * const page = await useGetChannelVideos({ maxResults: 6 });
24
+ * ```
25
+ */
26
+ export function useInitYoutube(config) {
27
+ youtubeConfig = { ...config };
28
+ useInitApis({
29
+ [YOUTUBE_API_NAME]: {
30
+ baseUri: YOUTUBE_BASE_URI,
31
+ endpoints: {
32
+ channels: "/channels",
33
+ playlistItems: "/playlistItems",
34
+ videos: "/videos",
35
+ },
36
+ defaultQueryParams: {
37
+ channels: { key: config.apiKey },
38
+ playlistItems: { key: config.apiKey },
39
+ videos: { key: config.apiKey },
40
+ },
41
+ },
42
+ [YOUTUBE_FEED_API_NAME]: {
43
+ baseUri: YOUTUBE_FEED_BASE_URI,
44
+ endpoints: { channelFeed: "/feeds/videos.xml" },
45
+ },
46
+ });
47
+ }
48
+ /**
49
+ * Returns a shallow copy of the YouTube config (safe to mutate locally).
50
+ *
51
+ * @returns The current `{ apiKey, channelId }`.
52
+ */
53
+ export function useGetYoutubeConfig() {
54
+ return { ...youtubeConfig };
55
+ }
56
+ /**
57
+ * Builds the public channel RSS feed URL. Needs no API key and
58
+ * costs zero quota — ideal for "latest videos" widgets.
59
+ *
60
+ * @param channelId - Channel id (`UC…`).
61
+ * @returns Feed URL.
62
+ */
63
+ export function useBuildYoutubeRssUrl(channelId) {
64
+ const url = new URL("https://www.youtube.com/feeds/videos.xml");
65
+ url.searchParams.set("channel_id", channelId);
66
+ return url.toString();
67
+ }
68
+ /**
69
+ * Parses an ISO 8601 duration (`PT1H2M3S`) into seconds.
70
+ * Pure and SSR-safe.
71
+ *
72
+ * @param input - Duration string from `contentDetails.duration`.
73
+ * @returns Total seconds (`0` for invalid input).
74
+ */
75
+ export function useParseIso8601Duration(input) {
76
+ const match = /^P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)W)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d+)?)S)?)?$/.exec(input.trim());
77
+ if (!match)
78
+ return 0;
79
+ const [, years = "0", months = "0", weeks = "0", days = "0", hours = "0", minutes = "0", seconds = "0",] = match;
80
+ return (Number(years) * 31_556_952 +
81
+ Number(months) * 2_629_746 +
82
+ Number(weeks) * 604_800 +
83
+ Number(days) * 86_400 +
84
+ Number(hours) * 3600 +
85
+ Number(minutes) * 60 +
86
+ Math.floor(Number(seconds)));
87
+ }
88
+ /**
89
+ * Decodes the five XML entities found in YouTube RSS feeds.
90
+ *
91
+ * @param value - Raw XML text.
92
+ * @returns Decoded text.
93
+ */
94
+ function decodeXmlEntities(value) {
95
+ return value
96
+ .replace(/&lt;/g, "<")
97
+ .replace(/&gt;/g, ">")
98
+ .replace(/&quot;/g, '"')
99
+ .replace(/&apos;/g, "'")
100
+ .replace(/&#39;/g, "'")
101
+ .replace(/&amp;/g, "&");
102
+ }
103
+ /**
104
+ * Returns a config-error Safe Result when the API key is missing.
105
+ *
106
+ * @param message - Human-readable hint.
107
+ * @returns Error result with `status: 0`.
108
+ */
109
+ function youtubeConfigError(message) {
110
+ return { data: null, error: { message, status: 0 }, url: "", status: 0, ok: false };
111
+ }
112
+ /**
113
+ * Hydrates a normalized video with player-ready URLs.
114
+ *
115
+ * @param video - Core fields.
116
+ * @returns Full {@link YoutubeVideoNormalized}.
117
+ */
118
+ function hydrateVideo(video) {
119
+ const thumbnails = {
120
+ default: video.thumbnails?.default ?? useBuildYoutubeThumbnail(video.id, "default"),
121
+ medium: video.thumbnails?.medium ?? useBuildYoutubeThumbnail(video.id, "mqdefault"),
122
+ high: video.thumbnails?.high ?? useBuildYoutubeThumbnail(video.id, "hqdefault"),
123
+ };
124
+ return {
125
+ id: video.id,
126
+ title: video.title,
127
+ description: video.description,
128
+ publishedAt: video.publishedAt,
129
+ channelTitle: video.channelTitle,
130
+ thumbnails,
131
+ thumbnail: thumbnails.high,
132
+ url: `https://www.youtube.com/watch?v=${video.id}`,
133
+ embedUrl: useBuildYoutubeEmbedUrl(video.id),
134
+ durationSeconds: video.durationSeconds,
135
+ viewCount: video.viewCount,
136
+ };
137
+ }
138
+ /**
139
+ * Resolves a channel id to its `uploads` playlist id (quota cost: 1 unit).
140
+ * Prefer passing `playlistId` to `useGetChannelVideos` to skip this call.
141
+ *
142
+ * @param channelId - Channel id (defaults to the configured one).
143
+ * @returns Safe Result with the uploads playlist id.
144
+ */
145
+ export async function useGetUploadsPlaylistId(channelId = youtubeConfig.channelId) {
146
+ if (!youtubeConfig.apiKey) {
147
+ return youtubeConfigError("Config Error: YouTube API key is missing. Call useInitYoutube({ apiKey, channelId }) first.");
148
+ }
149
+ if (!channelId) {
150
+ return youtubeConfigError("Config Error: channelId is missing. Pass it explicitly or via useInitYoutube.");
151
+ }
152
+ const result = await useGet(YOUTUBE_API_NAME, "channels", {
153
+ query: { part: "contentDetails", id: channelId },
154
+ });
155
+ if (!result.ok) {
156
+ return { data: null, error: result.error, url: result.url, status: result.status, ok: false };
157
+ }
158
+ const parsed = useValidate(YoutubeChannelsResponseSchema, result.data);
159
+ if (!parsed.ok) {
160
+ return {
161
+ data: null,
162
+ error: {
163
+ message: "YouTube Error: unexpected channels.list shape.",
164
+ status: 502,
165
+ details: parsed.error.details,
166
+ },
167
+ url: result.url,
168
+ status: 502,
169
+ ok: false,
170
+ };
171
+ }
172
+ const playlistId = parsed.data.items?.[0]?.contentDetails?.relatedPlaylists?.uploads ?? "";
173
+ if (!playlistId) {
174
+ return {
175
+ data: null,
176
+ error: { message: "YouTube Error: uploads playlist not found for this channel.", status: 404 },
177
+ url: result.url,
178
+ status: 404,
179
+ ok: false,
180
+ };
181
+ }
182
+ return { data: playlistId, error: null, url: result.url, status: result.status, ok: true };
183
+ }
184
+ /**
185
+ * Lists channel videos via the quota-cheap path
186
+ * (`channels` → `playlistItems`, never `search.list` which costs 100 units).
187
+ * Call server/build-time and cache the response (1h) to share quota
188
+ * across every repo using the same GCP project.
189
+ *
190
+ * @param params - Channel/playlist, page size (1–50) and page token.
191
+ * @returns Safe Result with normalized videos + pagination.
192
+ */
193
+ export async function useGetChannelVideos(params = {}) {
194
+ if (!youtubeConfig.apiKey) {
195
+ return youtubeConfigError("Config Error: YouTube API key is missing. Call useInitYoutube({ apiKey, channelId }) first.");
196
+ }
197
+ let playlistId = params.playlistId ?? "";
198
+ if (!playlistId) {
199
+ const resolved = await useGetUploadsPlaylistId(params.channelId);
200
+ if (!resolved.ok) {
201
+ return {
202
+ data: null,
203
+ error: resolved.error,
204
+ url: resolved.url,
205
+ status: resolved.status,
206
+ ok: false,
207
+ };
208
+ }
209
+ playlistId = resolved.data;
210
+ }
211
+ const maxResults = Math.min(50, Math.max(1, params.maxResults ?? 12));
212
+ const query = {
213
+ part: "snippet,contentDetails",
214
+ playlistId,
215
+ maxResults,
216
+ pageToken: params.pageToken,
217
+ };
218
+ const result = await useGet(YOUTUBE_API_NAME, "playlistItems", {
219
+ query,
220
+ });
221
+ if (!result.ok) {
222
+ return { data: null, error: result.error, url: result.url, status: result.status, ok: false };
223
+ }
224
+ const parsed = useValidate(YoutubePlaylistItemsResponseSchema, result.data);
225
+ if (!parsed.ok) {
226
+ return {
227
+ data: null,
228
+ error: {
229
+ message: "YouTube Error: unexpected playlistItems.list shape.",
230
+ status: 502,
231
+ details: parsed.error.details,
232
+ },
233
+ url: result.url,
234
+ status: 502,
235
+ ok: false,
236
+ };
237
+ }
238
+ const videos = [];
239
+ for (const item of parsed.data.items ?? []) {
240
+ const id = item.snippet?.resourceId?.videoId ?? item.contentDetails?.videoId ?? "";
241
+ if (!id)
242
+ continue;
243
+ videos.push(hydrateVideo({
244
+ id,
245
+ title: item.snippet?.title ?? "",
246
+ description: item.snippet?.description ?? "",
247
+ publishedAt: item.snippet?.publishedAt ?? "",
248
+ channelTitle: item.snippet?.channelTitle,
249
+ thumbnails: {
250
+ default: item.snippet?.thumbnails?.default?.url,
251
+ medium: item.snippet?.thumbnails?.medium?.url,
252
+ high: item.snippet?.thumbnails?.high?.url,
253
+ },
254
+ }));
255
+ }
256
+ return {
257
+ data: {
258
+ videos,
259
+ nextPageToken: parsed.data.nextPageToken,
260
+ prevPageToken: parsed.data.prevPageToken,
261
+ totalResults: parsed.data.pageInfo?.totalResults,
262
+ },
263
+ error: null,
264
+ url: result.url,
265
+ status: result.status,
266
+ ok: true,
267
+ };
268
+ }
269
+ /**
270
+ * Enriches videos with duration and view count (`videos.list`, 1 unit).
271
+ *
272
+ * @param ids - Up to 50 video ids.
273
+ * @returns Safe Result with enriched videos.
274
+ */
275
+ export async function useGetVideoDetails(ids) {
276
+ if (!youtubeConfig.apiKey) {
277
+ return youtubeConfigError("Config Error: YouTube API key is missing. Call useInitYoutube({ apiKey, channelId }) first.");
278
+ }
279
+ if (ids.length === 0 || ids.length > 50) {
280
+ return {
281
+ data: null,
282
+ error: { message: "Validation Error: pass between 1 and 50 video ids.", status: 400 },
283
+ url: "",
284
+ status: 400,
285
+ ok: false,
286
+ };
287
+ }
288
+ const result = await useGet(YOUTUBE_API_NAME, "videos", {
289
+ query: { part: "snippet,contentDetails,statistics", id: ids.join(",") },
290
+ });
291
+ if (!result.ok) {
292
+ return { data: null, error: result.error, url: result.url, status: result.status, ok: false };
293
+ }
294
+ const parsed = useValidate(YoutubeVideosResponseSchema, result.data);
295
+ if (!parsed.ok) {
296
+ return {
297
+ data: null,
298
+ error: {
299
+ message: "YouTube Error: unexpected videos.list shape.",
300
+ status: 502,
301
+ details: parsed.error.details,
302
+ },
303
+ url: result.url,
304
+ status: 502,
305
+ ok: false,
306
+ };
307
+ }
308
+ const videos = (parsed.data.items ?? [])
309
+ .filter((item) => Boolean(item.id))
310
+ .map((item) => hydrateVideo({
311
+ id: String(item.id),
312
+ title: item.snippet?.title ?? "",
313
+ description: item.snippet?.description ?? "",
314
+ publishedAt: item.snippet?.publishedAt ?? "",
315
+ channelTitle: item.snippet?.channelTitle,
316
+ durationSeconds: item.contentDetails?.duration
317
+ ? useParseIso8601Duration(item.contentDetails.duration)
318
+ : undefined,
319
+ viewCount: item.statistics?.viewCount ? Number(item.statistics.viewCount) : undefined,
320
+ }));
321
+ return { data: videos, error: null, url: result.url, status: result.status, ok: true };
322
+ }
323
+ /**
324
+ * Parses a YouTube channel RSS feed (`feeds/videos.xml`) into normalized
325
+ * videos. Pure and SSR-safe — no network, no DOM parser.
326
+ *
327
+ * @param xml - Raw feed XML.
328
+ * @returns Normalized videos (newest first, as served by the feed).
329
+ */
330
+ export function useParseYoutubeRss(xml) {
331
+ const videos = [];
332
+ const entryPattern = /<entry>([\s\S]*?)<\/entry>/g;
333
+ let entry;
334
+ while ((entry = entryPattern.exec(xml)) !== null) {
335
+ const body = entry[1] ?? "";
336
+ const pick = (pattern) => {
337
+ const match = pattern.exec(body);
338
+ return match?.[1] ? decodeXmlEntities(match[1].trim()) : "";
339
+ };
340
+ const id = pick(/<yt:videoId>([^<]+)<\/yt:videoId>/);
341
+ if (!id)
342
+ continue;
343
+ videos.push(hydrateVideo({
344
+ id,
345
+ title: pick(/<title>([\s\S]*?)<\/title>/),
346
+ description: pick(/<media:description>([\s\S]*?)<\/media:description>/),
347
+ publishedAt: pick(/<published>([^<]+)<\/published>/),
348
+ channelTitle: pick(/<author>\s*<name>([\s\S]*?)<\/name>/) || undefined,
349
+ }));
350
+ }
351
+ return videos;
352
+ }
353
+ /**
354
+ * Fetches the keyless channel RSS feed and parses it. Zero quota,
355
+ * limited to the latest ~15 videos with basic metadata.
356
+ *
357
+ * @param channelId - Channel id (defaults to the configured one).
358
+ * @returns Safe Result with normalized videos.
359
+ */
360
+ export async function useGetChannelVideosRss(channelId = youtubeConfig.channelId) {
361
+ if (!channelId) {
362
+ return youtubeConfigError("Config Error: channelId is missing. Pass it explicitly or via useInitYoutube.");
363
+ }
364
+ const result = await useGet(YOUTUBE_FEED_API_NAME, "channelFeed", {
365
+ query: { channel_id: channelId },
366
+ });
367
+ if (!result.ok) {
368
+ return { data: null, error: result.error, url: result.url, status: result.status, ok: false };
369
+ }
370
+ const xml = typeof result.data === "string" ? result.data : "";
371
+ if (!xml) {
372
+ return {
373
+ data: null,
374
+ error: { message: "YouTube Error: empty channel feed.", status: 502 },
375
+ url: result.url,
376
+ status: 502,
377
+ ok: false,
378
+ };
379
+ }
380
+ return {
381
+ data: useParseYoutubeRss(xml),
382
+ error: null,
383
+ url: result.url,
384
+ status: result.status,
385
+ ok: true,
386
+ };
387
+ }
388
+ //# sourceMappingURL=youtube.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"youtube.service.js","sourceRoot":"","sources":["../../../src/core/services/youtube.service.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./config/index.js";
2
2
  export * from "./core/index.js";
3
3
  export * from "./infrastructure/index.js";
4
+ export * from "./schemas/index.js";
4
5
  export * from "./types/index.js";
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./config/index.js";
2
2
  export * from "./core/index.js";
3
3
  export * from "./infrastructure/index.js";
4
+ export * from "./schemas/index.js";
4
5
  export * from "./types/index.js";
5
6
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC"}
@@ -1,4 +1,5 @@
1
1
  export * from "./dom/dom.service.js";
2
+ export * from "./media/media.service.js";
2
3
  export * from "./observer/observer.service.js";
3
4
  export * from "./sensors/sensors.service.js";
4
5
  export * from "./storage/storage.service.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/infrastructure/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,0BAA0B,CAAC;AACzC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,4BAA4B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/infrastructure/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,0BAA0B,CAAC;AACzC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,4BAA4B,CAAC"}
@@ -1,4 +1,5 @@
1
1
  export * from "./dom/dom.service.js";
2
+ export * from "./media/media.service.js";
2
3
  export * from "./observer/observer.service.js";
3
4
  export * from "./sensors/sensors.service.js";
4
5
  export * from "./storage/storage.service.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/infrastructure/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,0BAA0B,CAAC;AACzC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,4BAA4B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/infrastructure/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,0BAA0B,CAAC;AACzC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,4BAA4B,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Hydrates click-to-play facades rendered by `useBuildVideoEmbed`.
3
+ * SSR-safe: no-op outside the browser, no side effects on import.
4
+ * Call once per page (Astro/React/Vue/MDX `useEffect` or inline script).
5
+ *
6
+ * @param root - Scope element or document (defaults to `document`).
7
+ * @returns Cleanup function removing the delegated listener.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * useEnhanceVideoFacades(); // click on [data-video-facade] swaps in the iframe
12
+ * ```
13
+ */
14
+ export declare function useEnhanceVideoFacades(root?: ParentNode): () => void;
15
+ //# sourceMappingURL=media.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media.service.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/media/media.service.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,CAAC,EAAE,UAAU,GAAG,MAAM,IAAI,CA2BpE"}
@@ -0,0 +1,44 @@
1
+ import { useIsBrowser } from "../dom/dom.service.js";
2
+ /**
3
+ * Hydrates click-to-play facades rendered by `useBuildVideoEmbed`.
4
+ * SSR-safe: no-op outside the browser, no side effects on import.
5
+ * Call once per page (Astro/React/Vue/MDX `useEffect` or inline script).
6
+ *
7
+ * @param root - Scope element or document (defaults to `document`).
8
+ * @returns Cleanup function removing the delegated listener.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * useEnhanceVideoFacades(); // click on [data-video-facade] swaps in the iframe
13
+ * ```
14
+ */
15
+ export function useEnhanceVideoFacades(root) {
16
+ if (!useIsBrowser())
17
+ return () => undefined;
18
+ const scope = root ?? document;
19
+ const onClick = (event) => {
20
+ const target = event.target;
21
+ const button = target?.closest?.("[data-video-facade]");
22
+ if (!button || !scope.contains(button))
23
+ return;
24
+ const src = button.getAttribute("data-video-facade");
25
+ if (!src)
26
+ return;
27
+ const label = button.getAttribute("aria-label") ?? "Embedded video";
28
+ const iframe = document.createElement("iframe");
29
+ iframe.src = src;
30
+ iframe.title = label;
31
+ iframe.loading = "lazy";
32
+ iframe.referrerPolicy = "strict-origin-when-cross-origin";
33
+ iframe.allow =
34
+ "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share";
35
+ iframe.allowFullscreen = true;
36
+ iframe.style.width = "100%";
37
+ iframe.style.height = "100%";
38
+ iframe.style.border = "0";
39
+ button.replaceWith(iframe);
40
+ };
41
+ scope.addEventListener("click", onClick);
42
+ return () => scope.removeEventListener("click", onClick);
43
+ }
44
+ //# sourceMappingURL=media.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media.service.js","sourceRoot":"","sources":["../../../src/infrastructure/media/media.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAAiB;IACvD,IAAI,CAAC,YAAY,EAAE;QAAE,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC;IAC5C,MAAM,KAAK,GAAG,IAAI,IAAI,QAAQ,CAAC;IAE/B,MAAM,OAAO,GAAG,CAAC,KAAY,EAAQ,EAAE;QACtC,MAAM,MAAM,GAAG,KAAK,CAAC,MAA4B,CAAC;QAClD,MAAM,MAAM,GAAG,MAAM,EAAE,OAAO,EAAE,CAAC,qBAAqB,CAA6B,CAAC;QACpF,IAAI,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,OAAO;QAC/C,MAAM,GAAG,GAAG,MAAM,CAAC,YAAY,CAAC,mBAAmB,CAAC,CAAC;QACrD,IAAI,CAAC,GAAG;YAAE,OAAO;QACjB,MAAM,KAAK,GAAG,MAAM,CAAC,YAAY,CAAC,YAAY,CAAC,IAAI,gBAAgB,CAAC;QACpE,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAChD,MAAM,CAAC,GAAG,GAAG,GAAG,CAAC;QACjB,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;QACrB,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC;QACxB,MAAM,CAAC,cAAc,GAAG,iCAAiC,CAAC;QAC1D,MAAM,CAAC,KAAK;YACX,qGAAqG,CAAC;QACvG,MAAM,CAAC,eAAe,GAAG,IAAI,CAAC;QAC9B,MAAM,CAAC,KAAK,CAAC,KAAK,GAAG,MAAM,CAAC;QAC5B,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC;QAC7B,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC;QAC1B,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IAC5B,CAAC,CAAC;IAEF,KAAK,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACzC,OAAO,GAAG,EAAE,CAAC,KAAK,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AAC1D,CAAC"}