pankosmia-lib 0.0.2 → 0.0.5
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/README.md +35 -0
- package/package.json +6 -1
- package/src/http/getLib.js +315 -80
- package/src/http/index.js +6 -4
- package/src/http/postLib.js +374 -119
- package/src/i18n/i18nLib.js +42 -1
- package/src/i18n/index.js +2 -2
- package/src/usfm/index.js +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# pankosmia-lib
|
|
2
|
+
|
|
3
|
+
A utility library for the Pankosmia ecosystem.
|
|
4
|
+
|
|
5
|
+
## Modules
|
|
6
|
+
|
|
7
|
+
The library is divided into multiple modules:
|
|
8
|
+
|
|
9
|
+
- **`http`** — Functions for communicating with a Pankosmia server through HTTP requests (GET/POST).
|
|
10
|
+
- **`i18n`** — Internationalization utilities, mainly the `do18n` function.
|
|
11
|
+
- **`usfm`** — Utility functions for working with USFM.
|
|
12
|
+
|
|
13
|
+
Each module can be imported independently:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { ... } from "pankosmia-lib/http"
|
|
17
|
+
import { ... } from "pankosmia-lib/i18n"
|
|
18
|
+
import { ... } from "pankosmia-lib/usfm"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pnpm add pankosmia-lib
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Testing
|
|
28
|
+
|
|
29
|
+
Tests require a running Pankosmia server.
|
|
30
|
+
|
|
31
|
+
Once the server is running, execute:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pnpm run test
|
|
35
|
+
```
|
package/package.json
CHANGED
package/src/http/getLib.js
CHANGED
|
@@ -1,88 +1,323 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} GetSuccess
|
|
3
|
+
* @property {string} url - The requested URL.
|
|
4
|
+
* @property {true} ok - Indicates that the request succeeded.
|
|
5
|
+
* @property {number} status - The HTTP response status code.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {GetSuccess & {
|
|
10
|
+
* json: any
|
|
11
|
+
* }} GetJsonResult
|
|
12
|
+
*
|
|
13
|
+
* @typedef {GetSuccess & {
|
|
14
|
+
* text: string
|
|
15
|
+
* }} GetTextResult
|
|
16
|
+
*
|
|
17
|
+
* @typedef {GetSuccess & {
|
|
18
|
+
* bytes: ArrayBuffer
|
|
19
|
+
* }} GetBytesResult
|
|
20
|
+
*
|
|
21
|
+
* @typedef {Object} GetError
|
|
22
|
+
* @property {string} url - The requested URL.
|
|
23
|
+
* @property {false} ok - Indicates that the request failed.
|
|
24
|
+
* @property {number} status - The HTTP status code, or 0 when the request
|
|
25
|
+
* failed before receiving an HTTP response.
|
|
26
|
+
* @property {any|string} error - The server error or JavaScript error message.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Performs an HTTP GET request and parses the response as JSON.
|
|
31
|
+
*
|
|
32
|
+
* Use this function when the API endpoint returns a JSON response.
|
|
33
|
+
*
|
|
34
|
+
* @param {string} url - The URL to request.
|
|
35
|
+
* @param {boolean} [debug=false] - If true, logs the result to the console.
|
|
36
|
+
*
|
|
37
|
+
* @returns {Promise<GetJsonResult|GetError>} The HTTP response result.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* const result = await getJson(
|
|
41
|
+
* "http://127.0.0.1:19119/api/settings/languages/en"
|
|
42
|
+
* );
|
|
43
|
+
*
|
|
44
|
+
* if (result.ok) {
|
|
45
|
+
* console.log(result.status);
|
|
46
|
+
* console.log(result.json);
|
|
47
|
+
* } else {
|
|
48
|
+
* console.error(result.error);
|
|
49
|
+
* }
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* // Enable debug logging.
|
|
53
|
+
* const result = await getJson(
|
|
54
|
+
* "https://example.com/api/settings",
|
|
55
|
+
* true
|
|
56
|
+
* );
|
|
57
|
+
*/
|
|
58
|
+
async function getJson(url, debug = false) {
|
|
59
|
+
try {
|
|
60
|
+
const response = await fetch(url);
|
|
61
|
+
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
const result = {
|
|
64
|
+
url,
|
|
65
|
+
ok: false,
|
|
66
|
+
status: response.status,
|
|
67
|
+
error: JSON.parse(await response.text()),
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
if (debug) {
|
|
71
|
+
console.log("getJson", result);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const result = {
|
|
78
|
+
url,
|
|
79
|
+
ok: true,
|
|
80
|
+
status: response.status,
|
|
81
|
+
json: await response.json(),
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
if (debug) {
|
|
85
|
+
console.log("getJson", result);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return result;
|
|
89
|
+
} catch (err) {
|
|
90
|
+
const result = {
|
|
91
|
+
url,
|
|
92
|
+
ok: false,
|
|
93
|
+
status: 0,
|
|
94
|
+
error: err.message,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
if (debug) {
|
|
98
|
+
console.log("getJson", result);
|
|
37
99
|
}
|
|
100
|
+
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
38
103
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Performs an HTTP GET request and returns the response body as text.
|
|
107
|
+
*
|
|
108
|
+
* Use this function when the API endpoint returns plain text, source text,
|
|
109
|
+
* configuration text, USFM, XML, HTML, or another text-based response.
|
|
110
|
+
*
|
|
111
|
+
* @param {string} url - The URL to request.
|
|
112
|
+
* @param {boolean} [debug=false] - If true, logs the result to the console.
|
|
113
|
+
*
|
|
114
|
+
* @returns {Promise<GetTextResult|GetError>} The HTTP response result.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* const result = await getText(
|
|
118
|
+
* "http://127.0.0.1:19119/api/burrito/ingredient/raw/_local_/_local_/plt?ipath=test.usfm"
|
|
119
|
+
* );
|
|
120
|
+
*
|
|
121
|
+
* if (result.ok) {
|
|
122
|
+
* console.log(result.text);
|
|
123
|
+
* } else {
|
|
124
|
+
* console.error(result.error);
|
|
125
|
+
* }
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* // Enable debug logging.
|
|
129
|
+
* const result = await getText(
|
|
130
|
+
* "https://example.com/api/content",
|
|
131
|
+
* true
|
|
132
|
+
* );
|
|
133
|
+
*/
|
|
134
|
+
async function getText(url, debug = false) {
|
|
135
|
+
try {
|
|
136
|
+
const response = await fetch(url);
|
|
137
|
+
|
|
138
|
+
if (!response.ok) {
|
|
139
|
+
const result = {
|
|
140
|
+
url,
|
|
141
|
+
ok: false,
|
|
142
|
+
status: response.status,
|
|
143
|
+
error: JSON.parse(await response.text()),
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
if (debug) {
|
|
147
|
+
console.log("getText", result);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return result;
|
|
75
151
|
}
|
|
152
|
+
|
|
153
|
+
const result = {
|
|
154
|
+
url,
|
|
155
|
+
ok: true,
|
|
156
|
+
status: response.status,
|
|
157
|
+
text: await response.text(),
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
if (debug) {
|
|
161
|
+
console.log("getText", result);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return result;
|
|
165
|
+
} catch (err) {
|
|
166
|
+
const result = {
|
|
167
|
+
url,
|
|
168
|
+
ok: false,
|
|
169
|
+
status: 0,
|
|
170
|
+
error: err.message,
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
if (debug) {
|
|
174
|
+
console.log("getText", result);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
76
179
|
}
|
|
77
180
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
181
|
+
/**
|
|
182
|
+
* Performs an HTTP GET request and returns the response body as binary data.
|
|
183
|
+
*
|
|
184
|
+
* The response is returned as an ArrayBuffer, which can be converted to
|
|
185
|
+
* a Uint8Array when byte-level access is required.
|
|
186
|
+
*
|
|
187
|
+
* @param {string} url - The URL to request.
|
|
188
|
+
* @param {boolean} [debug=false] - If true, logs the result to the console.
|
|
189
|
+
*
|
|
190
|
+
* @returns {Promise<GetBytesResult|GetError>} The HTTP response result.
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* const result = await getBytes(
|
|
194
|
+
* "http://127.0.0.1:19119/api/burrito/ingredient/bytes/_local_/_local_/plt?ipath=test.usfm"
|
|
195
|
+
* );
|
|
196
|
+
*
|
|
197
|
+
* if (result.ok) {
|
|
198
|
+
* const bytes = new Uint8Array(result.bytes);
|
|
199
|
+
*
|
|
200
|
+
* console.log(bytes);
|
|
201
|
+
* } else {
|
|
202
|
+
* console.error(result.error);
|
|
203
|
+
* }
|
|
204
|
+
*
|
|
205
|
+
* @example
|
|
206
|
+
* // Enable debug logging.
|
|
207
|
+
* const result = await getBytes(
|
|
208
|
+
* "https://example.com/api/file",
|
|
209
|
+
* true
|
|
210
|
+
* );
|
|
211
|
+
*/
|
|
212
|
+
async function getBytes(url, debug = false) {
|
|
213
|
+
try {
|
|
214
|
+
const response = await fetch(url);
|
|
215
|
+
|
|
216
|
+
if (!response.ok) {
|
|
217
|
+
const result = {
|
|
218
|
+
url,
|
|
219
|
+
ok: false,
|
|
220
|
+
status: response.status,
|
|
221
|
+
error: JSON.parse(await response.text()),
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
if (debug) {
|
|
225
|
+
console.log("getBytes", result);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return result;
|
|
85
229
|
}
|
|
230
|
+
|
|
231
|
+
const result = {
|
|
232
|
+
url,
|
|
233
|
+
ok: true,
|
|
234
|
+
status: response.status,
|
|
235
|
+
bytes: await response.arrayBuffer(),
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
if (debug) {
|
|
239
|
+
console.log("getBytes", result);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return result;
|
|
243
|
+
} catch (err) {
|
|
244
|
+
const result = {
|
|
245
|
+
url,
|
|
246
|
+
ok: false,
|
|
247
|
+
status: 0,
|
|
248
|
+
error: err.message,
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
if (debug) {
|
|
252
|
+
console.log("getBytes", result);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return result;
|
|
256
|
+
}
|
|
86
257
|
}
|
|
87
258
|
|
|
88
|
-
|
|
259
|
+
/**
|
|
260
|
+
* Performs a GET request for JSON and passes the resulting JSON value
|
|
261
|
+
* to a setter function when the request succeeds.
|
|
262
|
+
*
|
|
263
|
+
* This is a convenience wrapper around {@link getJson}.
|
|
264
|
+
*
|
|
265
|
+
* If the request fails, the error response is returned instead of calling
|
|
266
|
+
* the setter.
|
|
267
|
+
*
|
|
268
|
+
* @param {Object} options - Function options.
|
|
269
|
+
* @param {string} options.url - The URL to request.
|
|
270
|
+
* @param {(json: any) => void} options.setter - Function that receives
|
|
271
|
+
* the parsed JSON response.
|
|
272
|
+
* @param {boolean} [options.debug=false] - If true, logs the request result.
|
|
273
|
+
*
|
|
274
|
+
* @returns {Promise<GetError|undefined>} The error response when the request
|
|
275
|
+
* fails, otherwise undefined.
|
|
276
|
+
*
|
|
277
|
+
* @example
|
|
278
|
+
* let languages;
|
|
279
|
+
*
|
|
280
|
+
* const result = await getAndSetJson({
|
|
281
|
+
* url: "http://127.0.0.1:19119/api/settings/languages/en",
|
|
282
|
+
* setter: (json) => {
|
|
283
|
+
* languages = json;
|
|
284
|
+
* },
|
|
285
|
+
* });
|
|
286
|
+
*
|
|
287
|
+
* if (result) {
|
|
288
|
+
* console.error(result.error);
|
|
289
|
+
* }
|
|
290
|
+
*
|
|
291
|
+
* @example
|
|
292
|
+
* // Using a framework state setter.
|
|
293
|
+
* await getAndSetJson({
|
|
294
|
+
* url: "https://example.com/api/settings",
|
|
295
|
+
* setter: setSettings,
|
|
296
|
+
* });
|
|
297
|
+
*
|
|
298
|
+
* @example
|
|
299
|
+
* // Enable debug logging.
|
|
300
|
+
* await getAndSetJson({
|
|
301
|
+
* url: "https://example.com/api/settings",
|
|
302
|
+
* setter: (json) => {
|
|
303
|
+
* console.log("Received:", json);
|
|
304
|
+
* },
|
|
305
|
+
* debug: true,
|
|
306
|
+
* });
|
|
307
|
+
*/
|
|
308
|
+
const getAndSetJson = async ({ url, setter, debug = false }) => {
|
|
309
|
+
const response = await getJson(url, debug);
|
|
310
|
+
|
|
311
|
+
if (response.ok) {
|
|
312
|
+
setter(response.json);
|
|
313
|
+
} else {
|
|
314
|
+
return response;
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
export {
|
|
319
|
+
getJson,
|
|
320
|
+
getAndSetJson,
|
|
321
|
+
getText,
|
|
322
|
+
getBytes,
|
|
323
|
+
};
|
package/src/http/index.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
|
|
2
|
-
import {getJson, getAndSetJson, getText} from "./getLib";
|
|
3
|
-
import {
|
|
2
|
+
import {getJson, getAndSetJson, getText,getBytes} from "./getLib.js";
|
|
3
|
+
import {postEmpty, postJson, postText,postBytes} from "./postLib.js";
|
|
4
4
|
|
|
5
5
|
export {
|
|
6
6
|
getJson,
|
|
7
7
|
getText,
|
|
8
8
|
getAndSetJson,
|
|
9
|
-
|
|
9
|
+
postEmpty,
|
|
10
10
|
postJson,
|
|
11
|
-
postText
|
|
11
|
+
postText,
|
|
12
|
+
postBytes,
|
|
13
|
+
getBytes
|
|
12
14
|
};
|
package/src/http/postLib.js
CHANGED
|
@@ -1,128 +1,383 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Sends an HTTP POST request without a request body.
|
|
3
|
+
*
|
|
4
|
+
* The server response is expected to contain JSON.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} url - The URL to send the POST request to.
|
|
7
|
+
* @param {boolean} [debug=false] - If true, logs the request result to the console.
|
|
8
|
+
*
|
|
9
|
+
* @returns {Promise<PostEmptyResult>} The HTTP response result.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* // POST /api/settings/languages/en
|
|
13
|
+
* const result = await postEmpty(
|
|
14
|
+
* "http://127.0.0.1:19119/api/settings/languages/en"
|
|
15
|
+
* );
|
|
16
|
+
*
|
|
17
|
+
* if (result.ok) {
|
|
18
|
+
* console.log(result.status);
|
|
19
|
+
* console.log(result.json);
|
|
20
|
+
* } else {
|
|
21
|
+
* console.error(result.error);
|
|
22
|
+
* }
|
|
23
|
+
*/
|
|
24
|
+
async function postEmpty(url, debug = false) {
|
|
25
|
+
try {
|
|
26
|
+
const response = await fetch(url, { method: "POST" });
|
|
27
|
+
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
const result = {
|
|
30
|
+
url,
|
|
31
|
+
ok: false,
|
|
32
|
+
status: response.status,
|
|
33
|
+
error: JSON.parse(await response.text()),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
if (debug) {
|
|
37
|
+
console.log("postEmpty", result);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const result = {
|
|
44
|
+
url,
|
|
45
|
+
ok: true,
|
|
46
|
+
status: response.status,
|
|
47
|
+
json: await response.json(),
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
if (debug) {
|
|
51
|
+
console.log("postEmpty", result);
|
|
37
52
|
}
|
|
53
|
+
|
|
54
|
+
return result;
|
|
55
|
+
} catch (err) {
|
|
56
|
+
const result = {
|
|
57
|
+
url,
|
|
58
|
+
ok: false,
|
|
59
|
+
status: 0,
|
|
60
|
+
error: err.message,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
if (debug) {
|
|
64
|
+
console.log("postEmpty", result);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
38
69
|
}
|
|
39
70
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Sends an HTTP POST request with a JSON request body.
|
|
73
|
+
*
|
|
74
|
+
* The request body is sent using the specified Content-Type.
|
|
75
|
+
* By default, the Content-Type is `application/json`.
|
|
76
|
+
*
|
|
77
|
+
* The server response is expected to contain JSON.
|
|
78
|
+
*
|
|
79
|
+
* @param {string} url - The URL to send the POST request to.
|
|
80
|
+
* @param {string|object} body - The request body. When using an object,
|
|
81
|
+
* convert it to JSON with JSON.stringify() before passing it.
|
|
82
|
+
* @param {boolean} [debug=false] - If true, logs the request result to the console.
|
|
83
|
+
* @param {string} [contentType="application/json"] - The Content-Type
|
|
84
|
+
* header sent with the request.
|
|
85
|
+
*
|
|
86
|
+
* @returns {Promise<PostJsonResult>} The HTTP response result.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* const body = JSON.stringify({
|
|
90
|
+
* payload: JSON.stringify({
|
|
91
|
+
* language: "en",
|
|
92
|
+
* }),
|
|
93
|
+
* });
|
|
94
|
+
*
|
|
95
|
+
* const result = await postJson(
|
|
96
|
+
* "http://127.0.0.1:19119/api/burrito/ingredient/raw/_local_/_local_/plt?ipath=test.json",
|
|
97
|
+
* body
|
|
98
|
+
* );
|
|
99
|
+
*
|
|
100
|
+
* if (result.ok) {
|
|
101
|
+
* console.log(result.json);
|
|
102
|
+
* } else {
|
|
103
|
+
* console.error(result.error);
|
|
104
|
+
* }
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* // Custom Content-Type
|
|
108
|
+
* const result = await postJson(
|
|
109
|
+
* "https://example.com/api/data",
|
|
110
|
+
* JSON.stringify({ hello: "world" }),
|
|
111
|
+
* false,
|
|
112
|
+
* "application/custom+json"
|
|
113
|
+
* );
|
|
114
|
+
*/
|
|
115
|
+
async function postJson(
|
|
116
|
+
url,
|
|
117
|
+
body,
|
|
118
|
+
debug = false,
|
|
119
|
+
contentType = "application/json",
|
|
120
|
+
) {
|
|
121
|
+
try {
|
|
122
|
+
const response = await fetch(url, {
|
|
123
|
+
method: "POST",
|
|
124
|
+
headers: { "Content-Type": contentType },
|
|
125
|
+
body,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (!response.ok) {
|
|
129
|
+
const result = {
|
|
130
|
+
url,
|
|
131
|
+
ok: false,
|
|
132
|
+
status: response.status,
|
|
133
|
+
error: JSON.parse(await response.text()),
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
if (debug) {
|
|
137
|
+
console.log("postJson", result);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const result = {
|
|
144
|
+
url,
|
|
145
|
+
ok: true,
|
|
146
|
+
status: response.status,
|
|
147
|
+
json: await response.json(),
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
if (debug) {
|
|
151
|
+
console.log("postJson", result);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return result;
|
|
155
|
+
} catch (err) {
|
|
156
|
+
const result = {
|
|
157
|
+
url,
|
|
158
|
+
ok: false,
|
|
159
|
+
status: 0,
|
|
160
|
+
error: err.message,
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
if (debug) {
|
|
164
|
+
console.log("postJson", result);
|
|
81
165
|
}
|
|
166
|
+
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
82
169
|
}
|
|
83
170
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
171
|
+
/**
|
|
172
|
+
* Sends an HTTP POST request with a text request body.
|
|
173
|
+
*
|
|
174
|
+
* The request body is sent using the specified Content-Type.
|
|
175
|
+
* By default, the Content-Type is `application/json`.
|
|
176
|
+
*
|
|
177
|
+
* Unlike {@link postJson}, the response body is returned as text.
|
|
178
|
+
*
|
|
179
|
+
* @param {string} url - The URL to send the POST request to.
|
|
180
|
+
* @param {string} body - The text request body.
|
|
181
|
+
* @param {boolean} [debug=false] - If true, logs the request result to the console.
|
|
182
|
+
* @param {string} [contentType="application/json"] - The Content-Type
|
|
183
|
+
* header sent with the request.
|
|
184
|
+
*
|
|
185
|
+
* @returns {Promise<PostTextResult>} The HTTP response result.
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* const body = JSON.stringify({
|
|
189
|
+
* payload: "this is a test",
|
|
190
|
+
* });
|
|
191
|
+
*
|
|
192
|
+
* const result = await postText(
|
|
193
|
+
* "http://127.0.0.1:19119/api/burrito/ingredient/raw/_local_/_local_/plt?ipath=textTest.usfm",
|
|
194
|
+
* body
|
|
195
|
+
* );
|
|
196
|
+
*
|
|
197
|
+
* if (result.ok) {
|
|
198
|
+
* console.log(result.text);
|
|
199
|
+
* } else {
|
|
200
|
+
* console.error(result.error);
|
|
201
|
+
* }
|
|
202
|
+
*
|
|
203
|
+
* @example
|
|
204
|
+
* // Sending plain text instead of JSON
|
|
205
|
+
* const result = await postText(
|
|
206
|
+
* "https://example.com/api/text",
|
|
207
|
+
* "this is a test",
|
|
208
|
+
* false,
|
|
209
|
+
* "text/plain"
|
|
210
|
+
* );
|
|
211
|
+
*/
|
|
212
|
+
async function postText(
|
|
213
|
+
url,
|
|
214
|
+
body,
|
|
215
|
+
debug = false,
|
|
216
|
+
contentType = "application/json",
|
|
217
|
+
) {
|
|
218
|
+
try {
|
|
219
|
+
const response = await fetch(url, {
|
|
220
|
+
method: "POST",
|
|
221
|
+
headers: { "Content-Type": contentType },
|
|
222
|
+
body,
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
if (!response.ok) {
|
|
226
|
+
const result = {
|
|
227
|
+
url,
|
|
228
|
+
ok: false,
|
|
229
|
+
status: response.status,
|
|
230
|
+
error: JSON.parse(await response.text()),
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
if (debug) {
|
|
234
|
+
console.log("postText", result);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return result;
|
|
125
238
|
}
|
|
239
|
+
|
|
240
|
+
const result = {
|
|
241
|
+
url,
|
|
242
|
+
ok: true,
|
|
243
|
+
status: response.status,
|
|
244
|
+
text: await response.text(),
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
if (debug) {
|
|
248
|
+
console.log("postText", result);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return result;
|
|
252
|
+
} catch (err) {
|
|
253
|
+
const result = {
|
|
254
|
+
url,
|
|
255
|
+
ok: false,
|
|
256
|
+
status: 0,
|
|
257
|
+
error: err.message,
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
if (debug) {
|
|
261
|
+
console.log("postText", result);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return result;
|
|
265
|
+
}
|
|
126
266
|
}
|
|
127
267
|
|
|
128
|
-
|
|
268
|
+
/**
|
|
269
|
+
* Sends an HTTP POST request containing binary data or multipart FormData.
|
|
270
|
+
*
|
|
271
|
+
* When using FormData, the Content-Type header is intentionally not set
|
|
272
|
+
* manually. The browser/runtime automatically creates the required
|
|
273
|
+
* multipart boundary.
|
|
274
|
+
*
|
|
275
|
+
* This is useful for uploading files or binary content to an API.
|
|
276
|
+
*
|
|
277
|
+
* @param {string} url - The URL to send the POST request to.
|
|
278
|
+
* @param {BodyInit} body - The request body. This can be FormData,
|
|
279
|
+
* Uint8Array, ArrayBuffer, Blob, or another valid fetch BodyInit.
|
|
280
|
+
* @param {boolean} [debug=false] - If true, logs the request result to the console.
|
|
281
|
+
* @param {string|null} [contentType=null] - Optional Content-Type.
|
|
282
|
+
* This is ignored when `body` is a FormData instance.
|
|
283
|
+
*
|
|
284
|
+
* @returns {Promise<PostBytesResult>} The HTTP response result.
|
|
285
|
+
*
|
|
286
|
+
* @example
|
|
287
|
+
* // Upload a file using multipart/form-data.
|
|
288
|
+
* const form = new FormData();
|
|
289
|
+
*
|
|
290
|
+
* form.append(
|
|
291
|
+
* "file",
|
|
292
|
+
* new Blob(["this is a test"], {
|
|
293
|
+
* type: "application/octet-stream",
|
|
294
|
+
* }),
|
|
295
|
+
* "bytesTest.usfm"
|
|
296
|
+
* );
|
|
297
|
+
*
|
|
298
|
+
* const result = await postBytes(
|
|
299
|
+
* "http://127.0.0.1:19119/api/burrito/ingredient/bytes/_local_/_local_/plt?ipath=bytesTest.usfm",
|
|
300
|
+
* form
|
|
301
|
+
* );
|
|
302
|
+
*
|
|
303
|
+
* if (result.ok) {
|
|
304
|
+
* console.log(result.text);
|
|
305
|
+
* } else {
|
|
306
|
+
* console.error(result.error);
|
|
307
|
+
* }
|
|
308
|
+
*
|
|
309
|
+
* @example
|
|
310
|
+
* // Send raw binary data.
|
|
311
|
+
* const body = new TextEncoder().encode("this is a test");
|
|
312
|
+
*
|
|
313
|
+
* const result = await postBytes(
|
|
314
|
+
* "https://example.com/api/upload",
|
|
315
|
+
* body,
|
|
316
|
+
* true,
|
|
317
|
+
* "application/octet-stream"
|
|
318
|
+
* );
|
|
319
|
+
*/
|
|
320
|
+
async function postBytes(url, body, debug = false, contentType = null) {
|
|
321
|
+
try {
|
|
322
|
+
const headers = {};
|
|
323
|
+
|
|
324
|
+
if (!(body instanceof FormData) && contentType) {
|
|
325
|
+
headers["Content-Type"] = contentType;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const response = await fetch(url, {
|
|
329
|
+
method: "POST",
|
|
330
|
+
headers,
|
|
331
|
+
body,
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
if (!response.ok) {
|
|
335
|
+
const result = {
|
|
336
|
+
url,
|
|
337
|
+
ok: false,
|
|
338
|
+
status: response.status,
|
|
339
|
+
error: JSON.parse(await response.text()),
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
if (debug) {
|
|
343
|
+
console.log("postBytes", result);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return result;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const result = {
|
|
350
|
+
url,
|
|
351
|
+
ok: true,
|
|
352
|
+
status: response.status,
|
|
353
|
+
text: await response.text(),
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
if (debug) {
|
|
357
|
+
console.log("postBytes", result);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return result;
|
|
361
|
+
} catch (err) {
|
|
362
|
+
const result = {
|
|
363
|
+
url,
|
|
364
|
+
ok: false,
|
|
365
|
+
status: 0,
|
|
366
|
+
error: err.message,
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
if (debug) {
|
|
370
|
+
console.log("postBytes", result);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return result;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export {
|
|
378
|
+
postEmpty,
|
|
379
|
+
postJson,
|
|
380
|
+
postText,
|
|
381
|
+
postBytes,
|
|
382
|
+
};
|
|
383
|
+
|
package/src/i18n/i18nLib.js
CHANGED
|
@@ -1,3 +1,44 @@
|
|
|
1
1
|
const doI18n = (key, source) => source[key] || key;
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
const doInterpolatedI18n = (key, values, source = {}) => {
|
|
4
|
+
if (!Array.isArray(values)) {
|
|
5
|
+
throw new TypeError("Interpolation values must be an array of strings");
|
|
6
|
+
}
|
|
7
|
+
for (const value of values) {
|
|
8
|
+
if (typeof value !== "string") {
|
|
9
|
+
throw new TypeError("Interpolation values must be strings");
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const template = source && source[key];
|
|
14
|
+
if (!template) {
|
|
15
|
+
throw new Error(`No localized string found for key: ${key}`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (typeof template !== "string") {
|
|
19
|
+
throw new TypeError("The localized string template must be a string");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const invalidPlaceholder = template.match(/%([^%]*)%/g)?.find((placeholder) => {
|
|
23
|
+
const position = placeholder.slice(1, -1);
|
|
24
|
+
return position === "" || (/\S/.test(position) && !/^\d+$/.test(position));
|
|
25
|
+
});
|
|
26
|
+
if (invalidPlaceholder) {
|
|
27
|
+
throw new SyntaxError(`Invalid interpolation placeholder: ${invalidPlaceholder}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const placeholders = template.match(/%\d+%/g) || [];
|
|
31
|
+
if (placeholders.length !== values.length) {
|
|
32
|
+
throw new RangeError("The number of interpolation values must match the placeholders");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return template.replace(/%(\d+)%/g, (placeholder, position) => {
|
|
36
|
+
const index = Number(position) - 1;
|
|
37
|
+
if (index < 0 || index >= values.length) {
|
|
38
|
+
throw new RangeError(`Placeholder ${placeholder} has no matching interpolation value`);
|
|
39
|
+
}
|
|
40
|
+
return values[index];
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export {doI18n, doInterpolatedI18n};
|
package/src/i18n/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { doI18n } from "./i18nLib";
|
|
1
|
+
import { doI18n, doInterpolatedI18n } from "./i18nLib.js";
|
|
2
2
|
|
|
3
|
-
export { doI18n };
|
|
3
|
+
export { doI18n, doInterpolatedI18n };
|
package/src/usfm/index.js
CHANGED