@youversion/platform-core 0.8.2 → 0.10.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.
@@ -1,8 +1,6 @@
1
1
  import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
2
  import { SignInWithYouVersionPKCEAuthorizationRequestBuilder } from '../SignInWithYouVersionPKCE';
3
3
  import { YouVersionPlatformConfiguration } from '../YouVersionPlatformConfiguration';
4
- import { SignInWithYouVersionPermission } from '../SignInWithYouVersionResult';
5
- import type { SignInWithYouVersionPermissionValues } from '../types/auth';
6
4
  import { setupBrowserMocks, cleanupBrowserMocks } from './mocks/browser';
7
5
 
8
6
  describe('SignInWithYouVersionPKCEAuthorizationRequestBuilder', () => {
@@ -61,15 +59,10 @@ describe('SignInWithYouVersionPKCEAuthorizationRequestBuilder', () => {
61
59
  .mockReturnValueOnce('stateBase64==') // State
62
60
  .mockReturnValueOnce('nonceBase64=='); // Nonce
63
61
 
64
- const permissions = new Set<SignInWithYouVersionPermissionValues>([
65
- SignInWithYouVersionPermission.bibles,
66
- SignInWithYouVersionPermission.highlights,
67
- ]);
68
62
  const redirectURL = new URL('https://example.com/callback');
69
63
 
70
64
  const result = await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make(
71
65
  'test-app-key',
72
- permissions,
73
66
  redirectURL,
74
67
  );
75
68
 
@@ -101,17 +94,14 @@ describe('SignInWithYouVersionPKCEAuthorizationRequestBuilder', () => {
101
94
  mocks.crypto.subtle.digest.mockResolvedValue(new Uint8Array(32).buffer);
102
95
  mocks.btoa.mockImplementation((str: string) => `base64_${callCount}_${str.length}`);
103
96
 
104
- const permissions = new Set<SignInWithYouVersionPermissionValues>();
105
97
  const redirectURL = new URL('https://example.com/callback');
106
98
 
107
99
  const result1 = await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make(
108
100
  'app-key',
109
- permissions,
110
101
  redirectURL,
111
102
  );
112
103
  const result2 = await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make(
113
104
  'app-key',
114
- permissions,
115
105
  redirectURL,
116
106
  );
117
107
 
@@ -136,14 +126,10 @@ describe('SignInWithYouVersionPKCEAuthorizationRequestBuilder', () => {
136
126
  });
137
127
 
138
128
  it('should build authorization URL with all required OAuth2 parameters', async () => {
139
- const permissions = new Set<SignInWithYouVersionPermissionValues>([
140
- SignInWithYouVersionPermission.bibles,
141
- ]);
142
129
  const redirectURL = new URL('https://example.com/callback');
143
130
 
144
131
  const result = await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make(
145
132
  'test-app-key',
146
- permissions,
147
133
  redirectURL,
148
134
  );
149
135
 
@@ -163,12 +149,10 @@ describe('SignInWithYouVersionPKCEAuthorizationRequestBuilder', () => {
163
149
  });
164
150
 
165
151
  it('should handle redirect URL with trailing slash', async () => {
166
- const permissions = new Set<SignInWithYouVersionPermissionValues>();
167
152
  const redirectURL = new URL('https://example.com/callback/');
168
153
 
169
154
  const result = await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make(
170
155
  'test-app-key',
171
- permissions,
172
156
  redirectURL,
173
157
  );
174
158
 
@@ -177,12 +161,10 @@ describe('SignInWithYouVersionPKCEAuthorizationRequestBuilder', () => {
177
161
  });
178
162
 
179
163
  it('should include x-yvp-installation-id param', async () => {
180
- const permissions = new Set<SignInWithYouVersionPermissionValues>();
181
164
  const redirectURL = new URL('https://example.com/callback');
182
165
 
183
166
  const result = await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make(
184
167
  'test-app-key',
185
- permissions,
186
168
  redirectURL,
187
169
  );
188
170
 
@@ -190,73 +172,41 @@ describe('SignInWithYouVersionPKCEAuthorizationRequestBuilder', () => {
190
172
  expect(params.get('x-yvp-installation-id')).not.toBeFalsy();
191
173
  });
192
174
 
193
- it('should create scope string with permissions and openid', async () => {
194
- const permissions = new Set<SignInWithYouVersionPermissionValues>([
195
- SignInWithYouVersionPermission.bibles,
196
- SignInWithYouVersionPermission.highlights,
197
- ]);
175
+ it('should create scope string with profile and openid', async () => {
198
176
  const redirectURL = new URL('https://example.com/callback');
199
177
 
200
178
  const result = await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make(
201
179
  'test-app-key',
202
- permissions,
203
180
  redirectURL,
181
+ ['profile'],
204
182
  );
205
183
 
206
184
  const params = new URLSearchParams(result.url.search);
207
185
  const scope = params.get('scope');
208
186
 
209
- expect(scope).toContain('bibles');
210
- expect(scope).toContain('highlights');
211
- expect(scope).toContain('openid');
212
- });
213
-
214
- it('should sort permissions alphabetically', async () => {
215
- const permissions = new Set<SignInWithYouVersionPermissionValues>([
216
- SignInWithYouVersionPermission.votd,
217
- SignInWithYouVersionPermission.bibles,
218
- SignInWithYouVersionPermission.demographics,
219
- ]);
220
- const redirectURL = new URL('https://example.com/callback');
221
-
222
- const result = await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make(
223
- 'test-app-key',
224
- permissions,
225
- redirectURL,
226
- );
227
-
228
- const params = new URLSearchParams(result.url.search);
229
- const scope = params.get('scope');
230
-
231
- // Should be sorted: bibles demographics votd openid
232
- expect(scope).toBe('bibles demographics votd openid');
187
+ expect(scope).toContain('profile');
233
188
  });
234
189
 
235
190
  it('should add openid when not present', async () => {
236
- const permissions = new Set<SignInWithYouVersionPermissionValues>([
237
- SignInWithYouVersionPermission.bibles,
238
- ]);
239
191
  const redirectURL = new URL('https://example.com/callback');
240
192
 
241
193
  const result = await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make(
242
194
  'test-app-key',
243
- permissions,
244
195
  redirectURL,
196
+ ['profile'],
245
197
  );
246
198
 
247
199
  const params = new URLSearchParams(result.url.search);
248
200
  const scope = params.get('scope');
249
201
 
250
- expect(scope).toBe('bibles openid');
202
+ expect(scope).toBe('profile openid');
251
203
  });
252
204
 
253
- it('should handle empty permissions set', async () => {
254
- const permissions = new Set<SignInWithYouVersionPermissionValues>();
205
+ it('should handle empty scope params', async () => {
255
206
  const redirectURL = new URL('https://example.com/callback');
256
207
 
257
208
  const result = await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make(
258
209
  'test-app-key',
259
- permissions,
260
210
  redirectURL,
261
211
  );
262
212
 
@@ -267,16 +217,10 @@ describe('SignInWithYouVersionPKCEAuthorizationRequestBuilder', () => {
267
217
  });
268
218
 
269
219
  it('should not duplicate openid if already present', async () => {
270
- // This test simulates if openid was somehow in the permissions set
271
- const permissions = new Set<SignInWithYouVersionPermissionValues>([
272
- 'openid' as SignInWithYouVersionPermissionValues,
273
- SignInWithYouVersionPermission.bibles,
274
- ]);
275
220
  const redirectURL = new URL('https://example.com/callback');
276
221
 
277
222
  const result = await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make(
278
223
  'test-app-key',
279
- permissions,
280
224
  redirectURL,
281
225
  );
282
226
 
@@ -347,14 +291,9 @@ describe('SignInWithYouVersionPKCEAuthorizationRequestBuilder', () => {
347
291
  });
348
292
 
349
293
  it('should use crypto.getRandomValues for secure random generation', async () => {
350
- const permissions = new Set<SignInWithYouVersionPermissionValues>();
351
294
  const redirectURL = new URL('https://example.com/callback');
352
295
 
353
- await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make(
354
- 'test-app-key',
355
- permissions,
356
- redirectURL,
357
- );
296
+ await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make('test-app-key', redirectURL);
358
297
 
359
298
  // Should call crypto.getRandomValues for code verifier, state, and nonce
360
299
  expect(mocks.crypto.getRandomValues).toHaveBeenCalledTimes(3);
@@ -367,14 +306,9 @@ describe('SignInWithYouVersionPKCEAuthorizationRequestBuilder', () => {
367
306
  });
368
307
 
369
308
  it('should use SHA-256 for code challenge', async () => {
370
- const permissions = new Set<SignInWithYouVersionPermissionValues>();
371
309
  const redirectURL = new URL('https://example.com/callback');
372
310
 
373
- await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make(
374
- 'test-app-key',
375
- permissions,
376
- redirectURL,
377
- );
311
+ await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make('test-app-key', redirectURL);
378
312
 
379
313
  expect(mocks.crypto.subtle.digest).toHaveBeenCalledWith('SHA-256', expect.any(Uint8Array));
380
314
  });
@@ -383,17 +317,14 @@ describe('SignInWithYouVersionPKCEAuthorizationRequestBuilder', () => {
383
317
  // Use real crypto for this test to verify actual randomness
384
318
  cleanupBrowserMocks();
385
319
 
386
- const permissions = new Set<SignInWithYouVersionPermissionValues>();
387
320
  const redirectURL = new URL('https://example.com/callback');
388
321
 
389
322
  const result1 = await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make(
390
323
  'test-app-key',
391
- permissions,
392
324
  redirectURL,
393
325
  );
394
326
  const result2 = await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make(
395
327
  'test-app-key',
396
- permissions,
397
328
  redirectURL,
398
329
  );
399
330
 
@@ -9,7 +9,6 @@ describe('SignInWithYouVersionResult', () => {
9
9
  accessToken: 'test-access-token',
10
10
  expiresIn: 3600,
11
11
  refreshToken: 'test-refresh-token',
12
- permissions: ['votd', 'bibles'],
13
12
  yvpUserId: 'test-user-id',
14
13
  name: 'test user',
15
14
  profilePicture: 'https://this-is-a-test-picture.com',
@@ -19,7 +18,6 @@ describe('SignInWithYouVersionResult', () => {
19
18
  expect(result.accessToken).toBe('test-access-token');
20
19
  expect(result.expiryDate).toStrictEqual(new Date(fixedDate.getTime() + 60 * 60 * 1000));
21
20
  expect(result.refreshToken).toBe('test-refresh-token');
22
- expect(result.permissions).toStrictEqual(['votd', 'bibles']);
23
21
  expect(result.yvpUserId).toBe('test-user-id');
24
22
  expect(result.name).toBe('test user');
25
23
  expect(result.profilePicture).toBe('https://this-is-a-test-picture.com');
@@ -1,9 +1,7 @@
1
1
  import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
2
  import { YouVersionAPIUsers } from '../Users';
3
3
  import { YouVersionPlatformConfiguration } from '../YouVersionPlatformConfiguration';
4
- import { SignInWithYouVersionPermission } from '../SignInWithYouVersionResult';
5
4
  import { YouVersionUserInfo } from '../YouVersionUserInfo';
6
- import type { SignInWithYouVersionPermissionValues } from '../types/auth';
7
5
  import { setupBrowserMocks, cleanupBrowserMocks } from './mocks/browser';
8
6
 
9
7
  const mockFetch = vi.fn();
@@ -40,13 +38,9 @@ describe('YouVersionAPIUsers', () => {
40
38
  it('should throw error when appKey is not set', async () => {
41
39
  YouVersionPlatformConfiguration.appKey = null;
42
40
 
43
- const permissions = new Set<SignInWithYouVersionPermissionValues>([
44
- SignInWithYouVersionPermission.bibles,
45
- ]);
46
-
47
- await expect(
48
- YouVersionAPIUsers.signIn(permissions, 'https://example.com/callback'),
49
- ).rejects.toThrow('YouVersionPlatformConfiguration.appKey must be set before calling signIn');
41
+ await expect(YouVersionAPIUsers.signIn('https://example.com/callback')).rejects.toThrow(
42
+ 'YouVersionPlatformConfiguration.appKey must be set before calling signIn',
43
+ );
50
44
  });
51
45
 
52
46
  it('should create authorization request and redirect on successful signIn', async () => {
@@ -62,13 +56,9 @@ describe('YouVersionAPIUsers', () => {
62
56
  vi.spyOn(crypto.subtle, 'digest').mockResolvedValue(new Uint8Array(32).buffer);
63
57
  mocks.btoa.mockReturnValue('mockBase64Value');
64
58
 
65
- const permissions = new Set<SignInWithYouVersionPermissionValues>([
66
- SignInWithYouVersionPermission.bibles,
67
- SignInWithYouVersionPermission.highlights,
68
- ]);
69
59
  const redirectURL = 'https://example.com/callback';
70
60
 
71
- await YouVersionAPIUsers.signIn(permissions, redirectURL);
61
+ await YouVersionAPIUsers.signIn(redirectURL);
72
62
 
73
63
  // Verify localStorage items stored
74
64
  expect(mocks.localStorage.setItem).toHaveBeenCalledWith(
@@ -210,7 +200,6 @@ describe('YouVersionAPIUsers', () => {
210
200
  expect(result).toBeTruthy();
211
201
  expect(result?.accessToken).toBe('access-token-123');
212
202
  expect(result?.refreshToken).toBe('refresh-token-456');
213
- expect(result?.permissions).toEqual(['bibles', 'highlights']);
214
203
  expect(result?.yvpUserId).toBe('1234567890');
215
204
  expect(result?.name).toBe('John Doe');
216
205
  expect(result?.email).toBe('john@example.com');
@@ -323,32 +312,11 @@ describe('YouVersionAPIUsers', () => {
323
312
  expect(result.accessToken).toBe('access-token-123');
324
313
  expect(result.expiryDate).toStrictEqual(new Date(fixedDate.getTime() + 60 * 60 * 1000));
325
314
  expect(result.refreshToken).toBe('refresh-token-456');
326
- expect(result.permissions).toEqual(['bibles', 'highlights']);
327
315
  expect(result.yvpUserId).toBe('1234567890');
328
316
  expect(result.name).toBe('John Doe');
329
317
  expect(result.email).toBe('john@example.com');
330
318
  expect(result.profilePicture).toBe('https://example.com/avatar.jpg');
331
319
  });
332
-
333
- it('should filter out unknown permissions', () => {
334
- const tokens = {
335
- access_token: 'token',
336
- expires_in: 3600,
337
- id_token:
338
- 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.Et9HFtf9R3GEMA0IICOfFMVXY7kkTX1wr4qCyhIf58U',
339
- refresh_token: 'refresh',
340
- scope: 'bibles unknown_permission highlights invalid_scope',
341
- token_type: 'Bearer',
342
- };
343
-
344
- // Mock JWT decoding
345
- vi.mocked(atob).mockReturnValue(JSON.stringify({ sub: 'user123' }));
346
-
347
- // @ts-expect-error - accessing private method for testing
348
- const result = YouVersionAPIUsers.extractSignInResult(tokens);
349
-
350
- expect(result.permissions).toEqual(['bibles', 'highlights']);
351
- });
352
320
  });
353
321
 
354
322
  describe('decodeJWT', () => {
@@ -557,8 +525,6 @@ describe('YouVersionAPIUsers', () => {
557
525
  // Assert that id_token is preserved (same as original)
558
526
  expect(result?.idToken).toBe(existingIdToken);
559
527
 
560
- expect(result?.permissions).toEqual(['bibles', 'highlights']);
561
-
562
528
  // Verify the refresh token request was made correctly
563
529
  expect(mockFetch).toHaveBeenCalledTimes(1);
564
530
  expect(mockFetch).toHaveBeenCalledWith(
@@ -77,7 +77,7 @@ describe('BibleClient', () => {
77
77
  expect(books.data[0]).toHaveProperty('title', 'Genesis');
78
78
  expect(books.data[0]).toHaveProperty('full_title', 'The First Book of Moses, Called Genesis');
79
79
  expect(books.data[0]).toHaveProperty('abbreviation', 'Gen');
80
- expect(books.data[0]).toHaveProperty('canon', 'ot');
80
+ expect(books.data[0]).toHaveProperty('canon', 'old_testament');
81
81
  expect(books.data[0]?.chapters).toHaveLength(50);
82
82
  });
83
83
  });
@@ -93,7 +93,7 @@ describe('BibleClient', () => {
93
93
  expect(book).toHaveProperty('id', 'GEN');
94
94
  expect(book).toHaveProperty('title', 'Genesis');
95
95
  expect(book).toHaveProperty('abbreviation', 'Gen');
96
- expect(book).toHaveProperty('canon', 'ot');
96
+ expect(book).toHaveProperty('canon', 'old_testament');
97
97
  });
98
98
 
99
99
  it('should throw an error for invalid inputs', async () => {
package/src/bible.ts CHANGED
@@ -81,7 +81,7 @@ export class BibleClient {
81
81
  /**
82
82
  * Fetches all books for a given Bible version.
83
83
  * @param versionId The version ID.
84
- * @param canon Optional canon filter ('ot', 'nt', 'deuterocanon').
84
+ * @param canon Optional canon filter ("old_testament", 'new_testament', 'deuterocanon').
85
85
  * @returns An array of BibleBook objects.
86
86
  */
87
87
  async getBooks(versionId: number, canon?: CANON): Promise<Collection<BibleBook>> {
package/src/index.ts CHANGED
@@ -11,7 +11,6 @@ export * from './Users';
11
11
  export * from './YouVersionUserInfo';
12
12
  export * from './SignInWithYouVersionResult';
13
13
  export * from './YouVersionAPI';
14
- export * from './URLBuilder';
15
14
  export * from './YouVersionPlatformConfiguration';
16
15
  export * from './types';
17
16
  export * from './utils/constants';
@@ -2,11 +2,7 @@ import { z } from 'zod';
2
2
  import { BOOK_IDS } from '../utils/constants';
3
3
  import { BibleChapterSchema } from './chapter';
4
4
 
5
- export const CanonSchema = z.enum([
6
- 'ot', // Old Testament
7
- 'nt', // New Testament
8
- 'dc', // Deuterocanon (Apocrypha)
9
- ]);
5
+ export const CanonSchema = z.enum(['old_testament', 'new_testament', 'deuterocanon']);
10
6
  export type Canon = z.infer<typeof CanonSchema>;
11
7
 
12
8
  // https://github.com/colinhacks/zod/discussions/4934#discussioncomment-13858053
package/src/types/auth.ts CHANGED
@@ -14,3 +14,5 @@ export interface AuthenticationState {
14
14
  result: SignInWithYouVersionResult | null;
15
15
  error: Error | null;
16
16
  }
17
+
18
+ export type AuthenticationScopes = 'profile' | 'email';
@@ -18,5 +18,9 @@ export type { Collection } from '../schemas/collection';
18
18
 
19
19
  // Re-export internal/non-API types
20
20
  export type { ApiConfig } from './api-config';
21
- export type { AuthenticationState, SignInWithYouVersionPermissionValues } from './auth';
21
+ export type {
22
+ AuthenticationState,
23
+ SignInWithYouVersionPermissionValues,
24
+ AuthenticationScopes,
25
+ } from './auth';
22
26
  export type { HighlightColor } from './highlight';
@@ -113,107 +113,107 @@ import type { Canon, BookUsfm } from '../schemas/book';
113
113
  * @see https://github.com/youversion/usfm-references/blob/main/usfm_references/books.py
114
114
  */
115
115
  export const BOOK_CANON: Record<BookUsfm, Canon> = {
116
- GEN: 'ot',
117
- EXO: 'ot',
118
- LEV: 'ot',
119
- NUM: 'ot',
120
- DEU: 'ot',
121
- JOS: 'ot',
122
- JDG: 'ot',
123
- RUT: 'ot',
124
- '1SA': 'ot',
125
- '2SA': 'ot',
126
- '1KI': 'ot',
127
- '2KI': 'ot',
128
- '1CH': 'ot',
129
- '2CH': 'ot',
130
- EZR: 'ot',
131
- NEH: 'ot',
132
- EST: 'ot',
133
- JOB: 'ot',
134
- PSA: 'ot',
135
- PRO: 'ot',
136
- ECC: 'ot',
137
- SNG: 'ot',
138
- ISA: 'ot',
139
- JER: 'ot',
140
- LAM: 'ot',
141
- EZK: 'ot',
142
- DAN: 'ot',
143
- HOS: 'ot',
144
- JOL: 'ot',
145
- AMO: 'ot',
146
- OBA: 'ot',
147
- JON: 'ot',
148
- MIC: 'ot',
149
- NAM: 'ot',
150
- HAB: 'ot',
151
- ZEP: 'ot',
152
- HAG: 'ot',
153
- ZEC: 'ot',
154
- MAL: 'ot',
155
- MAT: 'nt',
156
- MRK: 'nt',
157
- LUK: 'nt',
158
- JHN: 'nt',
159
- ACT: 'nt',
160
- ROM: 'nt',
161
- '1CO': 'nt',
162
- '2CO': 'nt',
163
- GAL: 'nt',
164
- EPH: 'nt',
165
- PHP: 'nt',
166
- COL: 'nt',
167
- '1TH': 'nt',
168
- '2TH': 'nt',
169
- '1TI': 'nt',
170
- '2TI': 'nt',
171
- TIT: 'nt',
172
- PHM: 'nt',
173
- HEB: 'nt',
174
- JAS: 'nt',
175
- '1PE': 'nt',
176
- '2PE': 'nt',
177
- '1JN': 'nt',
178
- '2JN': 'nt',
179
- '3JN': 'nt',
180
- JUD: 'nt',
181
- REV: 'nt',
182
- TOB: 'dc',
183
- JDT: 'dc',
184
- ESG: 'dc',
185
- WIS: 'dc',
186
- SIR: 'dc',
187
- BAR: 'dc',
188
- LJE: 'dc',
189
- S3Y: 'dc',
190
- SUS: 'dc',
191
- BEL: 'dc',
192
- '1MA': 'dc',
193
- '2MA': 'dc',
194
- '3MA': 'dc',
195
- '4MA': 'dc',
196
- '1ES': 'dc',
197
- '2ES': 'dc',
198
- MAN: 'dc',
199
- PS2: 'dc',
200
- ODA: 'dc',
201
- PSS: 'dc',
202
- '3ES': 'dc',
203
- EZA: 'dc',
204
- '5EZ': 'dc',
205
- '6EZ': 'dc',
206
- DAG: 'dc',
207
- PS3: 'dc',
208
- '2BA': 'dc',
209
- LBA: 'dc',
210
- JUB: 'dc',
211
- ENO: 'dc',
212
- '1MQ': 'dc',
213
- '2MQ': 'dc',
214
- '3MQ': 'dc',
215
- REP: 'dc',
216
- '4BA': 'dc',
217
- LAO: 'dc',
218
- LKA: 'nt', // Luke-Acts combo, treated canonically as New Testament
116
+ GEN: 'old_testament',
117
+ EXO: 'old_testament',
118
+ LEV: 'old_testament',
119
+ NUM: 'old_testament',
120
+ DEU: 'old_testament',
121
+ JOS: 'old_testament',
122
+ JDG: 'old_testament',
123
+ RUT: 'old_testament',
124
+ '1SA': 'old_testament',
125
+ '2SA': 'old_testament',
126
+ '1KI': 'old_testament',
127
+ '2KI': 'old_testament',
128
+ '1CH': 'old_testament',
129
+ '2CH': 'old_testament',
130
+ EZR: 'old_testament',
131
+ NEH: 'old_testament',
132
+ EST: 'old_testament',
133
+ JOB: 'old_testament',
134
+ PSA: 'old_testament',
135
+ PRO: 'old_testament',
136
+ ECC: 'old_testament',
137
+ SNG: 'old_testament',
138
+ ISA: 'old_testament',
139
+ JER: 'old_testament',
140
+ LAM: 'old_testament',
141
+ EZK: 'old_testament',
142
+ DAN: 'old_testament',
143
+ HOS: 'old_testament',
144
+ JOL: 'old_testament',
145
+ AMO: 'old_testament',
146
+ OBA: 'old_testament',
147
+ JON: 'old_testament',
148
+ MIC: 'old_testament',
149
+ NAM: 'old_testament',
150
+ HAB: 'old_testament',
151
+ ZEP: 'old_testament',
152
+ HAG: 'old_testament',
153
+ ZEC: 'old_testament',
154
+ MAL: 'old_testament',
155
+ MAT: 'new_testament',
156
+ MRK: 'new_testament',
157
+ LUK: 'new_testament',
158
+ JHN: 'new_testament',
159
+ ACT: 'new_testament',
160
+ ROM: 'new_testament',
161
+ '1CO': 'new_testament',
162
+ '2CO': 'new_testament',
163
+ GAL: 'new_testament',
164
+ EPH: 'new_testament',
165
+ PHP: 'new_testament',
166
+ COL: 'new_testament',
167
+ '1TH': 'new_testament',
168
+ '2TH': 'new_testament',
169
+ '1TI': 'new_testament',
170
+ '2TI': 'new_testament',
171
+ TIT: 'new_testament',
172
+ PHM: 'new_testament',
173
+ HEB: 'new_testament',
174
+ JAS: 'new_testament',
175
+ '1PE': 'new_testament',
176
+ '2PE': 'new_testament',
177
+ '1JN': 'new_testament',
178
+ '2JN': 'new_testament',
179
+ '3JN': 'new_testament',
180
+ JUD: 'new_testament',
181
+ REV: 'new_testament',
182
+ TOB: 'deuterocanon',
183
+ JDT: 'deuterocanon',
184
+ ESG: 'deuterocanon',
185
+ WIS: 'deuterocanon',
186
+ SIR: 'deuterocanon',
187
+ BAR: 'deuterocanon',
188
+ LJE: 'deuterocanon',
189
+ S3Y: 'deuterocanon',
190
+ SUS: 'deuterocanon',
191
+ BEL: 'deuterocanon',
192
+ '1MA': 'deuterocanon',
193
+ '2MA': 'deuterocanon',
194
+ '3MA': 'deuterocanon',
195
+ '4MA': 'deuterocanon',
196
+ '1ES': 'deuterocanon',
197
+ '2ES': 'deuterocanon',
198
+ MAN: 'deuterocanon',
199
+ PS2: 'deuterocanon',
200
+ ODA: 'deuterocanon',
201
+ PSS: 'deuterocanon',
202
+ '3ES': 'deuterocanon',
203
+ EZA: 'deuterocanon',
204
+ '5EZ': 'deuterocanon',
205
+ '6EZ': 'deuterocanon',
206
+ DAG: 'deuterocanon',
207
+ PS3: 'deuterocanon',
208
+ '2BA': 'deuterocanon',
209
+ LBA: 'deuterocanon',
210
+ JUB: 'deuterocanon',
211
+ ENO: 'deuterocanon',
212
+ '1MQ': 'deuterocanon',
213
+ '2MQ': 'deuterocanon',
214
+ '3MQ': 'deuterocanon',
215
+ REP: 'deuterocanon',
216
+ '4BA': 'deuterocanon',
217
+ LAO: 'deuterocanon',
218
+ LKA: 'new_testament', // Luke-Acts combo, treated canonically as New Testament
219
219
  };