@zivue/zuuid 0.1.0 → 0.2.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 (207) hide show
  1. package/CHANGELOG.md +16 -1
  2. package/README.md +291 -215
  3. package/dist/client.d.ts +16 -3
  4. package/dist/client.d.ts.map +1 -1
  5. package/dist/client.js +22 -0
  6. package/dist/entity.d.ts.map +1 -1
  7. package/dist/entity.js +9 -0
  8. package/dist/index.d.ts +11 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +11 -0
  11. package/dist/providers/comicvine/character.d.ts +2 -0
  12. package/dist/providers/comicvine/character.d.ts.map +1 -0
  13. package/dist/providers/comicvine/character.js +1 -0
  14. package/dist/providers/comicvine/constants.d.ts +8 -0
  15. package/dist/providers/comicvine/constants.d.ts.map +1 -0
  16. package/dist/providers/comicvine/constants.js +7 -0
  17. package/dist/providers/comicvine/index.d.ts +9 -0
  18. package/dist/providers/comicvine/index.d.ts.map +1 -0
  19. package/dist/providers/comicvine/index.js +8 -0
  20. package/dist/providers/comicvine/issue.d.ts +2 -0
  21. package/dist/providers/comicvine/issue.d.ts.map +1 -0
  22. package/dist/providers/comicvine/issue.js +1 -0
  23. package/dist/providers/comicvine/person.d.ts +2 -0
  24. package/dist/providers/comicvine/person.d.ts.map +1 -0
  25. package/dist/providers/comicvine/person.js +1 -0
  26. package/dist/providers/comicvine/publisher.d.ts +2 -0
  27. package/dist/providers/comicvine/publisher.d.ts.map +1 -0
  28. package/dist/providers/comicvine/publisher.js +1 -0
  29. package/dist/providers/comicvine/story-arc.d.ts +2 -0
  30. package/dist/providers/comicvine/story-arc.d.ts.map +1 -0
  31. package/dist/providers/comicvine/story-arc.js +1 -0
  32. package/dist/providers/comicvine/transform.d.ts +4 -0
  33. package/dist/providers/comicvine/transform.d.ts.map +1 -0
  34. package/dist/providers/comicvine/transform.js +63 -0
  35. package/dist/providers/comicvine/volume.d.ts +2 -0
  36. package/dist/providers/comicvine/volume.d.ts.map +1 -0
  37. package/dist/providers/comicvine/volume.js +1 -0
  38. package/dist/providers/common.d.ts +24 -0
  39. package/dist/providers/common.d.ts.map +1 -0
  40. package/dist/providers/common.js +140 -0
  41. package/dist/providers/gamesdb/constants.d.ts +4 -0
  42. package/dist/providers/gamesdb/constants.d.ts.map +1 -0
  43. package/dist/providers/gamesdb/constants.js +3 -0
  44. package/dist/providers/gamesdb/game.d.ts +7 -0
  45. package/dist/providers/gamesdb/game.d.ts.map +1 -0
  46. package/dist/providers/gamesdb/game.js +61 -0
  47. package/dist/providers/gamesdb/index.d.ts +4 -0
  48. package/dist/providers/gamesdb/index.d.ts.map +1 -0
  49. package/dist/providers/gamesdb/index.js +3 -0
  50. package/dist/providers/gamesdb/platform.d.ts +4 -0
  51. package/dist/providers/gamesdb/platform.d.ts.map +1 -0
  52. package/dist/providers/gamesdb/platform.js +23 -0
  53. package/dist/providers/jikan/anime.d.ts +2 -0
  54. package/dist/providers/jikan/anime.d.ts.map +1 -0
  55. package/dist/providers/jikan/anime.js +1 -0
  56. package/dist/providers/jikan/character.d.ts +2 -0
  57. package/dist/providers/jikan/character.d.ts.map +1 -0
  58. package/dist/providers/jikan/character.js +1 -0
  59. package/dist/providers/jikan/constants.d.ts +8 -0
  60. package/dist/providers/jikan/constants.d.ts.map +1 -0
  61. package/dist/providers/jikan/constants.js +7 -0
  62. package/dist/providers/jikan/index.d.ts +9 -0
  63. package/dist/providers/jikan/index.d.ts.map +1 -0
  64. package/dist/providers/jikan/index.js +8 -0
  65. package/dist/providers/jikan/magazine.d.ts +2 -0
  66. package/dist/providers/jikan/magazine.d.ts.map +1 -0
  67. package/dist/providers/jikan/magazine.js +1 -0
  68. package/dist/providers/jikan/manga.d.ts +2 -0
  69. package/dist/providers/jikan/manga.d.ts.map +1 -0
  70. package/dist/providers/jikan/manga.js +1 -0
  71. package/dist/providers/jikan/person.d.ts +2 -0
  72. package/dist/providers/jikan/person.d.ts.map +1 -0
  73. package/dist/providers/jikan/person.js +1 -0
  74. package/dist/providers/jikan/producer.d.ts +2 -0
  75. package/dist/providers/jikan/producer.d.ts.map +1 -0
  76. package/dist/providers/jikan/producer.js +1 -0
  77. package/dist/providers/jikan/transform.d.ts +8 -0
  78. package/dist/providers/jikan/transform.d.ts.map +1 -0
  79. package/dist/providers/jikan/transform.js +127 -0
  80. package/dist/providers/musicbrainz/artist.d.ts +4 -0
  81. package/dist/providers/musicbrainz/artist.d.ts.map +1 -0
  82. package/dist/providers/musicbrainz/artist.js +28 -0
  83. package/dist/providers/musicbrainz/constants.d.ts +10 -0
  84. package/dist/providers/musicbrainz/constants.d.ts.map +1 -0
  85. package/dist/providers/musicbrainz/constants.js +9 -0
  86. package/dist/providers/musicbrainz/helpers.d.ts +15 -0
  87. package/dist/providers/musicbrainz/helpers.d.ts.map +1 -0
  88. package/dist/providers/musicbrainz/helpers.js +71 -0
  89. package/dist/providers/musicbrainz/index.d.ts +8 -0
  90. package/dist/providers/musicbrainz/index.d.ts.map +1 -0
  91. package/dist/providers/musicbrainz/index.js +7 -0
  92. package/dist/providers/musicbrainz/label.d.ts +4 -0
  93. package/dist/providers/musicbrainz/label.d.ts.map +1 -0
  94. package/dist/providers/musicbrainz/label.js +28 -0
  95. package/dist/providers/musicbrainz/recording.d.ts +4 -0
  96. package/dist/providers/musicbrainz/recording.d.ts.map +1 -0
  97. package/dist/providers/musicbrainz/recording.js +41 -0
  98. package/dist/providers/musicbrainz/release-group.d.ts +7 -0
  99. package/dist/providers/musicbrainz/release-group.d.ts.map +1 -0
  100. package/dist/providers/musicbrainz/release-group.js +28 -0
  101. package/dist/providers/musicbrainz/release.d.ts +7 -0
  102. package/dist/providers/musicbrainz/release.d.ts.map +1 -0
  103. package/dist/providers/musicbrainz/release.js +50 -0
  104. package/dist/providers/musicbrainz/work.d.ts +4 -0
  105. package/dist/providers/musicbrainz/work.d.ts.map +1 -0
  106. package/dist/providers/musicbrainz/work.js +40 -0
  107. package/dist/providers/openfoodfacts/constants.d.ts +3 -0
  108. package/dist/providers/openfoodfacts/constants.d.ts.map +1 -0
  109. package/dist/providers/openfoodfacts/constants.js +2 -0
  110. package/dist/providers/openfoodfacts/index.d.ts +3 -0
  111. package/dist/providers/openfoodfacts/index.d.ts.map +1 -0
  112. package/dist/providers/openfoodfacts/index.js +2 -0
  113. package/dist/providers/openfoodfacts/product.d.ts +4 -0
  114. package/dist/providers/openfoodfacts/product.d.ts.map +1 -0
  115. package/dist/providers/openfoodfacts/product.js +29 -0
  116. package/dist/providers/openlibrary/author.d.ts +65 -0
  117. package/dist/providers/openlibrary/author.d.ts.map +1 -0
  118. package/dist/providers/openlibrary/author.js +228 -0
  119. package/dist/providers/openlibrary/book.d.ts +138 -0
  120. package/dist/providers/openlibrary/book.d.ts.map +1 -0
  121. package/dist/providers/openlibrary/book.js +536 -0
  122. package/dist/providers/openlibrary/client.d.ts +22 -0
  123. package/dist/providers/openlibrary/client.d.ts.map +1 -0
  124. package/dist/providers/openlibrary/client.js +61 -0
  125. package/dist/providers/openlibrary/constants.d.ts +4 -0
  126. package/dist/providers/openlibrary/constants.d.ts.map +1 -0
  127. package/dist/providers/openlibrary/constants.js +3 -0
  128. package/dist/providers/openlibrary/index.d.ts +6 -0
  129. package/dist/providers/openlibrary/index.d.ts.map +1 -0
  130. package/dist/providers/openlibrary/index.js +5 -0
  131. package/dist/providers/openlibrary/types.d.ts +23 -0
  132. package/dist/providers/openlibrary/types.d.ts.map +1 -0
  133. package/dist/providers/openlibrary/types.js +1 -0
  134. package/dist/providers/openstreetmap/constants.d.ts +6 -0
  135. package/dist/providers/openstreetmap/constants.d.ts.map +1 -0
  136. package/dist/providers/openstreetmap/constants.js +5 -0
  137. package/dist/providers/openstreetmap/index.d.ts +4 -0
  138. package/dist/providers/openstreetmap/index.d.ts.map +1 -0
  139. package/dist/providers/openstreetmap/index.js +3 -0
  140. package/dist/providers/openstreetmap/place.d.ts +4 -0
  141. package/dist/providers/openstreetmap/place.d.ts.map +1 -0
  142. package/dist/providers/openstreetmap/place.js +67 -0
  143. package/dist/providers/openstreetmap/venue.d.ts +2 -0
  144. package/dist/providers/openstreetmap/venue.d.ts.map +1 -0
  145. package/dist/providers/openstreetmap/venue.js +1 -0
  146. package/dist/providers/podcast/constants.d.ts +3 -0
  147. package/dist/providers/podcast/constants.d.ts.map +1 -0
  148. package/dist/providers/podcast/constants.js +2 -0
  149. package/dist/providers/podcast/index.d.ts +3 -0
  150. package/dist/providers/podcast/index.d.ts.map +1 -0
  151. package/dist/providers/podcast/index.js +2 -0
  152. package/dist/providers/podcast/podcast.d.ts +4 -0
  153. package/dist/providers/podcast/podcast.d.ts.map +1 -0
  154. package/dist/providers/podcast/podcast.js +30 -0
  155. package/dist/providers/setlistfm/artist.d.ts +2 -0
  156. package/dist/providers/setlistfm/artist.d.ts.map +1 -0
  157. package/dist/providers/setlistfm/artist.js +1 -0
  158. package/dist/providers/setlistfm/constants.d.ts +5 -0
  159. package/dist/providers/setlistfm/constants.d.ts.map +1 -0
  160. package/dist/providers/setlistfm/constants.js +4 -0
  161. package/dist/providers/setlistfm/index.d.ts +6 -0
  162. package/dist/providers/setlistfm/index.d.ts.map +1 -0
  163. package/dist/providers/setlistfm/index.js +5 -0
  164. package/dist/providers/setlistfm/setlist.d.ts +2 -0
  165. package/dist/providers/setlistfm/setlist.d.ts.map +1 -0
  166. package/dist/providers/setlistfm/setlist.js +1 -0
  167. package/dist/providers/setlistfm/transform.d.ts +7 -0
  168. package/dist/providers/setlistfm/transform.d.ts.map +1 -0
  169. package/dist/providers/setlistfm/transform.js +122 -0
  170. package/dist/providers/setlistfm/venue.d.ts +2 -0
  171. package/dist/providers/setlistfm/venue.d.ts.map +1 -0
  172. package/dist/providers/setlistfm/venue.js +1 -0
  173. package/dist/providers/ticketmaster/attraction.d.ts +2 -0
  174. package/dist/providers/ticketmaster/attraction.d.ts.map +1 -0
  175. package/dist/providers/ticketmaster/attraction.js +1 -0
  176. package/dist/providers/ticketmaster/constants.d.ts +5 -0
  177. package/dist/providers/ticketmaster/constants.d.ts.map +1 -0
  178. package/dist/providers/ticketmaster/constants.js +4 -0
  179. package/dist/providers/ticketmaster/event.d.ts +2 -0
  180. package/dist/providers/ticketmaster/event.d.ts.map +1 -0
  181. package/dist/providers/ticketmaster/event.js +1 -0
  182. package/dist/providers/ticketmaster/index.d.ts +6 -0
  183. package/dist/providers/ticketmaster/index.d.ts.map +1 -0
  184. package/dist/providers/ticketmaster/index.js +5 -0
  185. package/dist/providers/ticketmaster/transform.d.ts +7 -0
  186. package/dist/providers/ticketmaster/transform.d.ts.map +1 -0
  187. package/dist/providers/ticketmaster/transform.js +89 -0
  188. package/dist/providers/ticketmaster/venue.d.ts +2 -0
  189. package/dist/providers/ticketmaster/venue.d.ts.map +1 -0
  190. package/dist/providers/ticketmaster/venue.js +1 -0
  191. package/dist/providers/tmdb/movie.d.ts.map +1 -1
  192. package/dist/providers/tmdb/movie.js +9 -22
  193. package/dist/providers/tmdb/tv.d.ts.map +1 -1
  194. package/dist/providers/tmdb/tv.js +4 -2
  195. package/dist/providers/wger/constants.d.ts +4 -0
  196. package/dist/providers/wger/constants.d.ts.map +1 -0
  197. package/dist/providers/wger/constants.js +3 -0
  198. package/dist/providers/wger/equipment.d.ts +4 -0
  199. package/dist/providers/wger/equipment.d.ts.map +1 -0
  200. package/dist/providers/wger/equipment.js +22 -0
  201. package/dist/providers/wger/exercise.d.ts +4 -0
  202. package/dist/providers/wger/exercise.d.ts.map +1 -0
  203. package/dist/providers/wger/exercise.js +42 -0
  204. package/dist/providers/wger/index.d.ts +4 -0
  205. package/dist/providers/wger/index.d.ts.map +1 -0
  206. package/dist/providers/wger/index.js +3 -0
  207. package/package.json +201 -5
package/README.md CHANGED
@@ -1,10 +1,12 @@
1
1
  # @zivue/zuuid
2
2
 
3
- TypeScript helpers for fetching, searching, and transforming Zuuid datasets.
3
+ Search, fetch, and normalize media metadata from external providers into a shared Zuuid data shape.
4
4
 
5
- Zuuid is the metadata, search, and indexing substrate for Zivue/Stareto. This package focuses on the client-side model: provider-stable UUIDs, source records, and normalized Zuuid datasets.
5
+ This package is meant to be used by apps that need provider-backed lookup and transformation, but do not want provider-specific response shapes leaking through the app.
6
6
 
7
- Storage, caching, object keys, and persistence belong in a layer outside this package.
7
+ TMDB and Open Library include live search/fetch clients. Additional providers currently expose source-record transformers: you provide the raw provider payload, and this package normalizes it into `ZuuidData`.
8
+
9
+ Storage, caching, indexing, review state, object-store keys, and persistence belong in a layer outside this package.
8
10
 
9
11
  ## Install
10
12
 
@@ -21,6 +23,9 @@ const zuuid = createZuuidClient({
21
23
  providers: {
22
24
  tmdb: {
23
25
  bearerToken: process.env.TMDB_BEARER_TOKEN!
26
+ },
27
+ openlibrary: {
28
+ // Open Library does not require credentials.
24
29
  }
25
30
  }
26
31
  });
@@ -29,98 +34,97 @@ const search = await zuuid.movie.tmdb?.search({ query: "Fight Club" });
29
34
  const selected = search?.results[0];
30
35
  const movie = selected ? await zuuid.movie.tmdb?.fetch({ id: selected.source.value }) : undefined;
31
36
 
32
- console.log(movie?.zuuid);
33
- ```
34
-
35
- ## Generate A ZUUID
36
-
37
- Zuuid generation is UUID v5:
38
-
39
- ```txt
40
- uuid_v5(provider_namespace, "<normalized-category>:<external_id>")
37
+ console.log(movie?.primaryTitle);
38
+ // Fight Club
41
39
  ```
42
40
 
43
- ```ts
44
- import { providerZuuid } from "@zivue/zuuid";
45
-
46
- const zuuid = await providerZuuid({
47
- provider: "tmdb",
48
- category: "movie",
49
- externalId: "550"
50
- });
41
+ ## What You Get
51
42
 
52
- console.log(zuuid);
53
- // 1706d641-d381-5618-9425-d8cd8b35f898
54
- ```
43
+ The package has two main output shapes:
55
44
 
56
- `category` is trimmed and lowercased. `externalId` is trimmed but otherwise preserved.
45
+ - `SearchResponse<ZuuidSearchResult>` for search/list screens.
46
+ - `ZuuidData` for full fetched and transformed datasets.
57
47
 
58
- ## Zuuid Dataset
48
+ Search is lightweight and paginated. Fetch returns the full normalized dataset for a selected provider ID.
59
49
 
60
50
  ```ts
61
- import { createZuuidData } from "@zivue/zuuid";
62
-
63
- const dataset = createZuuidData({
64
- zuuid: "1706d641-d381-5618-9425-d8cd8b35f898",
65
- category: "movie",
66
- primaryTitle: "Fight Club"
67
- });
68
- ```
51
+ const movies = await zuuid.movie.tmdb?.search({ query: "Fight Club" });
69
52
 
70
- The dataset shape is intentionally flat:
53
+ console.log(movies?.pagination);
54
+ // { page: 1, totalPages: 10, totalResults: 190 }
71
55
 
72
- ```ts
73
- type ZuuidData = {
74
- zuuid: string;
75
- kind: EntityKind;
76
- category: string;
77
- primaryTitle: string;
78
- primaryDate?: string;
79
- rating?: number;
80
- cover?: string;
81
- aliases: Alias[];
82
- descriptions: Description[];
83
- details: Detail[];
84
- media: MediaAsset[];
85
- relations: EntityRelation[];
86
- recommendations: RecommendationEdge[];
87
- tags: string[];
88
- externalIds: ExternalId[];
89
- provenance: Provenance[];
90
- };
56
+ console.log(movies?.results[0]);
57
+ // {
58
+ // id: "1706d641-d381-5618-9425-d8cd8b35f898",
59
+ // zuuid: "1706d641-d381-5618-9425-d8cd8b35f898",
60
+ // category: "movie",
61
+ // title: "Fight Club",
62
+ // date: "1999-10-15",
63
+ // cover: "https://image.tmdb.org/t/p/w500/...",
64
+ // rating: 4.2,
65
+ // weight: 20.0,
66
+ // relationType: null,
67
+ // attribute: null,
68
+ // order: null,
69
+ // source: { source: "tmdb", category: "movie", value: "550" }
70
+ // }
91
71
  ```
92
72
 
93
- Search results, relations, and recommendations use the same lightweight list item fields: `id`, `zuuid`, `category`, `title`, `date`, `cover`, `rating`, `weight`, `relationType`, `attribute`, and `order`. That lets transformed datasets and search results carry useful related-entity context before those related entities are fetched as full Zuuid datasets.
94
-
95
- Backend-only concerns such as record versions, flags, review state, index state, and object-store metadata are intentionally not part of this package's dataset shape.
96
-
97
- ## Source Metadata
98
-
99
- Source records are provider records before they are transformed into canonical entities. They carry the external provider key, raw JSON payload, content hash, and observation timestamps.
73
+ The full fetched dataset is flat and provider-normalized:
100
74
 
101
75
  ```ts
102
- import { attachSourceMetadata, createSourceRecord } from "@zivue/zuuid";
76
+ const movie = await zuuid.movie.tmdb?.fetch({ id: "550" });
103
77
 
104
- const source = await createSourceRecord({
105
- source: { provider: "tmdb", category: "movie", externalId: "550" },
106
- payload: { title: "Fight Club" },
107
- observedAt: "2026-05-21T00:00:00.000Z"
108
- });
109
-
110
- const withSource = attachSourceMetadata(dataset, source);
78
+ console.log(movie);
79
+ // {
80
+ // zuuid,
81
+ // kind: "watch",
82
+ // category: "movie",
83
+ // primaryTitle: "Fight Club",
84
+ // primaryDate: "1999-10-15",
85
+ // rating,
86
+ // cover,
87
+ // aliases,
88
+ // descriptions,
89
+ // details,
90
+ // media,
91
+ // relations,
92
+ // recommendations,
93
+ // tags,
94
+ // externalIds,
95
+ // provenance
96
+ // }
111
97
  ```
112
98
 
113
- `attachSourceMetadata` returns a new dataset with `externalIds` and `provenance` updated.
99
+ Search results, relations, and recommendations share the same lightweight list item fields: `id`, `zuuid`, `category`, `title`, `date`, `cover`, `rating`, `weight`, `relationType`, `attribute`, and `order`.
114
100
 
115
- ## TMDB Movies, TV, And People
101
+ Ratings are normalized to a `0-5` scale when the provider exposes a compatible numeric score. The original provider score is preserved in details as `provider_rating` for providers whose native scale differs.
116
102
 
117
- The first provider module supports fetching and transforming TMDB movies, TV shows, and people.
103
+ ## Supported Providers
118
104
 
119
105
  | Provider | Category | Search | Fetch | Transform |
120
106
  | --- | --- | --- | --- | --- |
121
107
  | TMDB | movie | yes | yes | yes |
122
108
  | TMDB | tv | yes | yes | yes |
123
109
  | TMDB | person | yes | yes | yes |
110
+ | Open Library | book | yes | yes | yes |
111
+ | Open Library | author | yes | yes | yes |
112
+ | ComicVine | volume, issue, story_arc, character, person, publisher | no | no | yes |
113
+ | GamesDB | game, platform | no | no | yes |
114
+ | Jikan | anime, manga, producer, magazine, character, person | no | no | yes |
115
+ | MusicBrainz | release, release-group, recording, artist, label, work | no | no | yes |
116
+ | OpenFoodFacts | product | no | no | yes |
117
+ | OpenStreetMap | city, country, place, venue | no | no | yes |
118
+ | Podcast / iTunes | podcast | no | no | yes |
119
+ | Setlist.fm | setlist, artist, venue | no | no | yes |
120
+ | Ticketmaster | event, attraction, venue | no | no | yes |
121
+ | Wger | exercise, equipment | no | no | yes |
122
+
123
+ For transformer-only providers, "Search" and "Fetch" are marked `no` because this package does not perform those HTTP requests yet. Use your own provider client or stored payloads, wrap the payload in a `SourceRecord`, and call the category transformer.
124
+
125
+ ## TMDB Credentials
126
+
127
+ `TmdbProvider` accepts either a TMDB API Read Access Token or a v3 API key:
124
128
 
125
129
  ```ts
126
130
  import { TmdbProvider } from "@zivue/zuuid/providers/tmdb";
@@ -129,240 +133,312 @@ const tmdb = new TmdbProvider({
129
133
  bearerToken: process.env.TMDB_BEARER_TOKEN!
130
134
  });
131
135
 
132
- const movie = await tmdb.fetchMovie({ id: 550 });
133
- const tv = await tmdb.fetchTv({ id: 1399 });
134
- const person = await tmdb.fetchPerson({ id: 287 });
135
-
136
- console.log(movie?.zuuid);
137
- // 1706d641-d381-5618-9425-d8cd8b35f898
136
+ // or
137
+ const tmdbWithApiKey = new TmdbProvider({
138
+ apiKey: process.env.TMDB_API_KEY!
139
+ });
138
140
  ```
139
141
 
140
- You can also split fetching from transformation:
141
-
142
- ```ts
143
- import { TmdbProvider, transformTmdbMovie, transformTmdbPerson, transformTmdbTv } from "@zivue/zuuid/providers/tmdb";
142
+ TMDB bearer tokens usually start with `eyJ...`; v3 API keys are shorter hex-like strings.
144
143
 
145
- const source = await tmdb.fetchMovieSourceRecord({ id: 550 });
146
- const movie = source ? await transformTmdbMovie(source) : undefined;
144
+ ## Search
147
145
 
148
- const tvSource = await tmdb.fetchTvSourceRecord({ id: 1399 });
149
- const tv = tvSource ? await transformTmdbTv(tvSource) : undefined;
146
+ Use the category-first client facade when your app may have several providers:
150
147
 
151
- const personSource = await tmdb.fetchPersonSourceRecord({ id: 287 });
152
- const person = personSource ? await transformTmdbPerson(personSource) : undefined;
148
+ ```ts
149
+ const movies = await zuuid.movie.tmdb?.search({ query: "Fight Club" });
150
+ const tvShows = await zuuid.tv.tmdb?.search({ query: "Game of Thrones" });
151
+ const people = await zuuid.people.tmdb?.search({ query: "Brad Pitt" });
152
+ const books = await zuuid.read.openlibrary?.search({ query: "The Lord of the Rings" });
153
+ const authors = await zuuid.people.openlibrary?.search({ query: "J. K. Rowling" });
153
154
  ```
154
155
 
155
- Search returns unified lightweight candidates. Use search to find candidate IDs, then fetch the selected item for the full transformed dataset:
156
+ Provider methods are also available directly:
156
157
 
157
158
  ```ts
158
159
  const movies = await tmdb.searchMovies({ query: "Fight Club" });
159
160
  const tvShows = await tmdb.searchTv({ query: "Game of Thrones" });
160
161
  const people = await tmdb.searchPeople({ query: "Brad Pitt" });
162
+ ```
161
163
 
162
- console.log(movies.pagination);
163
- // { page: 1, totalPages: 10, totalResults: 190 }
164
-
165
- const selectedMovie = movies.results[0];
166
- console.log(selectedMovie);
167
- // {
168
- // id: "1706d641-d381-5618-9425-d8cd8b35f898",
169
- // zuuid: "1706d641-d381-5618-9425-d8cd8b35f898",
170
- // category: "movie",
171
- // title: "Fight Club",
172
- // date: "1999-10-15",
173
- // cover: "https://image.tmdb.org/t/p/w500/...",
174
- // rating: 8.4,
175
- // weight: 20.0,
176
- // relationType: null,
177
- // attribute: null,
178
- // order: null,
179
- // source: { source: "tmdb", category: "movie", value: "550" }
180
- // }
164
+ ```ts
165
+ import { OpenLibraryProvider } from "@zivue/zuuid/providers/openlibrary";
181
166
 
182
- const fullMovie = selectedMovie ? await tmdb.fetchMovie({ id: selectedMovie.source.value }) : undefined;
167
+ const openlibrary = new OpenLibraryProvider();
168
+ const books = await openlibrary.searchBooks({ query: "The Lord of the Rings" });
169
+ const authors = await openlibrary.searchAuthors({ query: "J. K. Rowling" });
183
170
  ```
184
171
 
185
- Raw search source records are also available:
172
+ Search options include pagination and common TMDB filters:
186
173
 
187
174
  ```ts
188
- const rawMovies = await tmdb.searchMovieSourceRecords({ query: "Fight Club" });
175
+ const movies = await tmdb.searchMovies({
176
+ query: "Fight Club",
177
+ page: 2,
178
+ includeAdult: false,
179
+ primaryReleaseYear: 1999
180
+ });
189
181
  ```
190
182
 
191
- Category-specific imports are also available:
183
+ If you need the raw TMDB search payload wrapped as source records:
192
184
 
193
185
  ```ts
194
- import { transformTmdbMovie } from "@zivue/zuuid/providers/tmdb/movie";
195
- import { transformTmdbTv } from "@zivue/zuuid/providers/tmdb/tv";
196
- import { transformTmdbPerson } from "@zivue/zuuid/providers/tmdb/person";
186
+ const rawMovies = await tmdb.searchMovieSourceRecords({ query: "Fight Club" });
197
187
  ```
198
188
 
199
- `TmdbProvider` accepts either `{ bearerToken }` or `{ apiKey }`. TMDB bearer tokens are API Read Access Tokens and usually start with `eyJ...`; v3 API keys are shorter hex-like strings.
189
+ ## Fetch
200
190
 
201
- ## Client Instantiation
202
-
203
- For applications with multiple providers, use `createZuuidClient` to wire provider config once:
191
+ Fetch returns transformed `ZuuidData`:
204
192
 
205
193
  ```ts
206
- import { createZuuidClient } from "@zivue/zuuid";
207
-
208
- const zuuid = createZuuidClient({
209
- providers: {
210
- tmdb: {
211
- bearerToken: process.env.TMDB_BEARER_TOKEN!
212
- }
213
- // Future movie providers can sit beside tmdb, e.g. omdb.
214
- }
215
- });
216
-
217
194
  const movie = await zuuid.movie.tmdb?.fetch({ id: 550 });
218
195
  const tv = await zuuid.tv.tmdb?.fetch({ id: 1399 });
219
196
  const person = await zuuid.people.tmdb?.fetch({ id: 287 });
197
+ const book = await zuuid.read.openlibrary?.fetch({ id: "OL82563W" });
198
+ const author = await zuuid.people.openlibrary?.fetch({ id: "OL23919A" });
220
199
  ```
221
200
 
222
- The config is provider-keyed because apps usually manage credentials per provider. The client facade is category-first, so multiple movie providers can live under `zuuid.movie`:
201
+ Direct provider methods are equivalent:
223
202
 
224
203
  ```ts
225
- zuuid.movie.tmdb?.fetch({ id: 550 });
226
- zuuid.tv.tmdb?.fetch({ id: 1399 });
227
- zuuid.people.tmdb?.fetch({ id: 287 });
204
+ const movie = await tmdb.fetchMovie({ id: 550 });
205
+ const tv = await tmdb.fetchTv({ id: 1399 });
206
+ const person = await tmdb.fetchPerson({ id: 287 });
228
207
 
229
- zuuid.movie.tmdb?.search({ query: "Fight Club" });
230
- zuuid.tv.tmdb?.search({ query: "Game of Thrones" });
231
- zuuid.people.tmdb?.search({ query: "Brad Pitt" });
232
- // later: zuuid.movie.omdb?.fetch(...)
208
+ const book = await openlibrary.fetchBook({ id: "OL82563W" });
209
+ const author = await openlibrary.fetchAuthor({ id: "OL23919A" });
233
210
  ```
234
211
 
235
- The client is stateless: it does not cache, persist, schedule, or read environment variables. It only closes over provider configuration and exposes category/provider methods.
212
+ ## Raw Source Records And Transform
236
213
 
237
- ## API
214
+ For debugging, caching in your own layer, or custom transform timing, split fetch from transform:
238
215
 
239
- ## Package Structure
216
+ ```ts
217
+ import { transformTmdbMovie } from "@zivue/zuuid/providers/tmdb";
240
218
 
241
- The source is split by Zuuid responsibility:
219
+ const source = await tmdb.fetchMovieSourceRecord({ id: 550 });
220
+ const movie = source ? await transformTmdbMovie(source, tmdb.transformOptions()) : undefined;
221
+ ```
242
222
 
243
- - `identity.ts`: provider namespaces and UUID v5 ZUUID generation
244
- - `entity.ts`: flat Zuuid data types and category helpers
245
- - `client.ts`: stateless package instantiator for configured providers
246
- - `providers/<provider>/index.ts`: provider module barrel
247
- - `providers/<provider>/client.ts`: shared provider client/config
248
- - `providers/<provider>/<category>.ts`: category-specific fetch and transform helpers
249
- - `providers/tmdb/movie.ts`: TMDB movie fetch and transform helpers
250
- - `providers/tmdb/tv.ts`: TMDB TV fetch and transform helpers
251
- - `providers/tmdb/person.ts`: TMDB person fetch and transform helpers
252
- - `source.ts`: source records, external IDs, and provenance
253
- - `hash.ts`: stable payload hashing
254
- - `uuid.ts`: UUID parsing/normalization and UUID v5 internals
255
- - `types.ts`: shared JSON value types
256
- - `index.ts`: public barrel exports
223
+ The source record contains the raw payload:
257
224
 
258
- ### `providerZuuid(input)`
225
+ ```ts
226
+ console.log(source?.payload);
227
+ ```
259
228
 
260
- Generates a deterministic ZUUID for a provider/category/external ID.
229
+ The transform result is normalized `ZuuidData`:
261
230
 
262
- ### `providerNamespace(provider)`
231
+ ```ts
232
+ console.log(movie?.primaryTitle);
233
+ console.log(movie?.externalIds);
234
+ console.log(movie?.provenance);
235
+ ```
263
236
 
264
- Returns the UUID namespace configured for a known provider.
237
+ For transformer-only providers:
265
238
 
266
- ### `createZuuidData(input)`
239
+ ```ts
240
+ import { createSourceRecord, transformGamesDbGame } from "@zivue/zuuid";
267
241
 
268
- Creates a normalized Zuuid dataset.
242
+ const source = await createSourceRecord({
243
+ source: { provider: "gamesdb", category: "game", externalId: "17444" },
244
+ payload: rawGamesDbPayload
245
+ });
269
246
 
270
- ### `createSourceRecord(input)`
247
+ const game = await transformGamesDbGame(source);
248
+ ```
271
249
 
272
- Creates a source record and computes the SHA-256 payload hash used for provenance.
250
+ Category-specific imports are available:
273
251
 
274
- ### `attachSourceMetadata(dataset, sourceRecord, confidence?)`
252
+ ```ts
253
+ import { transformTmdbMovie } from "@zivue/zuuid/providers/tmdb/movie";
254
+ import { transformTmdbTv } from "@zivue/zuuid/providers/tmdb/tv";
255
+ import { transformTmdbPerson } from "@zivue/zuuid/providers/tmdb/person";
256
+ import { transformOpenLibraryBook } from "@zivue/zuuid/providers/openlibrary/book";
257
+ import { transformOpenLibraryAuthor } from "@zivue/zuuid/providers/openlibrary/author";
258
+ import { transformMusicBrainzReleaseGroup } from "@zivue/zuuid/providers/musicbrainz/release-group";
259
+ import { transformComicVineIssue } from "@zivue/zuuid/providers/comicvine/issue";
260
+ import { transformJikanProducer } from "@zivue/zuuid/providers/jikan/producer";
261
+ import { transformOpenStreetMapVenue } from "@zivue/zuuid/providers/openstreetmap/venue";
262
+ import { transformWgerEquipment } from "@zivue/zuuid/providers/wger/equipment";
263
+ ```
275
264
 
276
- Returns a new dataset with external ID and provenance attached.
265
+ ## Data Model
277
266
 
278
- ### `TmdbProvider`
267
+ `ZuuidData` is the full normalized dataset:
279
268
 
280
- Fetches TMDB source records and transforms them into `ZuuidData`.
269
+ ```ts
270
+ type ZuuidData = {
271
+ zuuid: string;
272
+ kind: EntityKind;
273
+ category: string;
274
+ primaryTitle: string;
275
+ primaryDate?: string;
276
+ rating?: number;
277
+ cover?: string;
278
+ aliases: Alias[];
279
+ descriptions: Description[];
280
+ details: Detail[];
281
+ media: MediaAsset[];
282
+ relations: EntityRelation[];
283
+ recommendations: RecommendationEdge[];
284
+ tags: string[];
285
+ externalIds: ExternalId[];
286
+ provenance: Provenance[];
287
+ };
288
+ ```
281
289
 
282
- ### `transformTmdbMovie(sourceRecord, options?)`
290
+ `Detail.value` can be any JSON value, so details can hold strings, numbers, booleans, arrays, or structured objects without duplicating `value` and `data` fields.
283
291
 
284
- Transforms an already-fetched TMDB movie source record into `ZuuidData`.
292
+ ## ZUUIDs
285
293
 
286
- ### `transformTmdbTv(sourceRecord, options?)`
294
+ Every fetched dataset and unified search result includes a `zuuid`. This is a deterministic UUID v5 generated from the provider namespace, category, and external ID. It gives your app a stable cross-provider identifier while the provider's own ID remains available in `source` or `externalIds`.
287
295
 
288
- Transforms an already-fetched TMDB TV source record into `ZuuidData`.
296
+ ```ts
297
+ import { providerZuuid } from "@zivue/zuuid";
289
298
 
290
- ### `transformTmdbPerson(sourceRecord, options?)`
299
+ const zuuid = await providerZuuid({
300
+ provider: "tmdb",
301
+ category: "movie",
302
+ externalId: "550"
303
+ });
304
+ ```
291
305
 
292
- Transforms an already-fetched TMDB person source record into `ZuuidData`.
306
+ Most applications do not need to call `providerZuuid` directly; search and fetch do it internally.
293
307
 
294
- ### `categoryFor(value)`
308
+ ## Client Design
295
309
 
296
- Normalizes a category and derives its entity kind.
310
+ The client is stateless. It does not cache, persist, schedule, read environment variables, or write files. It only closes over provider configuration and exposes category/provider methods:
297
311
 
298
- ### `kindForCategory(category)`
312
+ ```ts
313
+ zuuid.movie.tmdb?.search({ query: "Fight Club" });
314
+ zuuid.movie.tmdb?.fetch({ id: 550 });
299
315
 
300
- Maps categories such as `movie`, `book`, `game`, `restaurant`, and `person` to broad kinds such as `watch`, `read`, `play`, `visit`, and `people`.
316
+ zuuid.tv.tmdb?.search({ query: "Game of Thrones" });
317
+ zuuid.tv.tmdb?.fetch({ id: 1399 });
301
318
 
302
- ## Development
319
+ zuuid.people.tmdb?.search({ query: "Brad Pitt" });
320
+ zuuid.people.tmdb?.fetch({ id: 287 });
321
+ zuuid.people.openlibrary?.search({ query: "J. K. Rowling" });
322
+ zuuid.people.openlibrary?.fetch({ id: "OL23919A" });
303
323
 
304
- ```sh
305
- npm install
306
- npm test
324
+ zuuid.read.openlibrary?.search({ query: "The Lord of the Rings" });
325
+ zuuid.read.openlibrary?.fetch({ id: "OL82563W" });
307
326
  ```
308
327
 
309
- ## Try It
328
+ ## Example Scripts
310
329
 
311
- Fetch and transform TMDB movie `550`:
330
+ The examples read `.env` from the repo root:
312
331
 
313
332
  ```sh
314
- TMDB_BEARER_TOKEN=... npm run example:tmdb-fetch -- movie 550
333
+ TMDB_BEARER_TOKEN=...
334
+ # or
335
+ TMDB_READ_ACCESS_TOKEN=...
336
+ # or
337
+ TMDB_API_KEY=...
315
338
  ```
316
339
 
317
- or:
340
+ Search a provider and print unified search results:
318
341
 
319
342
  ```sh
320
- TMDB_API_KEY=... npm run example:tmdb-fetch -- movie 550
343
+ npm run example:search -- movie "Fight Club"
344
+ npm run example:search -- tv "Game of Thrones"
345
+ npm run example:search -- people "Brad Pitt"
346
+ npm run example:search -- book "The Lord of the Rings"
347
+ npm run example:search -- author "J. K. Rowling"
321
348
  ```
322
349
 
323
- Fetch and transform TMDB TV show `1399`:
350
+ Fetch and transform a selected provider ID:
324
351
 
325
352
  ```sh
326
- TMDB_BEARER_TOKEN=... npm run example:tmdb-fetch -- tv 1399
353
+ npm run example:fetch -- movie 550
354
+ npm run example:fetch -- tv 1399
355
+ npm run example:fetch -- people 287
356
+ npm run example:fetch -- book OL82563W
357
+ npm run example:fetch -- author OL23919A
327
358
  ```
328
359
 
329
- Fetch and transform TMDB person `287`:
360
+ Fetch fixture-backed transformer examples:
330
361
 
331
362
  ```sh
332
- TMDB_BEARER_TOKEN=... npm run example:tmdb-fetch -- people 287
363
+ npm run example:fetch -- gamesdb:game 17444
364
+ npm run example:fetch -- gamesdb:platform 6
365
+ npm run example:fetch -- musicbrainz:release-group aaa50249-1e6b-3910-b830-7e2fb622a8c4
366
+ npm run example:fetch -- musicbrainz:recording 0b5d8c0f-4975-4e44-9e67-0a5f1b5939f6
367
+ npm run example:fetch -- comicvine:issue 101
368
+ npm run example:fetch -- comicvine:story_arc 201
369
+ npm run example:fetch -- jikan:producer 14
370
+ npm run example:fetch -- jikan:magazine 1
371
+ npm run example:fetch -- openfoodfacts:product 3017620422003
372
+ npm run example:fetch -- openstreetmap:venue W123456
373
+ npm run example:fetch -- wger:equipment 7
333
374
  ```
334
375
 
335
- Search TMDB and print unified search results:
376
+ `example:fetch` writes both raw and transformed JSON:
336
377
 
337
- ```sh
338
- npm run example:tmdb-search -- movie "Fight Club"
339
- npm run example:tmdb-search -- tv "Game of Thrones"
340
- npm run example:tmdb-search -- people "Brad Pitt"
378
+ ```text
379
+ data/<provider>/<category>/<id>.raw.json
380
+ data/<provider>/<category>/<id>.zuuid.json
341
381
  ```
342
382
 
343
- The example also reads `.env` from the repo root:
383
+ `example:search` writes raw and transformed search JSON:
344
384
 
345
- ```sh
346
- TMDB_BEARER_TOKEN=...
347
- # or
348
- TMDB_READ_ACCESS_TOKEN=...
349
- # or
350
- TMDB_API_KEY=...
385
+ ```text
386
+ data/<provider>/search/<category>/<query>.raw-search.json
387
+ data/<provider>/search/<category>/<query>.zuuid-search.json
351
388
  ```
352
389
 
353
- If both token and API key are present, the example uses the bearer token first. TMDB's API Read Access Token usually starts with `eyJ...`; the v3 API key is a shorter hex-like string.
354
-
355
- If `TMDB_API_KEY` accidentally contains a token starting with `eyJ`, the example treats it as a bearer token and sends it as `Authorization: Bearer ...`.
390
+ ## API Reference
391
+
392
+ Core exports:
393
+
394
+ - `createZuuidClient(config)`
395
+ - `providerZuuid(input)`
396
+ - `providerNamespace(provider)`
397
+ - `categoryFor(value)`
398
+ - `kindForCategory(category)`
399
+ - `createSourceRecord(input)`
400
+ - `attachSourceMetadata(dataset, sourceRecord, confidence?)`
401
+
402
+ Provider exports:
403
+
404
+ - `TmdbProvider`
405
+ - `OpenLibraryProvider`
406
+ - `transformTmdbMovie(sourceRecord, options?)`
407
+ - `transformTmdbTv(sourceRecord, options?)`
408
+ - `transformTmdbPerson(sourceRecord, options?)`
409
+ - `transformOpenLibraryBook(sourceRecord, options?)`
410
+ - `transformOpenLibraryAuthor(sourceRecord, options?)`
411
+ - `transformComicVine(sourceRecord)`
412
+ - `transformGamesDbGame(sourceRecord, options?)`
413
+ - `transformGamesDbPlatform(sourceRecord)`
414
+ - `transformJikan(sourceRecord)`
415
+ - `transformMusicBrainzRelease(sourceRecord, options?)`
416
+ - `transformMusicBrainzReleaseGroup(sourceRecord, options?)`
417
+ - `transformMusicBrainzRecording(sourceRecord)`
418
+ - `transformMusicBrainzArtist(sourceRecord)`
419
+ - `transformMusicBrainzLabel(sourceRecord)`
420
+ - `transformMusicBrainzWork(sourceRecord)`
421
+ - `transformOpenFoodFactsProduct(sourceRecord)`
422
+ - `transformOpenStreetMapPlace(sourceRecord)`
423
+ - `transformPodcast(sourceRecord)`
424
+ - `transformSetlistFm(sourceRecord)`
425
+ - `transformTicketmaster(sourceRecord)`
426
+ - `transformWgerExercise(sourceRecord)`
427
+ - `transformWgerEquipment(sourceRecord)`
428
+ - `searchTmdbMovies(provider, input, options?)`
429
+ - `searchTmdbTv(provider, input, options?)`
430
+ - `searchTmdbPeople(provider, input, options?)`
431
+ - `searchOpenLibraryBooks(provider, input, options?)`
432
+ - `searchOpenLibraryAuthors(provider, input, options?)`
356
433
 
357
- The example writes debug output to:
434
+ ## Development
358
435
 
359
- ```txt
360
- data/tmdb/movie/550.raw.json
361
- data/tmdb/movie/550.zuuid.json
362
- data/tmdb/tv/1399.raw.json
363
- data/tmdb/tv/1399.zuuid.json
364
- data/tmdb/people/287.raw.json
365
- data/tmdb/people/287.zuuid.json
366
- data/tmdb/search/movie/fight-club.zuuid-search.json
367
- data/tmdb/search/movie/fight-club.raw-search.json
436
+ ```sh
437
+ npm install
438
+ npm test
439
+ npm pack --dry-run
368
440
  ```
441
+
442
+ ## Package Boundary
443
+
444
+ This package intentionally does not include storage, caching, object-store metadata, record versions, review state, index state, or backend flags. Those concerns should live in the consuming application or service.