@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,131 @@
1
+ import type { GraphQLClient } from 'graphql-request';
2
+ import { gql } from 'graphql-request';
3
+ import type { BodyItemType, TranslationEditorContentItem } from '@eightyfourthousand/data-access';
4
+ import type { TranslationBlocksPage } from './get-translation-blocks';
5
+
6
+ const GET_PASSAGES_AROUND_WITH_JSON = gql`
7
+ fragment PassageWithJson on Passage {
8
+ uuid
9
+ workUuid
10
+ label
11
+ sort
12
+ type
13
+ xmlId
14
+ json
15
+ }
16
+
17
+ query GetPassagesAroundWithJson(
18
+ $uuid: ID!
19
+ $cursor: String!
20
+ $limit: Int
21
+ $filter: PassageFilter
22
+ ) {
23
+ work(uuid: $uuid) {
24
+ uuid
25
+ passages(
26
+ cursor: $cursor
27
+ limit: $limit
28
+ direction: AROUND
29
+ filter: $filter
30
+ ) {
31
+ nodes {
32
+ ...PassageWithJson
33
+ }
34
+ pageInfo {
35
+ nextCursor
36
+ prevCursor
37
+ hasMoreAfter
38
+ hasMoreBefore
39
+ }
40
+ }
41
+ }
42
+ }
43
+ `;
44
+
45
+ type PassageWithJson = {
46
+ uuid: string;
47
+ workUuid: string;
48
+ label: string;
49
+ sort: number;
50
+ type: string;
51
+ xmlId?: string | null;
52
+ json?: unknown | null;
53
+ };
54
+
55
+ type GetPassagesAroundWithJsonResponse = {
56
+ work: {
57
+ uuid: string;
58
+ passages: {
59
+ nodes: PassageWithJson[];
60
+ pageInfo: {
61
+ nextCursor?: string | null;
62
+ prevCursor?: string | null;
63
+ hasMoreAfter: boolean;
64
+ hasMoreBefore: boolean;
65
+ };
66
+ };
67
+ } | null;
68
+ };
69
+
70
+ /**
71
+ * Get translation blocks (TipTap JSON) around a specific passage UUID.
72
+ *
73
+ * Unlike getTranslationPassagesAround which returns Passage objects,
74
+ * this function returns pre-transformed TipTap editor blocks
75
+ * ready for rendering.
76
+ */
77
+ export async function getTranslationBlocksAround({
78
+ client,
79
+ uuid,
80
+ passageUuid,
81
+ type,
82
+ maxPassages,
83
+ }: {
84
+ client: GraphQLClient;
85
+ uuid: string;
86
+ passageUuid: string;
87
+ type?: BodyItemType | string;
88
+ maxPassages?: number;
89
+ }): Promise<TranslationBlocksPage> {
90
+ try {
91
+ const response = await client.request<GetPassagesAroundWithJsonResponse>(
92
+ GET_PASSAGES_AROUND_WITH_JSON,
93
+ {
94
+ uuid,
95
+ cursor: passageUuid,
96
+ limit: maxPassages,
97
+ filter: type ? { type } : undefined,
98
+ },
99
+ );
100
+
101
+ if (!response.work) {
102
+ return {
103
+ blocks: [],
104
+ hasMoreAfter: false,
105
+ hasMoreBefore: false,
106
+ };
107
+ }
108
+
109
+ const { passages } = response.work;
110
+
111
+ // Extract the JSON blocks, filtering out any null values
112
+ const blocks = passages.nodes
113
+ .map((node) => node.json)
114
+ .filter((json): json is TranslationEditorContentItem => json != null);
115
+
116
+ return {
117
+ blocks,
118
+ nextCursor: passages.pageInfo.nextCursor ?? undefined,
119
+ prevCursor: passages.pageInfo.prevCursor ?? undefined,
120
+ hasMoreAfter: passages.pageInfo.hasMoreAfter,
121
+ hasMoreBefore: passages.pageInfo.hasMoreBefore,
122
+ };
123
+ } catch (error) {
124
+ console.error('Error fetching translation blocks around:', error);
125
+ return {
126
+ blocks: [],
127
+ hasMoreAfter: false,
128
+ hasMoreBefore: false,
129
+ };
130
+ }
131
+ }
@@ -0,0 +1,160 @@
1
+ import type { GraphQLClient } from 'graphql-request';
2
+ import { gql } from 'graphql-request';
3
+ import type {
4
+ BodyItemType,
5
+ PaginationDirection,
6
+ TranslationEditorContent,
7
+ TranslationEditorContentItem,
8
+ } from '@eightyfourthousand/data-access';
9
+
10
+ const GET_PASSAGES_WITH_JSON = gql`
11
+ fragment PassageWithJson on Passage {
12
+ uuid
13
+ workUuid
14
+ label
15
+ sort
16
+ type
17
+ xmlId
18
+ json
19
+ }
20
+
21
+ query GetPassagesWithJson(
22
+ $uuid: ID!
23
+ $cursor: String
24
+ $limit: Int
25
+ $direction: PaginationDirection
26
+ $filter: PassageFilter
27
+ ) {
28
+ work(uuid: $uuid) {
29
+ uuid
30
+ passages(
31
+ cursor: $cursor
32
+ limit: $limit
33
+ direction: $direction
34
+ filter: $filter
35
+ ) {
36
+ nodes {
37
+ ...PassageWithJson
38
+ }
39
+ pageInfo {
40
+ nextCursor
41
+ prevCursor
42
+ hasMoreAfter
43
+ hasMoreBefore
44
+ }
45
+ }
46
+ }
47
+ }
48
+ `;
49
+
50
+ type PassageWithJson = {
51
+ uuid: string;
52
+ workUuid: string;
53
+ label: string;
54
+ sort: number;
55
+ type: string;
56
+ xmlId?: string | null;
57
+ json?: unknown | null;
58
+ };
59
+
60
+ type GetPassagesWithJsonResponse = {
61
+ work: {
62
+ uuid: string;
63
+ passages: {
64
+ nodes: PassageWithJson[];
65
+ pageInfo: {
66
+ nextCursor?: string | null;
67
+ prevCursor?: string | null;
68
+ hasMoreAfter: boolean;
69
+ hasMoreBefore: boolean;
70
+ };
71
+ };
72
+ } | null;
73
+ };
74
+
75
+ /**
76
+ * Result of fetching translation blocks with pagination info
77
+ */
78
+ export type TranslationBlocksPage = {
79
+ blocks: TranslationEditorContent;
80
+ nextCursor?: string;
81
+ prevCursor?: string;
82
+ hasMoreAfter: boolean;
83
+ hasMoreBefore: boolean;
84
+ };
85
+
86
+ /**
87
+ * Map internal pagination direction to GraphQL enum
88
+ */
89
+ function mapDirection(
90
+ direction?: PaginationDirection,
91
+ ): 'FORWARD' | 'BACKWARD' | undefined {
92
+ if (!direction) return undefined;
93
+ return direction === 'forward' ? 'FORWARD' : 'BACKWARD';
94
+ }
95
+
96
+ /**
97
+ * Get paginated translation blocks (TipTap JSON) for a work.
98
+ *
99
+ * Unlike getTranslationPassages which returns Passage objects,
100
+ * this function returns pre-transformed TipTap editor blocks
101
+ * ready for rendering.
102
+ */
103
+ export async function getTranslationBlocks({
104
+ client,
105
+ uuid,
106
+ type,
107
+ cursor,
108
+ maxPassages = 100,
109
+ direction,
110
+ }: {
111
+ client: GraphQLClient;
112
+ uuid: string;
113
+ type?: BodyItemType | string;
114
+ cursor?: string;
115
+ maxPassages?: number;
116
+ direction?: PaginationDirection;
117
+ }): Promise<TranslationBlocksPage> {
118
+ try {
119
+ const response = await client.request<GetPassagesWithJsonResponse>(
120
+ GET_PASSAGES_WITH_JSON,
121
+ {
122
+ uuid,
123
+ cursor,
124
+ limit: maxPassages,
125
+ direction: mapDirection(direction),
126
+ filter: type ? { type } : undefined,
127
+ },
128
+ );
129
+
130
+ if (!response.work) {
131
+ return {
132
+ blocks: [],
133
+ hasMoreAfter: false,
134
+ hasMoreBefore: false,
135
+ };
136
+ }
137
+
138
+ const { passages } = response.work;
139
+
140
+ // Extract the JSON blocks, filtering out any null values
141
+ const blocks = passages.nodes
142
+ .map((node) => node.json)
143
+ .filter((json): json is TranslationEditorContentItem => json != null);
144
+
145
+ return {
146
+ blocks,
147
+ nextCursor: passages.pageInfo.nextCursor ?? undefined,
148
+ prevCursor: passages.pageInfo.prevCursor ?? undefined,
149
+ hasMoreAfter: passages.pageInfo.hasMoreAfter,
150
+ hasMoreBefore: passages.pageInfo.hasMoreBefore,
151
+ };
152
+ } catch (error) {
153
+ console.error('Error fetching translation blocks:', error);
154
+ return {
155
+ blocks: [],
156
+ hasMoreAfter: false,
157
+ hasMoreBefore: false,
158
+ };
159
+ }
160
+ }
@@ -0,0 +1,114 @@
1
+ import type { GraphQLClient } from 'graphql-request';
2
+ import { gql } from 'graphql-request';
3
+ import type { Imprint } from '@eightyfourthousand/data-access';
4
+ import { imprintFromGraphQL } from '../mappers';
5
+
6
+ const GET_WORK_WITH_IMPRINT = gql`
7
+ fragment LicenseFields on License {
8
+ name
9
+ link
10
+ description
11
+ }
12
+
13
+ fragment TitlesByLanguageFields on TitlesByLanguage {
14
+ tibetan
15
+ english
16
+ wylie
17
+ sanskrit
18
+ }
19
+
20
+ fragment ImprintFields on Imprint {
21
+ toh
22
+ section
23
+ version
24
+ restriction
25
+ publishYear
26
+ tibetanAuthors
27
+ isAuthorContested
28
+ sourceDescription
29
+ publisherStatement
30
+ tibetanTranslators
31
+ license {
32
+ ...LicenseFields
33
+ }
34
+ mainTitles {
35
+ ...TitlesByLanguageFields
36
+ }
37
+ longTitles {
38
+ ...TitlesByLanguageFields
39
+ }
40
+ }
41
+
42
+ query GetWorkWithImprint($uuid: ID!, $toh: String) {
43
+ work(uuid: $uuid, toh: $toh) {
44
+ uuid
45
+ imprint {
46
+ ...ImprintFields
47
+ }
48
+ }
49
+ }
50
+ `;
51
+
52
+ type GetWorkWithImprintResponse = {
53
+ work: {
54
+ uuid: string;
55
+ imprint: {
56
+ toh: string;
57
+ section?: string | null;
58
+ version?: string | null;
59
+ restriction?: boolean | null;
60
+ publishYear?: string | null;
61
+ tibetanAuthors?: string[] | null;
62
+ isAuthorContested: boolean;
63
+ sourceDescription?: string | null;
64
+ publisherStatement?: string | null;
65
+ tibetanTranslators?: string | null;
66
+ license: {
67
+ name?: string | null;
68
+ link?: string | null;
69
+ description?: string | null;
70
+ } | null;
71
+ mainTitles?: {
72
+ tibetan?: string | null;
73
+ english?: string | null;
74
+ wylie?: string | null;
75
+ sanskrit?: string | null;
76
+ } | null;
77
+ longTitles?: {
78
+ tibetan?: string | null;
79
+ english?: string | null;
80
+ wylie?: string | null;
81
+ sanskrit?: string | null;
82
+ } | null;
83
+ } | null;
84
+ } | null;
85
+ };
86
+
87
+ /**
88
+ * Get the imprint for a translation work.
89
+ */
90
+ export async function getTranslationImprint({
91
+ client,
92
+ uuid,
93
+ toh,
94
+ }: {
95
+ client: GraphQLClient;
96
+ uuid: string;
97
+ toh: string;
98
+ }): Promise<Imprint | undefined> {
99
+ try {
100
+ const response = await client.request<GetWorkWithImprintResponse>(
101
+ GET_WORK_WITH_IMPRINT,
102
+ { uuid, toh },
103
+ );
104
+
105
+ if (!response.work?.imprint) {
106
+ return undefined;
107
+ }
108
+
109
+ return imprintFromGraphQL(response.work.imprint, response.work.uuid);
110
+ } catch (error) {
111
+ console.error('Error fetching translation imprint:', error);
112
+ return undefined;
113
+ }
114
+ }
@@ -0,0 +1,99 @@
1
+ import type { GraphQLClient } from 'graphql-request';
2
+ import { gql } from 'graphql-request';
3
+ import type { Work } from '@eightyfourthousand/data-access';
4
+ import { workFromGraphQL } from '../mappers';
5
+
6
+ const GET_WORK_BY_UUID = gql`
7
+ query GetWorkByUuid($uuid: ID!) {
8
+ work(uuid: $uuid) {
9
+ uuid
10
+ title
11
+ toh
12
+ publicationDate
13
+ publicationVersion
14
+ pages
15
+ restriction
16
+ section
17
+ }
18
+ }
19
+ `;
20
+
21
+ const GET_WORK_BY_TOH = gql`
22
+ query GetWorkByToh($toh: String!) {
23
+ work(toh: $toh) {
24
+ uuid
25
+ title
26
+ toh
27
+ publicationDate
28
+ publicationVersion
29
+ pages
30
+ restriction
31
+ section
32
+ }
33
+ }
34
+ `;
35
+
36
+ type GetWorkResponse = {
37
+ work: {
38
+ uuid: string;
39
+ title: string;
40
+ toh: string[];
41
+ publicationDate: string;
42
+ publicationVersion: string;
43
+ pages: number;
44
+ restriction: boolean;
45
+ section: string;
46
+ } | null;
47
+ };
48
+
49
+ /**
50
+ * Get work metadata by UUID.
51
+ */
52
+ export async function getTranslationMetadataByUuid({
53
+ client,
54
+ uuid,
55
+ }: {
56
+ client: GraphQLClient;
57
+ uuid: string;
58
+ }): Promise<Work | null> {
59
+ try {
60
+ const response = await client.request<GetWorkResponse>(GET_WORK_BY_UUID, {
61
+ uuid,
62
+ });
63
+
64
+ if (!response.work) {
65
+ return null;
66
+ }
67
+
68
+ return workFromGraphQL(response.work);
69
+ } catch (error) {
70
+ console.error('Error fetching translation metadata by UUID:', error);
71
+ return null;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Get work metadata by Tohoku catalog number.
77
+ */
78
+ export async function getTranslationMetadataByToh({
79
+ client,
80
+ toh,
81
+ }: {
82
+ client: GraphQLClient;
83
+ toh: string;
84
+ }): Promise<Work | null> {
85
+ try {
86
+ const response = await client.request<GetWorkResponse>(GET_WORK_BY_TOH, {
87
+ toh,
88
+ });
89
+
90
+ if (!response.work) {
91
+ return null;
92
+ }
93
+
94
+ return workFromGraphQL(response.work);
95
+ } catch (error) {
96
+ console.error('Error fetching translation metadata by toh:', error);
97
+ return null;
98
+ }
99
+ }
@@ -0,0 +1,153 @@
1
+ import type { GraphQLClient } from 'graphql-request';
2
+ import { gql } from 'graphql-request';
3
+ import type { BodyItemType, PassagesPage } from '@eightyfourthousand/data-access';
4
+ import { passagesPageFromGraphQL } from '../mappers';
5
+
6
+ const GET_PASSAGES_AROUND = gql`
7
+ fragment AnnotationFields on Annotation {
8
+ uuid
9
+ type
10
+ start
11
+ end
12
+ metadata
13
+ }
14
+
15
+ fragment AlignmentFields on Alignment {
16
+ folioUuid
17
+ toh
18
+ tibetan
19
+ folioNumber
20
+ volumeNumber
21
+ }
22
+
23
+ fragment PassageWithAnnotations on Passage {
24
+ uuid
25
+ workUuid
26
+ content
27
+ label
28
+ sort
29
+ type
30
+ xmlId
31
+ annotations {
32
+ ...AnnotationFields
33
+ }
34
+ alignments {
35
+ ...AlignmentFields
36
+ }
37
+ }
38
+
39
+ query GetPassagesAround(
40
+ $uuid: ID!
41
+ $cursor: String!
42
+ $limit: Int
43
+ $filter: PassageFilter
44
+ ) {
45
+ work(uuid: $uuid) {
46
+ uuid
47
+ passages(
48
+ cursor: $cursor
49
+ limit: $limit
50
+ direction: AROUND
51
+ filter: $filter
52
+ ) {
53
+ nodes {
54
+ ...PassageWithAnnotations
55
+ }
56
+ pageInfo {
57
+ nextCursor
58
+ prevCursor
59
+ hasMoreAfter
60
+ hasMoreBefore
61
+ }
62
+ }
63
+ }
64
+ }
65
+ `;
66
+
67
+ type GetPassagesAroundResponse = {
68
+ work: {
69
+ uuid: string;
70
+ passages: {
71
+ nodes: Array<{
72
+ uuid: string;
73
+ workUuid: string;
74
+ content: string;
75
+ label: string;
76
+ sort: number;
77
+ type: string;
78
+ xmlId?: string | null;
79
+ annotations: Array<{
80
+ uuid: string;
81
+ type: string;
82
+ start: number;
83
+ end: number;
84
+ metadata?: Record<string, unknown> | null;
85
+ }>;
86
+ alignments: Array<{
87
+ folioUuid: string;
88
+ toh: string;
89
+ tibetan: string;
90
+ folioNumber: number;
91
+ volumeNumber: number;
92
+ }>;
93
+ }>;
94
+ pageInfo: {
95
+ nextCursor?: string | null;
96
+ prevCursor?: string | null;
97
+ hasMoreAfter: boolean;
98
+ hasMoreBefore: boolean;
99
+ };
100
+ };
101
+ } | null;
102
+ };
103
+
104
+ /**
105
+ * Get passages around a specific passage UUID.
106
+ *
107
+ * NOTE: maxCharacters is not supported by the GraphQL API yet.
108
+ * This parameter is accepted for API compatibility but is ignored.
109
+ */
110
+ export async function getTranslationPassagesAround({
111
+ client,
112
+ uuid,
113
+ passageUuid,
114
+ type,
115
+ maxPassages,
116
+ maxCharacters: _maxCharacters,
117
+ }: {
118
+ client: GraphQLClient;
119
+ uuid: string;
120
+ passageUuid: string;
121
+ type?: BodyItemType;
122
+ maxPassages?: number;
123
+ maxCharacters?: number;
124
+ }): Promise<PassagesPage> {
125
+ try {
126
+ const response = await client.request<GetPassagesAroundResponse>(
127
+ GET_PASSAGES_AROUND,
128
+ {
129
+ uuid,
130
+ cursor: passageUuid,
131
+ limit: maxPassages,
132
+ filter: type ? { type } : undefined,
133
+ },
134
+ );
135
+
136
+ if (!response.work) {
137
+ return {
138
+ passages: [],
139
+ hasMoreAfter: false,
140
+ hasMoreBefore: false,
141
+ };
142
+ }
143
+
144
+ return passagesPageFromGraphQL(response.work.passages, response.work.uuid);
145
+ } catch (error) {
146
+ console.error('Error fetching translation passages around:', error);
147
+ return {
148
+ passages: [],
149
+ hasMoreAfter: false,
150
+ hasMoreBefore: false,
151
+ };
152
+ }
153
+ }