@youversion/platform-core 1.2.1 → 1.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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @youversion/platform-core@1.2.1 build /home/runner/work/platform-sdk-react/platform-sdk-react/packages/core
2
+ > @youversion/platform-core@1.3.0 build /home/runner/work/platform-sdk-react/platform-sdk-react/packages/core
3
3
  > tsup src/index.ts --format cjs,esm --dts
4
4
 
5
5
  CLI Building entry: src/index.ts
@@ -8,11 +8,11 @@
8
8
  CLI Target: es2022
9
9
  CJS Build start
10
10
  ESM Build start
11
- ESM dist/index.js 41.09 KB
11
+ ESM dist/index.js 41.47 KB
12
12
  ESM ⚡️ Build success in 34ms
13
- CJS dist/index.cjs 42.90 KB
14
- CJS ⚡️ Build success in 34ms
13
+ CJS dist/index.cjs 43.29 KB
14
+ CJS ⚡️ Build success in 35ms
15
15
  DTS Build start
16
- DTS ⚡️ Build success in 2020ms
17
- DTS dist/index.d.cts 32.78 KB
18
- DTS dist/index.d.ts 32.78 KB
16
+ DTS ⚡️ Build success in 1804ms
17
+ DTS dist/index.d.cts 32.84 KB
18
+ DTS dist/index.d.ts 32.84 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @youversion/platform-core
2
2
 
3
+ ## 1.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - b2b86c2: Add support for array query parameters in API client and improve language range handling
8
+ - **API Client**: Enhanced query string serialization to support array parameters, properly formatting them as repeated keys (e.g., `?param=one&param=two`)
9
+ - **Bible Client**: Updated `getVersions()` method to accept either a single language range string or an array of language ranges, providing more flexibility for filtering Bible versions
10
+ - **Schema**: Renamed language range schema to use plural naming convention for consistency
11
+ - **Testing**: Added comprehensive test coverage for query string building with both scalar and array parameters
12
+
13
+ This change maintains backward compatibility while providing more flexible API parameter handling.
14
+
3
15
  ## 1.2.1
4
16
 
5
17
  ### Patch Changes
package/dist/index.cjs CHANGED
@@ -69,9 +69,15 @@ var ApiClient = class {
69
69
  */
70
70
  buildQueryString(params) {
71
71
  if (!params) return "";
72
- const queryString = new URLSearchParams(
73
- Object.entries(params).map(([key, value]) => [key, String(value)])
74
- ).toString();
72
+ const searchParams = new URLSearchParams();
73
+ Object.entries(params).forEach(([key, value]) => {
74
+ if (Array.isArray(value)) {
75
+ value.forEach((item) => searchParams.append(key, String(item)));
76
+ } else {
77
+ searchParams.append(key, String(value));
78
+ }
79
+ });
80
+ const queryString = searchParams.toString();
75
81
  return queryString ? `?${queryString}` : "";
76
82
  }
77
83
  /**
@@ -205,14 +211,15 @@ var BibleClient = class {
205
211
  /**
206
212
  * Fetches a collection of Bible versions filtered by language ranges.
207
213
  *
208
- * @param language_ranges - A comma-separated list of language codes or ranges to filter the versions (required).
214
+ * @param language_ranges - One or more language codes or ranges to filter the versions (required).
209
215
  * @param license_id - Optional license ID to filter versions by license.
210
216
  * @returns A promise that resolves to a collection of BibleVersion objects.
211
217
  */
212
218
  async getVersions(language_ranges, license_id) {
213
- this.languageRangesSchema.parse(language_ranges);
219
+ const languageRangeArray = Array.isArray(language_ranges) ? language_ranges : [language_ranges];
220
+ const parsedLanguageRanges = import_zod.z.array(this.languageRangesSchema).nonempty("At least one language range is required").parse(languageRangeArray);
214
221
  const params = {
215
- "language_ranges[]": language_ranges
222
+ "language_ranges[]": parsedLanguageRanges
216
223
  };
217
224
  if (license_id !== void 0) {
218
225
  params.license_id = license_id;
package/dist/index.d.cts CHANGED
@@ -258,7 +258,8 @@ interface HighlightColor {
258
258
  label: string;
259
259
  }
260
260
 
261
- type QueryParams = Record<string, string | number | boolean>;
261
+ type PrimitiveQueryParam = string | number | boolean;
262
+ type QueryParams = Record<string, PrimitiveQueryParam | PrimitiveQueryParam[]>;
262
263
  type RequestData = Record<string, string | number | boolean | object>;
263
264
  type RequestHeaders = Record<string, string>;
264
265
  /**
@@ -333,11 +334,11 @@ declare class BibleClient {
333
334
  /**
334
335
  * Fetches a collection of Bible versions filtered by language ranges.
335
336
  *
336
- * @param language_ranges - A comma-separated list of language codes or ranges to filter the versions (required).
337
+ * @param language_ranges - One or more language codes or ranges to filter the versions (required).
337
338
  * @param license_id - Optional license ID to filter versions by license.
338
339
  * @returns A promise that resolves to a collection of BibleVersion objects.
339
340
  */
340
- getVersions(language_ranges: string, license_id?: string | number): Promise<Collection<BibleVersion>>;
341
+ getVersions(language_ranges: string | string[], license_id?: string | number): Promise<Collection<BibleVersion>>;
341
342
  /**
342
343
  * Fetches a Bible version by its ID.
343
344
  * @param id The version ID.
package/dist/index.d.ts CHANGED
@@ -258,7 +258,8 @@ interface HighlightColor {
258
258
  label: string;
259
259
  }
260
260
 
261
- type QueryParams = Record<string, string | number | boolean>;
261
+ type PrimitiveQueryParam = string | number | boolean;
262
+ type QueryParams = Record<string, PrimitiveQueryParam | PrimitiveQueryParam[]>;
262
263
  type RequestData = Record<string, string | number | boolean | object>;
263
264
  type RequestHeaders = Record<string, string>;
264
265
  /**
@@ -333,11 +334,11 @@ declare class BibleClient {
333
334
  /**
334
335
  * Fetches a collection of Bible versions filtered by language ranges.
335
336
  *
336
- * @param language_ranges - A comma-separated list of language codes or ranges to filter the versions (required).
337
+ * @param language_ranges - One or more language codes or ranges to filter the versions (required).
337
338
  * @param license_id - Optional license ID to filter versions by license.
338
339
  * @returns A promise that resolves to a collection of BibleVersion objects.
339
340
  */
340
- getVersions(language_ranges: string, license_id?: string | number): Promise<Collection<BibleVersion>>;
341
+ getVersions(language_ranges: string | string[], license_id?: string | number): Promise<Collection<BibleVersion>>;
341
342
  /**
342
343
  * Fetches a Bible version by its ID.
343
344
  * @param id The version ID.
package/dist/index.js CHANGED
@@ -30,9 +30,15 @@ var ApiClient = class {
30
30
  */
31
31
  buildQueryString(params) {
32
32
  if (!params) return "";
33
- const queryString = new URLSearchParams(
34
- Object.entries(params).map(([key, value]) => [key, String(value)])
35
- ).toString();
33
+ const searchParams = new URLSearchParams();
34
+ Object.entries(params).forEach(([key, value]) => {
35
+ if (Array.isArray(value)) {
36
+ value.forEach((item) => searchParams.append(key, String(item)));
37
+ } else {
38
+ searchParams.append(key, String(value));
39
+ }
40
+ });
41
+ const queryString = searchParams.toString();
36
42
  return queryString ? `?${queryString}` : "";
37
43
  }
38
44
  /**
@@ -166,14 +172,15 @@ var BibleClient = class {
166
172
  /**
167
173
  * Fetches a collection of Bible versions filtered by language ranges.
168
174
  *
169
- * @param language_ranges - A comma-separated list of language codes or ranges to filter the versions (required).
175
+ * @param language_ranges - One or more language codes or ranges to filter the versions (required).
170
176
  * @param license_id - Optional license ID to filter versions by license.
171
177
  * @returns A promise that resolves to a collection of BibleVersion objects.
172
178
  */
173
179
  async getVersions(language_ranges, license_id) {
174
- this.languageRangesSchema.parse(language_ranges);
180
+ const languageRangeArray = Array.isArray(language_ranges) ? language_ranges : [language_ranges];
181
+ const parsedLanguageRanges = z.array(this.languageRangesSchema).nonempty("At least one language range is required").parse(languageRangeArray);
175
182
  const params = {
176
- "language_ranges[]": language_ranges
183
+ "language_ranges[]": parsedLanguageRanges
177
184
  };
178
185
  if (license_id !== void 0) {
179
186
  params.license_id = license_id;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@youversion/platform-core",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -1,4 +1,5 @@
1
1
  import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { http, HttpResponse } from 'msw';
2
3
  import { ApiClient } from '../client';
3
4
  import { BibleClient } from '../bible';
4
5
  import {
@@ -9,6 +10,8 @@ import {
9
10
  BibleVersionSchema,
10
11
  VOTDSchema,
11
12
  } from '../schemas';
13
+ import { server } from './setup';
14
+ import { mockVersions } from './MockVersions';
12
15
 
13
16
  describe('BibleClient', () => {
14
17
  let apiClient: ApiClient;
@@ -34,6 +37,50 @@ describe('BibleClient', () => {
34
37
  expect(hasNIV).toBe(true);
35
38
  });
36
39
 
40
+ it('should send multiple language ranges when provided', async () => {
41
+ server.use(
42
+ http.get('https://api.youversion.com/v1/bibles', ({ request }) => {
43
+ const url = new URL(request.url);
44
+ const languageRanges = url.searchParams.getAll('language_ranges[]');
45
+
46
+ expect(languageRanges).toEqual(['en*', 'es*']);
47
+
48
+ return HttpResponse.json({
49
+ data: mockVersions,
50
+ next_page_token: null,
51
+ total_size: mockVersions.length,
52
+ });
53
+ }),
54
+ );
55
+
56
+ const versions = await bibleClient.getVersions(['en*', 'es*']);
57
+
58
+ const { success } = BibleVersionSchema.safeParse(versions.data[0]);
59
+ expect(success).toBe(true);
60
+ });
61
+
62
+ it('should accept a wildcard language range', async () => {
63
+ server.use(
64
+ http.get('https://api.youversion.com/v1/bibles', ({ request }) => {
65
+ const url = new URL(request.url);
66
+ const languageRanges = url.searchParams.getAll('language_ranges[]');
67
+
68
+ expect(languageRanges).toEqual(['*']);
69
+
70
+ return HttpResponse.json({
71
+ data: mockVersions,
72
+ next_page_token: null,
73
+ total_size: mockVersions.length,
74
+ });
75
+ }),
76
+ );
77
+
78
+ const versions = await bibleClient.getVersions('*');
79
+
80
+ const { success } = BibleVersionSchema.safeParse(versions.data[0]);
81
+ expect(success).toBe(true);
82
+ });
83
+
37
84
  it('should throw an error for invalid language ranges', async () => {
38
85
  await expect(bibleClient.getVersions('')).rejects.toThrow(
39
86
  'Language ranges must be a non-empty string',
@@ -41,6 +88,9 @@ describe('BibleClient', () => {
41
88
  await expect(bibleClient.getVersions(' ')).rejects.toThrow(
42
89
  'Language ranges must be a non-empty string',
43
90
  );
91
+ await expect(bibleClient.getVersions([])).rejects.toThrow(
92
+ 'At least one language range is required',
93
+ );
44
94
  });
45
95
  });
46
96
 
@@ -43,6 +43,45 @@ describe('ApiClient', () => {
43
43
  });
44
44
  });
45
45
 
46
+ describe('buildQueryString', () => {
47
+ const buildQueryString = (params?: Parameters<ApiClient['get']>[1]) =>
48
+ (
49
+ apiClient as unknown as {
50
+ buildQueryString: (params?: Parameters<ApiClient['get']>[1]) => string;
51
+ }
52
+ ).buildQueryString(params);
53
+
54
+ it('should serialize single scalar parameter', () => {
55
+ const query = buildQueryString({ param: 'value' });
56
+
57
+ expect(query).toBe('?param=value');
58
+ });
59
+
60
+ it('should serialize an array of length 1 as repeated key', () => {
61
+ const query = buildQueryString({ param: ['only'] });
62
+
63
+ expect(query).toBe('?param=only');
64
+ });
65
+
66
+ it('should serialize an array of length 2 as repeated keys', () => {
67
+ const query = buildQueryString({ param: ['one', 'two'] });
68
+
69
+ expect(query).toBe('?param=one&param=two');
70
+ });
71
+
72
+ it('should serialize an array of length 3 as repeated keys', () => {
73
+ const query = buildQueryString({ param: ['one', 'two', 'three'] });
74
+
75
+ expect(query).toBe('?param=one&param=two&param=three');
76
+ });
77
+
78
+ it('should handle both scalar and array parameters together', () => {
79
+ const query = buildQueryString({ param: 'value', list: ['one', 'two'] });
80
+
81
+ expect(query).toBe('?param=value&list=one&list=two');
82
+ });
83
+ });
84
+
46
85
  describe('get', () => {
47
86
  it('should make GET request and return data', async () => {
48
87
  server.use(
@@ -71,6 +110,22 @@ describe('ApiClient', () => {
71
110
 
72
111
  expect(result).toEqual({ param: 'value' });
73
112
  });
113
+
114
+ it('should include array query parameters as repeated keys', async () => {
115
+ server.use(
116
+ http.get('https://test_placeholder.youversion.com/test', ({ request }) => {
117
+ const url = new URL(request.url);
118
+ const params = url.searchParams.getAll('param');
119
+ return HttpResponse.json({ params });
120
+ }),
121
+ );
122
+
123
+ const result = await apiClient.get<{ params: string[] }>('/test', {
124
+ param: ['one', 'two'],
125
+ });
126
+
127
+ expect(result).toEqual({ params: ['one', 'two'] });
128
+ });
74
129
  });
75
130
 
76
131
  describe('post', () => {
package/src/bible.ts CHANGED
@@ -50,17 +50,23 @@ export class BibleClient {
50
50
  /**
51
51
  * Fetches a collection of Bible versions filtered by language ranges.
52
52
  *
53
- * @param language_ranges - A comma-separated list of language codes or ranges to filter the versions (required).
53
+ * @param language_ranges - One or more language codes or ranges to filter the versions (required).
54
54
  * @param license_id - Optional license ID to filter versions by license.
55
55
  * @returns A promise that resolves to a collection of BibleVersion objects.
56
56
  */
57
57
  async getVersions(
58
- language_ranges: string,
58
+ language_ranges: string | string[],
59
59
  license_id?: string | number,
60
60
  ): Promise<Collection<BibleVersion>> {
61
- this.languageRangesSchema.parse(language_ranges);
62
- const params: Record<string, string | number> = {
63
- 'language_ranges[]': language_ranges,
61
+ const languageRangeArray = Array.isArray(language_ranges) ? language_ranges : [language_ranges];
62
+
63
+ const parsedLanguageRanges = z
64
+ .array(this.languageRangesSchema)
65
+ .nonempty('At least one language range is required')
66
+ .parse(languageRangeArray);
67
+
68
+ const params: Record<string, string | number | string[]> = {
69
+ 'language_ranges[]': parsedLanguageRanges,
64
70
  };
65
71
  if (license_id !== undefined) {
66
72
  params.license_id = license_id;
package/src/client.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { ApiConfig } from './types';
2
2
 
3
- type QueryParams = Record<string, string | number | boolean>;
3
+ type PrimitiveQueryParam = string | number | boolean;
4
+ type QueryParams = Record<string, PrimitiveQueryParam | PrimitiveQueryParam[]>;
4
5
  type RequestData = Record<string, string | number | boolean | object>;
5
6
  type RequestHeaders = Record<string, string>;
6
7
 
@@ -41,9 +42,18 @@ export class ApiClient {
41
42
  */
42
43
  private buildQueryString(params?: QueryParams): string {
43
44
  if (!params) return '';
44
- const queryString = new URLSearchParams(
45
- Object.entries(params).map(([key, value]) => [key, String(value)]),
46
- ).toString();
45
+
46
+ const searchParams = new URLSearchParams();
47
+
48
+ Object.entries(params).forEach(([key, value]) => {
49
+ if (Array.isArray(value)) {
50
+ value.forEach((item) => searchParams.append(key, String(item)));
51
+ } else {
52
+ searchParams.append(key, String(value));
53
+ }
54
+ });
55
+
56
+ const queryString = searchParams.toString();
47
57
  return queryString ? `?${queryString}` : '';
48
58
  }
49
59