@eightyfourthousand/client-graphql 2026.3.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 (54) hide show
  1. package/codegen.ts +34 -0
  2. package/package.json +25 -0
  3. package/project.json +21 -0
  4. package/src/index.ts +79 -0
  5. package/src/lib/client-ssr.ts +44 -0
  6. package/src/lib/client.ts +58 -0
  7. package/src/lib/config.ts +21 -0
  8. package/src/lib/functions/get-bibliography-entry.ts +40 -0
  9. package/src/lib/functions/get-glossary-entry.ts +136 -0
  10. package/src/lib/functions/get-glossary-instance.ts +48 -0
  11. package/src/lib/functions/get-glossary-terms.ts +44 -0
  12. package/src/lib/functions/get-passage.ts +78 -0
  13. package/src/lib/functions/get-term-passages.ts +53 -0
  14. package/src/lib/functions/get-translation-blocks-around.ts +131 -0
  15. package/src/lib/functions/get-translation-blocks.ts +160 -0
  16. package/src/lib/functions/get-translation-imprint.ts +114 -0
  17. package/src/lib/functions/get-translation-metadata.ts +99 -0
  18. package/src/lib/functions/get-translation-passages-around.ts +153 -0
  19. package/src/lib/functions/get-translation-passages.ts +168 -0
  20. package/src/lib/functions/get-translation-titles.ts +55 -0
  21. package/src/lib/functions/get-translation-toc.ts +115 -0
  22. package/src/lib/functions/get-translation-uuids.ts +63 -0
  23. package/src/lib/functions/get-translations-metadata.ts +81 -0
  24. package/src/lib/functions/get-work-bibliography.ts +47 -0
  25. package/src/lib/functions/get-work-folios.ts +52 -0
  26. package/src/lib/functions/get-work-glossary-terms-around.ts +126 -0
  27. package/src/lib/functions/get-work-glossary-terms.ts +150 -0
  28. package/src/lib/functions/get-work-glossary.ts +54 -0
  29. package/src/lib/functions/get-works.ts +111 -0
  30. package/src/lib/functions/has-permission.ts +38 -0
  31. package/src/lib/functions/index.ts +33 -0
  32. package/src/lib/functions/save-passages.ts +76 -0
  33. package/src/lib/generated/graphql.ts +1318 -0
  34. package/src/lib/graphql/fragments/alignment.graphql +7 -0
  35. package/src/lib/graphql/fragments/annotation.graphql +7 -0
  36. package/src/lib/graphql/fragments/imprint.graphql +34 -0
  37. package/src/lib/graphql/fragments/passage.graphql +29 -0
  38. package/src/lib/graphql/fragments/title.graphql +6 -0
  39. package/src/lib/graphql/fragments/toc.graphql +40 -0
  40. package/src/lib/graphql/fragments/work.graphql +10 -0
  41. package/src/lib/graphql/queries/passages.graphql +116 -0
  42. package/src/lib/graphql/queries/work.graphql +38 -0
  43. package/src/lib/graphql/queries/works.graphql +23 -0
  44. package/src/lib/mappers/alignment.ts +34 -0
  45. package/src/lib/mappers/annotation.ts +204 -0
  46. package/src/lib/mappers/imprint.ts +79 -0
  47. package/src/lib/mappers/index.ts +7 -0
  48. package/src/lib/mappers/passage.ts +80 -0
  49. package/src/lib/mappers/title.ts +35 -0
  50. package/src/lib/mappers/toc.ts +49 -0
  51. package/src/lib/mappers/work.ts +38 -0
  52. package/src/ssr.ts +73 -0
  53. package/tsconfig.json +17 -0
  54. package/tsconfig.lib.json +14 -0
@@ -0,0 +1,168 @@
1
+ import type { GraphQLClient } from 'graphql-request';
2
+ import { gql } from 'graphql-request';
3
+ import type {
4
+ BodyItemType,
5
+ PaginationDirection,
6
+ PassagesPage,
7
+ } from '@eightyfourthousand/data-access';
8
+ import { passagesPageFromGraphQL } from '../mappers';
9
+
10
+ const GET_PASSAGES = gql`
11
+ fragment AnnotationFields on Annotation {
12
+ uuid
13
+ type
14
+ start
15
+ end
16
+ metadata
17
+ }
18
+
19
+ fragment AlignmentFields on Alignment {
20
+ folioUuid
21
+ toh
22
+ tibetan
23
+ folioNumber
24
+ volumeNumber
25
+ }
26
+
27
+ fragment PassageWithAnnotations on Passage {
28
+ uuid
29
+ workUuid
30
+ content
31
+ label
32
+ sort
33
+ type
34
+ xmlId
35
+ annotations {
36
+ ...AnnotationFields
37
+ }
38
+ alignments {
39
+ ...AlignmentFields
40
+ }
41
+ }
42
+
43
+ query GetPassages(
44
+ $uuid: ID!
45
+ $cursor: String
46
+ $limit: Int
47
+ $direction: PaginationDirection
48
+ $filter: PassageFilter
49
+ ) {
50
+ work(uuid: $uuid) {
51
+ uuid
52
+ passages(
53
+ cursor: $cursor
54
+ limit: $limit
55
+ direction: $direction
56
+ filter: $filter
57
+ ) {
58
+ nodes {
59
+ ...PassageWithAnnotations
60
+ }
61
+ pageInfo {
62
+ nextCursor
63
+ prevCursor
64
+ hasMoreAfter
65
+ hasMoreBefore
66
+ }
67
+ }
68
+ }
69
+ }
70
+ `;
71
+
72
+ type GetPassagesResponse = {
73
+ work: {
74
+ uuid: string;
75
+ passages: {
76
+ nodes: Array<{
77
+ uuid: string;
78
+ workUuid: string;
79
+ content: string;
80
+ label: string;
81
+ sort: number;
82
+ type: string;
83
+ xmlId?: string | null;
84
+ annotations: Array<{
85
+ uuid: string;
86
+ type: string;
87
+ start: number;
88
+ end: number;
89
+ metadata?: Record<string, unknown> | null;
90
+ }>;
91
+ alignments: Array<{
92
+ folioUuid: string;
93
+ toh: string;
94
+ tibetan: string;
95
+ folioNumber: number;
96
+ volumeNumber: number;
97
+ }>;
98
+ }>;
99
+ pageInfo: {
100
+ nextCursor?: string | null;
101
+ prevCursor?: string | null;
102
+ hasMoreAfter: boolean;
103
+ hasMoreBefore: boolean;
104
+ };
105
+ };
106
+ } | null;
107
+ };
108
+
109
+ /**
110
+ * Map internal pagination direction to GraphQL enum
111
+ */
112
+ function mapDirection(
113
+ direction?: PaginationDirection,
114
+ ): 'FORWARD' | 'BACKWARD' | undefined {
115
+ if (!direction) return undefined;
116
+ return direction === 'forward' ? 'FORWARD' : 'BACKWARD';
117
+ }
118
+
119
+ /**
120
+ * Get paginated passages for a translation work.
121
+ *
122
+ * NOTE: maxCharacters is not supported by the GraphQL API yet.
123
+ * This parameter is accepted for API compatibility but is ignored.
124
+ */
125
+ export async function getTranslationPassages({
126
+ client,
127
+ uuid,
128
+ type,
129
+ cursor,
130
+ maxPassages = 100,
131
+ maxCharacters: _maxCharacters,
132
+ direction,
133
+ }: {
134
+ client: GraphQLClient;
135
+ uuid: string;
136
+ type?: BodyItemType;
137
+ cursor?: string;
138
+ maxPassages?: number;
139
+ maxCharacters?: number;
140
+ direction?: PaginationDirection;
141
+ }): Promise<PassagesPage> {
142
+ try {
143
+ const response = await client.request<GetPassagesResponse>(GET_PASSAGES, {
144
+ uuid,
145
+ cursor,
146
+ limit: maxPassages,
147
+ direction: mapDirection(direction),
148
+ filter: type ? { type } : undefined,
149
+ });
150
+
151
+ if (!response.work) {
152
+ return {
153
+ passages: [],
154
+ hasMoreAfter: false,
155
+ hasMoreBefore: false,
156
+ };
157
+ }
158
+
159
+ return passagesPageFromGraphQL(response.work.passages, response.work.uuid);
160
+ } catch (error) {
161
+ console.error('Error fetching translation passages:', error);
162
+ return {
163
+ passages: [],
164
+ hasMoreAfter: false,
165
+ hasMoreBefore: false,
166
+ };
167
+ }
168
+ }
@@ -0,0 +1,55 @@
1
+ import type { GraphQLClient } from 'graphql-request';
2
+ import { gql } from 'graphql-request';
3
+ import type { Titles } from '@eightyfourthousand/data-access';
4
+ import { titlesFromGraphQL } from '../mappers';
5
+
6
+ const GET_WORK_TITLES = gql`
7
+ query GetWorkWithTitles($uuid: ID!) {
8
+ work(uuid: $uuid) {
9
+ titles {
10
+ uuid
11
+ content
12
+ language
13
+ type
14
+ }
15
+ }
16
+ }
17
+ `;
18
+
19
+ type GetWorkTitlesResponse = {
20
+ work: {
21
+ titles: Array<{
22
+ uuid: string;
23
+ content: string;
24
+ language: string;
25
+ type: string;
26
+ }>;
27
+ } | null;
28
+ };
29
+
30
+ /**
31
+ * Get all titles for a work.
32
+ */
33
+ export async function getTranslationTitles({
34
+ client,
35
+ uuid,
36
+ }: {
37
+ client: GraphQLClient;
38
+ uuid: string;
39
+ }): Promise<Titles> {
40
+ try {
41
+ const response = await client.request<GetWorkTitlesResponse>(
42
+ GET_WORK_TITLES,
43
+ { uuid },
44
+ );
45
+
46
+ if (!response.work) {
47
+ return [];
48
+ }
49
+
50
+ return titlesFromGraphQL(response.work.titles);
51
+ } catch (error) {
52
+ console.error('Error fetching translation titles:', error);
53
+ return [];
54
+ }
55
+ }
@@ -0,0 +1,115 @@
1
+ import type { GraphQLClient } from 'graphql-request';
2
+ import { gql } from 'graphql-request';
3
+ import type { Toc } from '@eightyfourthousand/data-access';
4
+ import { tocFromGraphQL } from '../mappers';
5
+
6
+ const GET_WORK_WITH_TOC = gql`
7
+ fragment TocEntryFields on TocEntry {
8
+ uuid
9
+ content
10
+ label
11
+ sort
12
+ level
13
+ section
14
+ }
15
+
16
+ fragment TocEntryNested on TocEntry {
17
+ ...TocEntryFields
18
+ children {
19
+ ...TocEntryFields
20
+ children {
21
+ ...TocEntryFields
22
+ children {
23
+ ...TocEntryFields
24
+ children {
25
+ ...TocEntryFields
26
+ children {
27
+ ...TocEntryFields
28
+ children {
29
+ ...TocEntryFields
30
+ children {
31
+ ...TocEntryFields
32
+ children {
33
+ ...TocEntryFields
34
+ children {
35
+ ...TocEntryFields
36
+ }
37
+ }
38
+ }
39
+ }
40
+ }
41
+ }
42
+ }
43
+ }
44
+ }
45
+ }
46
+
47
+ fragment TocFields on Toc {
48
+ frontMatter {
49
+ ...TocEntryNested
50
+ }
51
+ body {
52
+ ...TocEntryNested
53
+ }
54
+ backMatter {
55
+ ...TocEntryNested
56
+ }
57
+ }
58
+
59
+ query GetWorkWithToc($uuid: ID!) {
60
+ work(uuid: $uuid) {
61
+ uuid
62
+ toc {
63
+ ...TocFields
64
+ }
65
+ }
66
+ }
67
+ `;
68
+
69
+ type GetWorkWithTocResponse = {
70
+ work: {
71
+ uuid: string;
72
+ toc: {
73
+ frontMatter: TocEntryNode[];
74
+ body: TocEntryNode[];
75
+ backMatter: TocEntryNode[];
76
+ } | null;
77
+ } | null;
78
+ };
79
+
80
+ type TocEntryNode = {
81
+ uuid: string;
82
+ content: string;
83
+ label?: string | null;
84
+ sort: number;
85
+ level: number;
86
+ section: string;
87
+ children?: TocEntryNode[] | null;
88
+ };
89
+
90
+ /**
91
+ * Get the table of contents for a translation work.
92
+ */
93
+ export async function getTranslationToc({
94
+ client,
95
+ uuid,
96
+ }: {
97
+ client: GraphQLClient;
98
+ uuid: string;
99
+ }): Promise<Toc | undefined> {
100
+ try {
101
+ const response = await client.request<GetWorkWithTocResponse>(
102
+ GET_WORK_WITH_TOC,
103
+ { uuid },
104
+ );
105
+
106
+ if (!response.work?.toc) {
107
+ return undefined;
108
+ }
109
+
110
+ return tocFromGraphQL(response.work.toc);
111
+ } catch (error) {
112
+ console.error('Error fetching translation TOC:', error);
113
+ return undefined;
114
+ }
115
+ }
@@ -0,0 +1,63 @@
1
+ import type { GraphQLClient } from 'graphql-request';
2
+ import { gql } from 'graphql-request';
3
+
4
+ const DEFAULT_PAGE_SIZE = 200;
5
+
6
+ const GET_WORK_UUIDS = gql`
7
+ query GetWorkUuids($cursor: String, $limit: Int, $filter: WorkFilter) {
8
+ works(cursor: $cursor, limit: $limit, filter: $filter) {
9
+ items {
10
+ uuid
11
+ }
12
+ pageInfo {
13
+ nextCursor
14
+ hasMoreAfter
15
+ }
16
+ }
17
+ }
18
+ `;
19
+
20
+ type GetWorkUuidsResponse = {
21
+ works: {
22
+ items: Array<{ uuid: string }>;
23
+ pageInfo: {
24
+ nextCursor: string | null;
25
+ hasMoreAfter: boolean;
26
+ };
27
+ };
28
+ };
29
+
30
+ type WorkFilter = {
31
+ maxPages?: number;
32
+ };
33
+
34
+ /**
35
+ * Get work UUIDs with optional filtering.
36
+ * @param client - GraphQL client
37
+ * @param filter - Optional filter criteria (e.g., maxPages)
38
+ * @param limit - Optional limit on total results (fetches all pages if not specified)
39
+ */
40
+ export async function getTranslationUuids({
41
+ client,
42
+ filter,
43
+ limit,
44
+ }: {
45
+ client: GraphQLClient;
46
+ filter?: WorkFilter;
47
+ limit?: number;
48
+ }): Promise<string[]> {
49
+ try {
50
+ // Single request - no pagination needed for static params
51
+ const response: GetWorkUuidsResponse =
52
+ await client.request<GetWorkUuidsResponse>(GET_WORK_UUIDS, {
53
+ cursor: null,
54
+ limit: limit ?? DEFAULT_PAGE_SIZE,
55
+ filter,
56
+ });
57
+
58
+ return response.works.items.map((w) => w.uuid);
59
+ } catch (error) {
60
+ console.error('Error fetching translation UUIDs:', error);
61
+ return [];
62
+ }
63
+ }
@@ -0,0 +1,81 @@
1
+ import type { GraphQLClient } from 'graphql-request';
2
+ import { gql } from 'graphql-request';
3
+ import type { Work } from '@eightyfourthousand/data-access';
4
+ import { worksFromGraphQL } from '../mappers';
5
+
6
+ const GET_ALL_WORKS = gql`
7
+ query GetAllWorks($cursor: String, $limit: Int) {
8
+ works(cursor: $cursor, limit: $limit) {
9
+ items {
10
+ uuid
11
+ title
12
+ toh
13
+ publicationDate
14
+ publicationVersion
15
+ pages
16
+ restriction
17
+ section
18
+ }
19
+ pageInfo {
20
+ nextCursor
21
+ hasMoreAfter
22
+ }
23
+ }
24
+ }
25
+ `;
26
+
27
+ type GraphQLWork = {
28
+ uuid: string;
29
+ title: string;
30
+ toh: string[];
31
+ publicationDate: string;
32
+ publicationVersion: string;
33
+ pages: number;
34
+ restriction: boolean;
35
+ section: string;
36
+ };
37
+
38
+ type GetAllWorksResponse = {
39
+ works: {
40
+ items: GraphQLWork[];
41
+ pageInfo: {
42
+ nextCursor: string | null;
43
+ hasMoreAfter: boolean;
44
+ };
45
+ };
46
+ };
47
+
48
+ /**
49
+ * Get metadata for all published translation works (fetches all pages).
50
+ */
51
+ export async function getTranslationsMetadata({
52
+ client,
53
+ limit = 200,
54
+ }: {
55
+ client: GraphQLClient;
56
+ limit?: number;
57
+ }): Promise<Work[]> {
58
+ try {
59
+ const allWorks: GraphQLWork[] = [];
60
+ let cursor: string | null = null;
61
+
62
+ // Fetch all pages
63
+ do {
64
+ const response: GetAllWorksResponse =
65
+ await client.request<GetAllWorksResponse>(GET_ALL_WORKS, {
66
+ cursor,
67
+ limit,
68
+ });
69
+
70
+ allWorks.push(...response.works.items);
71
+ cursor = response.works.pageInfo.hasMoreAfter
72
+ ? response.works.pageInfo.nextCursor
73
+ : null;
74
+ } while (cursor !== null);
75
+
76
+ return worksFromGraphQL(allWorks);
77
+ } catch (error) {
78
+ console.error('Error fetching translations metadata:', error);
79
+ return [];
80
+ }
81
+ }
@@ -0,0 +1,47 @@
1
+ import type { GraphQLClient } from 'graphql-request';
2
+ import { gql } from 'graphql-request';
3
+ import type { BibliographyEntries } from '@eightyfourthousand/data-access';
4
+
5
+ const GET_WORK_BIBLIOGRAPHY = gql`
6
+ query GetWorkBibliography($uuid: ID!) {
7
+ work(uuid: $uuid) {
8
+ bibliography {
9
+ heading
10
+ entries {
11
+ uuid
12
+ html
13
+ workUuid
14
+ }
15
+ }
16
+ }
17
+ }
18
+ `;
19
+
20
+ type GetWorkBibliographyResponse = {
21
+ work: {
22
+ bibliography: BibliographyEntries;
23
+ } | null;
24
+ };
25
+
26
+ /**
27
+ * Get bibliography entries for a specific work.
28
+ */
29
+ export async function getWorkBibliography({
30
+ client,
31
+ uuid,
32
+ }: {
33
+ client: GraphQLClient;
34
+ uuid: string;
35
+ }): Promise<BibliographyEntries> {
36
+ try {
37
+ const response = await client.request<GetWorkBibliographyResponse>(
38
+ GET_WORK_BIBLIOGRAPHY,
39
+ { uuid },
40
+ );
41
+
42
+ return response.work?.bibliography ?? [];
43
+ } catch (error) {
44
+ console.error('Error fetching work bibliography:', error);
45
+ return [];
46
+ }
47
+ }
@@ -0,0 +1,52 @@
1
+ import type { GraphQLClient } from 'graphql-request';
2
+ import { gql } from 'graphql-request';
3
+ import type { Folio } from '@eightyfourthousand/data-access';
4
+
5
+ const GET_WORK_FOLIOS = gql`
6
+ query GetWorkFolios($uuid: ID!, $toh: String!, $page: Int, $size: Int) {
7
+ work(uuid: $uuid) {
8
+ folios(toh: $toh, page: $page, size: $size) {
9
+ uuid
10
+ content
11
+ volume
12
+ folio
13
+ side
14
+ }
15
+ }
16
+ }
17
+ `;
18
+
19
+ type GetWorkFoliosResponse = {
20
+ work: {
21
+ folios: Folio[];
22
+ } | null;
23
+ };
24
+
25
+ /**
26
+ * Get folios for a specific work, filtered by toh and paginated.
27
+ */
28
+ export async function getWorkFolios({
29
+ client,
30
+ uuid,
31
+ toh,
32
+ page = 0,
33
+ size = 10,
34
+ }: {
35
+ client: GraphQLClient;
36
+ uuid: string;
37
+ toh: string;
38
+ page?: number;
39
+ size?: number;
40
+ }): Promise<Folio[]> {
41
+ try {
42
+ const response = await client.request<GetWorkFoliosResponse>(
43
+ GET_WORK_FOLIOS,
44
+ { uuid, toh, page, size },
45
+ );
46
+
47
+ return response.work?.folios ?? [];
48
+ } catch (error) {
49
+ console.error('Error fetching work folios:', error);
50
+ return [];
51
+ }
52
+ }
@@ -0,0 +1,126 @@
1
+ import type { GraphQLClient } from 'graphql-request';
2
+ import { gql } from 'graphql-request';
3
+ import type { GlossaryTermInstance } from '@eightyfourthousand/data-access';
4
+ import type { GlossaryTermsPage } from './get-work-glossary-terms';
5
+
6
+ const GET_WORK_GLOSSARY_TERMS_AROUND = gql`
7
+ query GetWorkGlossaryTermsAround(
8
+ $uuid: ID!
9
+ $cursor: String!
10
+ $limit: Int
11
+ $withAttestations: Boolean
12
+ ) {
13
+ work(uuid: $uuid) {
14
+ glossaryTerms(
15
+ cursor: $cursor
16
+ limit: $limit
17
+ direction: AROUND
18
+ withAttestations: $withAttestations
19
+ ) {
20
+ nodes {
21
+ uuid
22
+ authority
23
+ definition
24
+ termNumber
25
+ names {
26
+ english
27
+ tibetan
28
+ sanskrit
29
+ pali
30
+ chinese
31
+ wylie
32
+ }
33
+ passages(first: 10) {
34
+ items {
35
+ uuid
36
+ type
37
+ label
38
+ }
39
+ nextCursor
40
+ hasMore
41
+ }
42
+ }
43
+ pageInfo {
44
+ nextCursor
45
+ prevCursor
46
+ hasMoreAfter
47
+ hasMoreBefore
48
+ }
49
+ totalCount
50
+ }
51
+ }
52
+ }
53
+ `;
54
+
55
+ type GetWorkGlossaryTermsAroundResponse = {
56
+ work: {
57
+ glossaryTerms: {
58
+ nodes: GlossaryTermInstance[];
59
+ pageInfo: {
60
+ nextCursor?: string | null;
61
+ prevCursor?: string | null;
62
+ hasMoreAfter: boolean;
63
+ hasMoreBefore: boolean;
64
+ };
65
+ totalCount: number;
66
+ };
67
+ } | null;
68
+ };
69
+
70
+ /**
71
+ * Get glossary terms around a specific term UUID.
72
+ * Used for navigating to a glossary entry that hasn't been loaded yet.
73
+ */
74
+ export async function getWorkGlossaryTermsAround({
75
+ client,
76
+ uuid,
77
+ termUuid,
78
+ limit,
79
+ withAttestations = false,
80
+ }: {
81
+ client: GraphQLClient;
82
+ uuid: string;
83
+ termUuid: string;
84
+ limit?: number;
85
+ withAttestations?: boolean;
86
+ }): Promise<GlossaryTermsPage> {
87
+ try {
88
+ const response = await client.request<GetWorkGlossaryTermsAroundResponse>(
89
+ GET_WORK_GLOSSARY_TERMS_AROUND,
90
+ {
91
+ uuid,
92
+ cursor: termUuid,
93
+ limit,
94
+ withAttestations,
95
+ },
96
+ );
97
+
98
+ if (!response.work) {
99
+ return {
100
+ terms: [],
101
+ hasMoreAfter: false,
102
+ hasMoreBefore: false,
103
+ totalCount: 0,
104
+ };
105
+ }
106
+
107
+ const { glossaryTerms } = response.work;
108
+
109
+ return {
110
+ terms: glossaryTerms.nodes,
111
+ nextCursor: glossaryTerms.pageInfo.nextCursor ?? undefined,
112
+ prevCursor: glossaryTerms.pageInfo.prevCursor ?? undefined,
113
+ hasMoreAfter: glossaryTerms.pageInfo.hasMoreAfter,
114
+ hasMoreBefore: glossaryTerms.pageInfo.hasMoreBefore,
115
+ totalCount: glossaryTerms.totalCount,
116
+ };
117
+ } catch (error) {
118
+ console.error('Error fetching glossary terms around:', error);
119
+ return {
120
+ terms: [],
121
+ hasMoreAfter: false,
122
+ hasMoreBefore: false,
123
+ totalCount: 0,
124
+ };
125
+ }
126
+ }