@youversion/platform-core 1.11.0 → 1.12.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.
- package/.turbo/turbo-build.log +8 -8
- package/CHANGELOG.md +6 -0
- package/dist/index.cjs +335 -313
- package/dist/index.d.cts +8 -10
- package/dist/index.d.ts +8 -10
- package/dist/index.js +335 -313
- package/package.json +1 -1
- package/src/__tests__/bible.test.ts +229 -0
- package/src/bible.ts +64 -33
package/package.json
CHANGED
|
@@ -113,6 +113,235 @@ describe('BibleClient', () => {
|
|
|
113
113
|
'At least one language range is required',
|
|
114
114
|
);
|
|
115
115
|
});
|
|
116
|
+
|
|
117
|
+
describe('options validation', () => {
|
|
118
|
+
it('should accept valid page_size as positive integer', async () => {
|
|
119
|
+
const versions = await bibleClient.getVersions('en*', undefined, { page_size: 10 });
|
|
120
|
+
expect(versions.data.length).toBeLessThanOrEqual(10);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should accept page_size as "*" with 1-3 fields specified', async () => {
|
|
124
|
+
server.use(
|
|
125
|
+
http.get('*/v1/bibles', () => {
|
|
126
|
+
return HttpResponse.json({
|
|
127
|
+
data: mockVersions.map(({ id, title }) => ({ id, title })),
|
|
128
|
+
next_page_token: null,
|
|
129
|
+
total_size: mockVersions.length,
|
|
130
|
+
});
|
|
131
|
+
}),
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const versions = await bibleClient.getVersions('en*', undefined, {
|
|
135
|
+
page_size: '*',
|
|
136
|
+
fields: ['id', 'title'],
|
|
137
|
+
});
|
|
138
|
+
expect(versions.data.length).toBeGreaterThan(0);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should accept page_size as "*" with exactly 1 field', async () => {
|
|
142
|
+
server.use(
|
|
143
|
+
http.get('*/v1/bibles', () => {
|
|
144
|
+
return HttpResponse.json({
|
|
145
|
+
data: mockVersions.map(({ id }) => ({ id })),
|
|
146
|
+
next_page_token: null,
|
|
147
|
+
total_size: mockVersions.length,
|
|
148
|
+
});
|
|
149
|
+
}),
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
const versions = await bibleClient.getVersions('en*', undefined, {
|
|
153
|
+
page_size: '*',
|
|
154
|
+
fields: ['id'],
|
|
155
|
+
});
|
|
156
|
+
expect(versions.data.length).toBeGreaterThan(0);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('should accept page_size as "*" with exactly 3 fields', async () => {
|
|
160
|
+
server.use(
|
|
161
|
+
http.get('*/v1/bibles', () => {
|
|
162
|
+
return HttpResponse.json({
|
|
163
|
+
data: mockVersions.map(({ id, title, abbreviation }) => ({
|
|
164
|
+
id,
|
|
165
|
+
title,
|
|
166
|
+
abbreviation,
|
|
167
|
+
})),
|
|
168
|
+
next_page_token: null,
|
|
169
|
+
total_size: mockVersions.length,
|
|
170
|
+
});
|
|
171
|
+
}),
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
const versions = await bibleClient.getVersions('en*', undefined, {
|
|
175
|
+
page_size: '*',
|
|
176
|
+
fields: ['id', 'title', 'abbreviation'],
|
|
177
|
+
});
|
|
178
|
+
expect(versions.data.length).toBeGreaterThan(0);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('should throw when page_size is "*" but no fields specified', async () => {
|
|
182
|
+
await expect(bibleClient.getVersions('en*', undefined, { page_size: '*' })).rejects.toThrow(
|
|
183
|
+
/required 1-3 fields to be specified/,
|
|
184
|
+
);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should throw when page_size is "*" with empty fields array', async () => {
|
|
188
|
+
await expect(
|
|
189
|
+
bibleClient.getVersions('en*', undefined, { page_size: '*', fields: [] }),
|
|
190
|
+
).rejects.toThrow(/required 1-3 fields to be specified/);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should throw when page_size is "*" with more than 3 fields', async () => {
|
|
194
|
+
await expect(
|
|
195
|
+
bibleClient.getVersions('en*', undefined, {
|
|
196
|
+
page_size: '*',
|
|
197
|
+
fields: ['id', 'title', 'abbreviation', 'language_tag'],
|
|
198
|
+
}),
|
|
199
|
+
).rejects.toThrow(/required 1-3 fields to be specified/);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('should throw for page_size of zero', async () => {
|
|
203
|
+
await expect(bibleClient.getVersions('en*', undefined, { page_size: 0 })).rejects.toThrow();
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('should throw for negative page_size', async () => {
|
|
207
|
+
await expect(
|
|
208
|
+
bibleClient.getVersions('en*', undefined, { page_size: -1 }),
|
|
209
|
+
).rejects.toThrow();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('should throw for non-integer page_size', async () => {
|
|
213
|
+
await expect(
|
|
214
|
+
bibleClient.getVersions('en*', undefined, { page_size: 1.5 }),
|
|
215
|
+
).rejects.toThrow();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('should throw for invalid page_size string', async () => {
|
|
219
|
+
await expect(
|
|
220
|
+
// @ts-expect-error - testing invalid input
|
|
221
|
+
bibleClient.getVersions('en*', undefined, { page_size: 'invalid' }),
|
|
222
|
+
).rejects.toThrow();
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('should accept valid page_token string', async () => {
|
|
226
|
+
server.use(
|
|
227
|
+
http.get('*/v1/bibles', () => {
|
|
228
|
+
return HttpResponse.json({
|
|
229
|
+
data: mockVersions,
|
|
230
|
+
next_page_token: null,
|
|
231
|
+
total_size: mockVersions.length,
|
|
232
|
+
});
|
|
233
|
+
}),
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
const versions = await bibleClient.getVersions('en*', undefined, {
|
|
237
|
+
page_token: 'some-token-value',
|
|
238
|
+
});
|
|
239
|
+
expect(versions.data.length).toBeGreaterThan(0);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('should accept valid fields array with BibleVersionSchema keys', async () => {
|
|
243
|
+
server.use(
|
|
244
|
+
http.get('*/v1/bibles', () => {
|
|
245
|
+
return HttpResponse.json({
|
|
246
|
+
data: mockVersions,
|
|
247
|
+
next_page_token: null,
|
|
248
|
+
total_size: mockVersions.length,
|
|
249
|
+
});
|
|
250
|
+
}),
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
const versions = await bibleClient.getVersions('en*', undefined, {
|
|
254
|
+
fields: ['id', 'title', 'abbreviation'],
|
|
255
|
+
});
|
|
256
|
+
expect(versions.data.length).toBeGreaterThan(0);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('should throw for invalid fields that are not BibleVersionSchema keys', async () => {
|
|
260
|
+
await expect(
|
|
261
|
+
bibleClient.getVersions('en*', undefined, {
|
|
262
|
+
// @ts-expect-error - testing invalid input
|
|
263
|
+
fields: ['invalid_field'],
|
|
264
|
+
}),
|
|
265
|
+
).rejects.toThrow();
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('should accept undefined options', async () => {
|
|
269
|
+
const versions = await bibleClient.getVersions('en*', undefined, undefined);
|
|
270
|
+
expect(versions.data.length).toBeGreaterThan(0);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('should accept empty options object', async () => {
|
|
274
|
+
const versions = await bibleClient.getVersions('en*', undefined, {});
|
|
275
|
+
expect(versions.data.length).toBeGreaterThan(0);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('should send all_available param when set to true', async () => {
|
|
279
|
+
server.use(
|
|
280
|
+
http.get('*/v1/bibles', ({ request }) => {
|
|
281
|
+
const url = new URL(request.url);
|
|
282
|
+
const allAvailable = url.searchParams.get('all_available');
|
|
283
|
+
|
|
284
|
+
expect(allAvailable).toBe('true');
|
|
285
|
+
|
|
286
|
+
return HttpResponse.json({
|
|
287
|
+
data: mockVersions,
|
|
288
|
+
next_page_token: null,
|
|
289
|
+
total_size: mockVersions.length,
|
|
290
|
+
});
|
|
291
|
+
}),
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
const versions = await bibleClient.getVersions('en*', undefined, { all_available: true });
|
|
295
|
+
expect(versions.data.length).toBeGreaterThan(0);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('should not send all_available param when set to false', async () => {
|
|
299
|
+
server.use(
|
|
300
|
+
http.get('*/v1/bibles', ({ request }) => {
|
|
301
|
+
const url = new URL(request.url);
|
|
302
|
+
const allAvailable = url.searchParams.get('all_available');
|
|
303
|
+
|
|
304
|
+
expect(allAvailable).toBeNull();
|
|
305
|
+
|
|
306
|
+
return HttpResponse.json({
|
|
307
|
+
data: mockVersions,
|
|
308
|
+
next_page_token: null,
|
|
309
|
+
total_size: mockVersions.length,
|
|
310
|
+
});
|
|
311
|
+
}),
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
const versions = await bibleClient.getVersions('en*', undefined, { all_available: false });
|
|
315
|
+
expect(versions.data.length).toBeGreaterThan(0);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it('should not send all_available param when not specified', async () => {
|
|
319
|
+
server.use(
|
|
320
|
+
http.get('*/v1/bibles', ({ request }) => {
|
|
321
|
+
const url = new URL(request.url);
|
|
322
|
+
const allAvailable = url.searchParams.get('all_available');
|
|
323
|
+
|
|
324
|
+
expect(allAvailable).toBeNull();
|
|
325
|
+
|
|
326
|
+
return HttpResponse.json({
|
|
327
|
+
data: mockVersions,
|
|
328
|
+
next_page_token: null,
|
|
329
|
+
total_size: mockVersions.length,
|
|
330
|
+
});
|
|
331
|
+
}),
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
const versions = await bibleClient.getVersions('en*', undefined, {});
|
|
335
|
+
expect(versions.data.length).toBeGreaterThan(0);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('should throw for invalid all_available type', async () => {
|
|
339
|
+
await expect(
|
|
340
|
+
// @ts-expect-error - testing invalid input
|
|
341
|
+
bibleClient.getVersions('en*', undefined, { all_available: 'true' }),
|
|
342
|
+
).rejects.toThrow();
|
|
343
|
+
});
|
|
344
|
+
});
|
|
116
345
|
});
|
|
117
346
|
|
|
118
347
|
describe('getVersion', () => {
|
package/src/bible.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
BibleIndex,
|
|
12
12
|
CANON,
|
|
13
13
|
} from './types';
|
|
14
|
+
import { BibleVersionSchema } from './schemas';
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Client for interacting with Bible API endpoints.
|
|
@@ -19,25 +20,48 @@ export class BibleClient {
|
|
|
19
20
|
private client: ApiClient;
|
|
20
21
|
|
|
21
22
|
// Validation schemas
|
|
22
|
-
private versionIdSchema = z
|
|
23
|
-
|
|
23
|
+
private static readonly versionIdSchema = z
|
|
24
|
+
.number()
|
|
25
|
+
.int()
|
|
26
|
+
.positive('Version ID must be a positive integer');
|
|
27
|
+
private static readonly bookSchema = z
|
|
24
28
|
.string()
|
|
25
29
|
.trim()
|
|
26
30
|
.min(3, 'Book ID must be exactly 3 characters')
|
|
27
31
|
.max(3, 'Book ID must be exactly 3 characters');
|
|
28
|
-
private chapterSchema = z
|
|
32
|
+
private static readonly chapterSchema = z
|
|
29
33
|
.number()
|
|
30
34
|
.int('Chapter must be an integer')
|
|
31
35
|
.positive('Chapter must be a positive integer');
|
|
32
|
-
private verseSchema = z
|
|
36
|
+
private static readonly verseSchema = z
|
|
33
37
|
.number()
|
|
34
38
|
.int('Verse must be an integer')
|
|
35
39
|
.positive('Verse must be a positive integer');
|
|
36
|
-
private languageRangesSchema = z
|
|
40
|
+
private static readonly languageRangesSchema = z
|
|
37
41
|
.string()
|
|
38
42
|
.trim()
|
|
39
43
|
.min(1, 'Language ranges must be a non-empty string');
|
|
40
|
-
private booleanSchema = z.boolean();
|
|
44
|
+
private static readonly booleanSchema = z.boolean();
|
|
45
|
+
private static readonly GetVersionsOptionsSchema = z
|
|
46
|
+
.object({
|
|
47
|
+
page_size: z.union([z.number().int().positive(), z.literal('*')]).optional(),
|
|
48
|
+
page_token: z.string().optional(),
|
|
49
|
+
fields: z.array(BibleVersionSchema.keyof()).optional(),
|
|
50
|
+
all_available: z.boolean().optional(),
|
|
51
|
+
})
|
|
52
|
+
.optional()
|
|
53
|
+
.refine(
|
|
54
|
+
(data) => {
|
|
55
|
+
if (data?.page_size === '*') {
|
|
56
|
+
return data.fields && data.fields.length >= 1 && data.fields.length <= 3;
|
|
57
|
+
}
|
|
58
|
+
return true;
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
message: 'page_size="*" required 1-3 fields to be specified',
|
|
62
|
+
path: ['page_size', 'fields'],
|
|
63
|
+
},
|
|
64
|
+
);
|
|
41
65
|
|
|
42
66
|
/**
|
|
43
67
|
* Creates a new BibleClient instance.
|
|
@@ -57,12 +81,12 @@ export class BibleClient {
|
|
|
57
81
|
async getVersions(
|
|
58
82
|
language_ranges: string | string[],
|
|
59
83
|
license_id?: string | number,
|
|
60
|
-
options?:
|
|
84
|
+
options?: z.infer<typeof BibleClient.GetVersionsOptionsSchema>,
|
|
61
85
|
): Promise<Collection<BibleVersion>> {
|
|
62
86
|
const languageRangeArray = Array.isArray(language_ranges) ? language_ranges : [language_ranges];
|
|
63
87
|
|
|
64
88
|
const parsedLanguageRanges = z
|
|
65
|
-
.array(
|
|
89
|
+
.array(BibleClient.languageRangesSchema)
|
|
66
90
|
.nonempty('At least one language range is required')
|
|
67
91
|
.parse(languageRangeArray);
|
|
68
92
|
|
|
@@ -70,19 +94,26 @@ export class BibleClient {
|
|
|
70
94
|
'language_ranges[]': parsedLanguageRanges,
|
|
71
95
|
};
|
|
72
96
|
|
|
73
|
-
if (license_id
|
|
97
|
+
if (license_id) {
|
|
74
98
|
params.license_id = license_id;
|
|
75
99
|
}
|
|
76
100
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
pageSizeSchema.parse(options.page_size);
|
|
101
|
+
BibleClient.GetVersionsOptionsSchema.parse(options);
|
|
102
|
+
if (options?.page_size) {
|
|
80
103
|
params.page_size = options.page_size;
|
|
81
104
|
}
|
|
82
105
|
|
|
83
|
-
if (options?.
|
|
106
|
+
if (options?.fields) {
|
|
107
|
+
params['fields[]'] = options.fields;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (options?.page_token) {
|
|
84
111
|
params.page_token = options.page_token;
|
|
85
112
|
}
|
|
113
|
+
|
|
114
|
+
if (options?.all_available) {
|
|
115
|
+
params.all_available = 'true';
|
|
116
|
+
}
|
|
86
117
|
return this.client.get<Collection<BibleVersion>>(`/v1/bibles`, params);
|
|
87
118
|
}
|
|
88
119
|
|
|
@@ -92,7 +123,7 @@ export class BibleClient {
|
|
|
92
123
|
* @returns The requested BibleVersion object.
|
|
93
124
|
*/
|
|
94
125
|
async getVersion(id: number): Promise<BibleVersion> {
|
|
95
|
-
|
|
126
|
+
BibleClient.versionIdSchema.parse(id);
|
|
96
127
|
return this.client.get<BibleVersion>(`/v1/bibles/${id}`);
|
|
97
128
|
}
|
|
98
129
|
|
|
@@ -105,7 +136,7 @@ export class BibleClient {
|
|
|
105
136
|
* available in the Bible version.
|
|
106
137
|
*/
|
|
107
138
|
async getBooks(versionId: number, canon?: CANON): Promise<Collection<BibleBook>> {
|
|
108
|
-
|
|
139
|
+
BibleClient.versionIdSchema.parse(versionId);
|
|
109
140
|
return this.client.get<Collection<BibleBook>>(`/v1/bibles/${versionId}/books`, {
|
|
110
141
|
...(canon && { canon }),
|
|
111
142
|
});
|
|
@@ -120,8 +151,8 @@ export class BibleClient {
|
|
|
120
151
|
* available. Use the `passage_id` with `getPassage()` to fetch intro content.
|
|
121
152
|
*/
|
|
122
153
|
async getBook(versionId: number, book: string): Promise<BibleBook> {
|
|
123
|
-
|
|
124
|
-
|
|
154
|
+
BibleClient.versionIdSchema.parse(versionId);
|
|
155
|
+
BibleClient.bookSchema.parse(book);
|
|
125
156
|
return this.client.get<BibleBook>(`/v1/bibles/${versionId}/books/${book}`);
|
|
126
157
|
}
|
|
127
158
|
|
|
@@ -132,8 +163,8 @@ export class BibleClient {
|
|
|
132
163
|
* @returns An array of BibleChapter objects.
|
|
133
164
|
*/
|
|
134
165
|
async getChapters(versionId: number, book: string): Promise<Collection<BibleChapter>> {
|
|
135
|
-
|
|
136
|
-
|
|
166
|
+
BibleClient.versionIdSchema.parse(versionId);
|
|
167
|
+
BibleClient.bookSchema.parse(book);
|
|
137
168
|
return this.client.get<Collection<BibleChapter>>(
|
|
138
169
|
`/v1/bibles/${versionId}/books/${book}/chapters`,
|
|
139
170
|
);
|
|
@@ -147,9 +178,9 @@ export class BibleClient {
|
|
|
147
178
|
* @returns The requested BibleChapter object.
|
|
148
179
|
*/
|
|
149
180
|
async getChapter(versionId: number, book: string, chapter: number): Promise<BibleChapter> {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
181
|
+
BibleClient.versionIdSchema.parse(versionId);
|
|
182
|
+
BibleClient.bookSchema.parse(book);
|
|
183
|
+
BibleClient.chapterSchema.parse(chapter);
|
|
153
184
|
|
|
154
185
|
return this.client.get<BibleChapter>(
|
|
155
186
|
`/v1/bibles/${versionId}/books/${book}/chapters/${chapter}`,
|
|
@@ -168,9 +199,9 @@ export class BibleClient {
|
|
|
168
199
|
book: string,
|
|
169
200
|
chapter: number,
|
|
170
201
|
): Promise<Collection<BibleVerse>> {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
202
|
+
BibleClient.versionIdSchema.parse(versionId);
|
|
203
|
+
BibleClient.bookSchema.parse(book);
|
|
204
|
+
BibleClient.chapterSchema.parse(chapter);
|
|
174
205
|
|
|
175
206
|
return this.client.get<Collection<BibleVerse>>(
|
|
176
207
|
`/v1/bibles/${versionId}/books/${book}/chapters/${chapter}/verses`,
|
|
@@ -191,10 +222,10 @@ export class BibleClient {
|
|
|
191
222
|
chapter: number,
|
|
192
223
|
verse: number,
|
|
193
224
|
): Promise<BibleVerse> {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
225
|
+
BibleClient.versionIdSchema.parse(versionId);
|
|
226
|
+
BibleClient.bookSchema.parse(book);
|
|
227
|
+
BibleClient.chapterSchema.parse(chapter);
|
|
228
|
+
BibleClient.verseSchema.parse(verse);
|
|
198
229
|
|
|
199
230
|
return this.client.get<BibleVerse>(
|
|
200
231
|
`/v1/bibles/${versionId}/books/${book}/chapters/${chapter}/verses/${verse}`,
|
|
@@ -229,12 +260,12 @@ export class BibleClient {
|
|
|
229
260
|
include_headings?: boolean,
|
|
230
261
|
include_notes?: boolean,
|
|
231
262
|
): Promise<BiblePassage> {
|
|
232
|
-
|
|
263
|
+
BibleClient.versionIdSchema.parse(versionId);
|
|
233
264
|
if (include_headings !== undefined) {
|
|
234
|
-
|
|
265
|
+
BibleClient.booleanSchema.parse(include_headings);
|
|
235
266
|
}
|
|
236
267
|
if (include_notes !== undefined) {
|
|
237
|
-
|
|
268
|
+
BibleClient.booleanSchema.parse(include_notes);
|
|
238
269
|
}
|
|
239
270
|
const params: Record<string, string | number | boolean> = {
|
|
240
271
|
format,
|
|
@@ -254,7 +285,7 @@ export class BibleClient {
|
|
|
254
285
|
* @returns The BibleIndex object containing full hierarchy of books, chapters, and verses.
|
|
255
286
|
*/
|
|
256
287
|
async getIndex(versionId: number): Promise<BibleIndex> {
|
|
257
|
-
|
|
288
|
+
BibleClient.versionIdSchema.parse(versionId);
|
|
258
289
|
return this.client.get<BibleIndex>(`/v1/bibles/${versionId}/index`);
|
|
259
290
|
}
|
|
260
291
|
|