@chubbyts/chubbyts-decode-encode 2.5.2 → 2.7.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/README.md +184 -47
- package/dist/decoder/json-type-decoder.d.ts +1 -1
- package/dist/decoder/json-type-decoder.js +2 -2
- package/dist/decoder/ndjson-type-decoder.d.ts +2 -0
- package/dist/decoder/ndjson-type-decoder.js +31 -0
- package/dist/decoder/yaml-type-decoder.d.ts +1 -1
- package/dist/decoder/yaml-type-decoder.js +2 -2
- package/dist/encoder/json-type-encoder.d.ts +1 -1
- package/dist/encoder/json-type-encoder.js +2 -2
- package/dist/encoder/ndjson-type-encoder.d.ts +2 -0
- package/dist/encoder/ndjson-type-encoder.js +13 -0
- package/dist/encoder/yaml-type-encoder.d.ts +1 -1
- package/dist/encoder/yaml-type-encoder.js +2 -2
- package/dist/service-factory.js +20 -1
- package/package.json +13 -5
package/README.md
CHANGED
|
@@ -19,7 +19,9 @@
|
|
|
19
19
|
|
|
20
20
|
## Description
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
Decode and encode request/response bodies by content type: json, jsonx, ndjson, url-encoded and yaml.
|
|
23
|
+
|
|
24
|
+
Strings are decoded into plain data (objects, arrays, strings, numbers, booleans and null) and encoded back. Each format can be registered under additional content types, for example `application/problem+json` or `application/yaml`.
|
|
23
25
|
|
|
24
26
|
## Requirements
|
|
25
27
|
|
|
@@ -36,43 +38,65 @@ A simple decode/encode solution for json / jsonx / url-encoded / xml / yaml.
|
|
|
36
38
|
|
|
37
39
|
Through [NPM](https://www.npmjs.com) as [@chubbyts/chubbyts-decode-encode][1].
|
|
38
40
|
|
|
39
|
-
```
|
|
40
|
-
npm i @chubbyts/chubbyts-decode-encode@^2.
|
|
41
|
+
```sh
|
|
42
|
+
npm i @chubbyts/chubbyts-decode-encode@^2.7.0
|
|
41
43
|
```
|
|
42
44
|
|
|
43
45
|
## Usage
|
|
44
46
|
|
|
47
|
+
### Data
|
|
48
|
+
|
|
49
|
+
Every type decoder returns and every type encoder accepts `Data`, the json data model:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
type Data = { [key: string]: Data } | Array<Data> | string | number | boolean | null;
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The matching type guards `isNull`, `isBoolean`, `isNumber`, `isString`, `isArray` and `isObject` (plain objects only) can be imported from `@chubbyts/chubbyts-decode-encode/data`.
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import { isObject } from '@chubbyts/chubbyts-decode-encode/data';
|
|
59
|
+
|
|
60
|
+
isObject({ key: 'value' }); // true
|
|
61
|
+
isObject(new Date()); // false
|
|
62
|
+
```
|
|
63
|
+
|
|
45
64
|
### Decoder
|
|
46
65
|
|
|
47
66
|
#### createDecoder
|
|
48
67
|
|
|
49
68
|
```ts
|
|
50
|
-
import { createDecoder } from '@chubbyts/chubbyts-decode-encode/
|
|
51
|
-
import { createJsonTypeDecoder } from '@chubbyts/chubbyts-decode-encode/
|
|
69
|
+
import { createDecoder } from '@chubbyts/chubbyts-decode-encode/decoder/decoder';
|
|
70
|
+
import { createJsonTypeDecoder } from '@chubbyts/chubbyts-decode-encode/decoder/json-type-decoder';
|
|
52
71
|
|
|
53
72
|
const decoder = createDecoder([createJsonTypeDecoder()]);
|
|
54
|
-
const data = decoder.decode('{"key":"value"}', 'application/json');
|
|
55
|
-
// data: {key:
|
|
73
|
+
const data = decoder.decode('{"key":"value"}', 'application/json');
|
|
74
|
+
// data: { key: 'value' }
|
|
56
75
|
const contentTypes = decoder.contentTypes;
|
|
57
76
|
// contentTypes: ['application/json']
|
|
58
77
|
```
|
|
59
78
|
|
|
79
|
+
The `Decoder` type accepts an optional third argument `context` (`Record<string, unknown>`). The shipped decoder ignores it, it exists for custom `Decoder` implementations, for example one that decodes depending on the current user.
|
|
80
|
+
|
|
60
81
|
#### createJsonTypeDecoder
|
|
61
82
|
|
|
62
83
|
```ts
|
|
63
|
-
import { createJsonTypeDecoder } from '@chubbyts/chubbyts-decode-encode/
|
|
84
|
+
import { createJsonTypeDecoder } from '@chubbyts/chubbyts-decode-encode/decoder/json-type-decoder';
|
|
64
85
|
|
|
65
86
|
const jsonTypeDecoder = createJsonTypeDecoder();
|
|
66
87
|
const data = jsonTypeDecoder.decode('{"key":"value"}');
|
|
67
|
-
// data: {key:
|
|
88
|
+
// data: { key: 'value' }
|
|
68
89
|
const contentType = jsonTypeDecoder.contentType;
|
|
69
90
|
// contentType: 'application/json'
|
|
91
|
+
|
|
92
|
+
// any other json based content type, like `application/problem+json` (RFC 9457)
|
|
93
|
+
const problemJsonTypeDecoder = createJsonTypeDecoder('application/problem+json');
|
|
70
94
|
```
|
|
71
95
|
|
|
72
96
|
#### createJsonxTypeDecoder
|
|
73
97
|
|
|
74
98
|
```ts
|
|
75
|
-
import { createJsonxTypeDecoder } from '@chubbyts/chubbyts-decode-encode/
|
|
99
|
+
import { createJsonxTypeDecoder } from '@chubbyts/chubbyts-decode-encode/decoder/jsonx-type-decoder';
|
|
76
100
|
|
|
77
101
|
const jsonxTypeDecoder = createJsonxTypeDecoder();
|
|
78
102
|
const data = jsonxTypeDecoder.decode(`
|
|
@@ -81,33 +105,67 @@ const data = jsonxTypeDecoder.decode(`
|
|
|
81
105
|
<json:string name="key">value</json:string>
|
|
82
106
|
</json:object>
|
|
83
107
|
`);
|
|
84
|
-
// data: {key:
|
|
108
|
+
// data: { key: 'value' }
|
|
85
109
|
const contentType = jsonxTypeDecoder.contentType;
|
|
86
110
|
// contentType: 'application/jsonx+xml'
|
|
87
111
|
```
|
|
88
112
|
|
|
113
|
+
#### createNdjsonTypeDecoder
|
|
114
|
+
|
|
115
|
+
[NDJSON](https://github.com/ndjson/ndjson-spec) / [JSON Lines](https://jsonlines.org): one json document per line, decoded into an array.
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import { createNdjsonTypeDecoder } from '@chubbyts/chubbyts-decode-encode/decoder/ndjson-type-decoder';
|
|
119
|
+
|
|
120
|
+
const ndjsonTypeDecoder = createNdjsonTypeDecoder();
|
|
121
|
+
const data = ndjsonTypeDecoder.decode('{"key":"value1"}\n{"key":"value2"}\n');
|
|
122
|
+
// data: [{ key: 'value1' }, { key: 'value2' }]
|
|
123
|
+
const contentType = ndjsonTypeDecoder.contentType;
|
|
124
|
+
// contentType: 'application/jsonl'
|
|
125
|
+
|
|
126
|
+
// any other ndjson / json lines content type, like `application/x-ndjson`
|
|
127
|
+
const xNdjsonTypeDecoder = createNdjsonTypeDecoder('application/x-ndjson');
|
|
128
|
+
|
|
129
|
+
// silently ignore empty lines
|
|
130
|
+
const lenientNdjsonTypeDecoder = createNdjsonTypeDecoder('application/jsonl', true);
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`\n` and `\r\n` line endings are accepted, the line terminator after the last line is optional. Empty lines (including lines with only json whitespace: space, tab, `\r`) are not valid json and throw a `DecodeError`, unless `ignoreEmptyLines` (second argument) is `true`. The `DecodeError` message starts with the line number, for example `Line 3: ...`.
|
|
134
|
+
|
|
135
|
+
There is no registered media type for ndjson / json lines, so the service factories register all common ones: `application/jsonl`, `application/x-jsonl`, `text/jsonl`, `application/ndjson` and `application/x-ndjson`.
|
|
136
|
+
|
|
89
137
|
#### createUrlEncodedTypeDecoder
|
|
90
138
|
|
|
91
139
|
```ts
|
|
92
|
-
import { createUrlEncodedTypeDecoder } from '@chubbyts/chubbyts-decode-encode/
|
|
140
|
+
import { createUrlEncodedTypeDecoder } from '@chubbyts/chubbyts-decode-encode/decoder/url-encoded-type-decoder';
|
|
93
141
|
|
|
94
142
|
const urlEncodedTypeDecoder = createUrlEncodedTypeDecoder();
|
|
95
143
|
const data = urlEncodedTypeDecoder.decode('key=value');
|
|
96
|
-
// data: {key:
|
|
144
|
+
// data: { key: 'value' }
|
|
97
145
|
const contentType = urlEncodedTypeDecoder.contentType;
|
|
98
146
|
// contentType: 'application/x-www-form-urlencoded'
|
|
99
147
|
```
|
|
100
148
|
|
|
149
|
+
Values are converted to their json type: `true` and `false` become booleans, `null` becomes `null` and a value becomes a number only if the number converts back to the exact same string, so `01` or `1.0` stay strings.
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
const data = urlEncodedTypeDecoder.decode('a=1&b=true&c=null&d=01&e[]=x');
|
|
153
|
+
// data: { a: 1, b: true, c: null, d: '01', e: ['x'] }
|
|
154
|
+
```
|
|
155
|
+
|
|
101
156
|
#### createYamlTypeDecoder
|
|
102
157
|
|
|
103
158
|
```ts
|
|
104
|
-
import { createYamlTypeDecoder } from '@chubbyts/chubbyts-decode-encode/
|
|
159
|
+
import { createYamlTypeDecoder } from '@chubbyts/chubbyts-decode-encode/decoder/yaml-type-decoder';
|
|
105
160
|
|
|
106
161
|
const yamlTypeDecoder = createYamlTypeDecoder();
|
|
107
162
|
const data = yamlTypeDecoder.decode('key: value');
|
|
108
|
-
// data: {key:
|
|
163
|
+
// data: { key: 'value' }
|
|
109
164
|
const contentType = yamlTypeDecoder.contentType;
|
|
110
165
|
// contentType: 'application/x-yaml'
|
|
166
|
+
|
|
167
|
+
// the registered media type `application/yaml` (RFC 9512)
|
|
168
|
+
const officialYamlTypeDecoder = createYamlTypeDecoder('application/yaml');
|
|
111
169
|
```
|
|
112
170
|
|
|
113
171
|
### Encoder
|
|
@@ -115,82 +173,161 @@ const contentType = yamlTypeDecoder.contentType;
|
|
|
115
173
|
#### createEncoder
|
|
116
174
|
|
|
117
175
|
```ts
|
|
118
|
-
import { createEncoder } from '@chubbyts/chubbyts-
|
|
119
|
-
import { createJsonTypeEncoder } from '@chubbyts/chubbyts-
|
|
176
|
+
import { createEncoder } from '@chubbyts/chubbyts-decode-encode/encoder/encoder';
|
|
177
|
+
import { createJsonTypeEncoder } from '@chubbyts/chubbyts-decode-encode/encoder/json-type-encoder';
|
|
120
178
|
|
|
121
179
|
const encoder = createEncoder([createJsonTypeEncoder()]);
|
|
122
|
-
const encodedData = encoder.encode({key:
|
|
123
|
-
// encodedData: {"key":"value"}
|
|
180
|
+
const encodedData = encoder.encode({ key: 'value' }, 'application/json');
|
|
181
|
+
// encodedData: '{"key":"value"}'
|
|
124
182
|
const contentTypes = encoder.contentTypes;
|
|
125
183
|
// contentTypes: ['application/json']
|
|
126
184
|
```
|
|
127
185
|
|
|
186
|
+
The `Encoder` type accepts an optional third argument `context` (`Record<string, unknown>`). The shipped encoder ignores it, it exists for custom `Encoder` implementations, for example one that encodes depending on the current user.
|
|
187
|
+
|
|
128
188
|
#### createJsonTypeEncoder
|
|
129
189
|
|
|
130
190
|
```ts
|
|
131
|
-
import { createJsonTypeEncoder } from '@chubbyts/chubbyts-
|
|
191
|
+
import { createJsonTypeEncoder } from '@chubbyts/chubbyts-decode-encode/encoder/json-type-encoder';
|
|
132
192
|
|
|
133
193
|
const jsonTypeEncoder = createJsonTypeEncoder();
|
|
134
|
-
const encodedData = jsonTypeEncoder.encode({key:
|
|
135
|
-
// encodedData: {"key":"value"}
|
|
194
|
+
const encodedData = jsonTypeEncoder.encode({ key: 'value' });
|
|
195
|
+
// encodedData: '{"key":"value"}'
|
|
136
196
|
const contentType = jsonTypeEncoder.contentType;
|
|
137
|
-
//
|
|
197
|
+
// contentType: 'application/json'
|
|
198
|
+
|
|
199
|
+
// pretty printed
|
|
200
|
+
const prettyJsonTypeEncoder = createJsonTypeEncoder(true);
|
|
201
|
+
|
|
202
|
+
// any other json based content type, like `application/problem+json` (RFC 9457)
|
|
203
|
+
const problemJsonTypeEncoder = createJsonTypeEncoder(false, 'application/problem+json');
|
|
138
204
|
```
|
|
139
205
|
|
|
140
206
|
#### createJsonxTypeEncoder
|
|
141
207
|
|
|
142
208
|
```ts
|
|
143
|
-
import { createJsonxTypeEncoder } from '@chubbyts/chubbyts-
|
|
209
|
+
import { createJsonxTypeEncoder } from '@chubbyts/chubbyts-decode-encode/encoder/jsonx-type-encoder';
|
|
144
210
|
|
|
145
|
-
const jsonxTypeEncoder = createJsonxTypeEncoder();
|
|
146
|
-
const encodedData = jsonxTypeEncoder.encode({key:
|
|
147
|
-
// encodedData:
|
|
148
|
-
//
|
|
211
|
+
const jsonxTypeEncoder = createJsonxTypeEncoder(true); // pretty printed, defaults to false
|
|
212
|
+
const encodedData = jsonxTypeEncoder.encode({ key: 'value' });
|
|
213
|
+
// encodedData:
|
|
214
|
+
// <?xml version="1.0" encoding="UTF-8"?>
|
|
215
|
+
// <json:object xsi:schemaLocation="http://www.datapower.com/schemas/json jsonx.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:json="http://www.ibm.com/xmlns/prod/2009/jsonx">
|
|
149
216
|
// <json:string name="key">value</json:string>
|
|
150
217
|
// </json:object>
|
|
151
218
|
const contentType = jsonxTypeEncoder.contentType;
|
|
152
|
-
//
|
|
219
|
+
// contentType: 'application/jsonx+xml'
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
#### createNdjsonTypeEncoder
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
import { createNdjsonTypeEncoder } from '@chubbyts/chubbyts-decode-encode/encoder/ndjson-type-encoder';
|
|
226
|
+
|
|
227
|
+
const ndjsonTypeEncoder = createNdjsonTypeEncoder();
|
|
228
|
+
const encodedData = ndjsonTypeEncoder.encode([{ key: 'value1' }, { key: 'value2' }]);
|
|
229
|
+
// encodedData: '{"key":"value1"}\n{"key":"value2"}\n'
|
|
230
|
+
const contentType = ndjsonTypeEncoder.contentType;
|
|
231
|
+
// contentType: 'application/jsonl'
|
|
232
|
+
|
|
233
|
+
// any other ndjson / json lines content type, like `application/x-ndjson`
|
|
234
|
+
const xNdjsonTypeEncoder = createNdjsonTypeEncoder('application/x-ndjson');
|
|
153
235
|
```
|
|
154
236
|
|
|
237
|
+
Every line, including the last one, is terminated by `\n`. Only arrays can be encoded, any other value throws an `EncodeError`.
|
|
238
|
+
|
|
155
239
|
#### createUrlEncodedTypeEncoder
|
|
156
240
|
|
|
157
241
|
```ts
|
|
158
|
-
import { createUrlEncodedTypeEncoder } from '@chubbyts/chubbyts-
|
|
242
|
+
import { createUrlEncodedTypeEncoder } from '@chubbyts/chubbyts-decode-encode/encoder/url-encoded-type-encoder';
|
|
159
243
|
|
|
160
244
|
const urlEncodedTypeEncoder = createUrlEncodedTypeEncoder();
|
|
161
|
-
const encodedData = urlEncodedTypeEncoder.encode({
|
|
162
|
-
// encodedData:
|
|
245
|
+
const encodedData = urlEncodedTypeEncoder.encode({ a: 1, b: true, c: null, d: [1, 2] });
|
|
246
|
+
// encodedData: 'a=1&b=true&c=null&d[0]=1&d[1]=2'
|
|
163
247
|
const contentType = urlEncodedTypeEncoder.contentType;
|
|
164
|
-
//
|
|
248
|
+
// contentType: 'application/x-www-form-urlencoded'
|
|
165
249
|
```
|
|
166
250
|
|
|
167
251
|
#### createYamlTypeEncoder
|
|
168
252
|
|
|
169
253
|
```ts
|
|
170
|
-
import { createYamlTypeEncoder } from '@chubbyts/chubbyts-
|
|
254
|
+
import { createYamlTypeEncoder } from '@chubbyts/chubbyts-decode-encode/encoder/yaml-type-encoder';
|
|
171
255
|
|
|
172
256
|
const yamlTypeEncoder = createYamlTypeEncoder();
|
|
173
|
-
const encodedData = yamlTypeEncoder.encode({key:
|
|
174
|
-
// encodedData: key: value
|
|
257
|
+
const encodedData = yamlTypeEncoder.encode({ key: 'value' });
|
|
258
|
+
// encodedData: 'key: value\n'
|
|
175
259
|
const contentType = yamlTypeEncoder.contentType;
|
|
176
|
-
//
|
|
260
|
+
// contentType: 'application/x-yaml'
|
|
261
|
+
|
|
262
|
+
// the registered media type `application/yaml` (RFC 9512)
|
|
263
|
+
const officialYamlTypeEncoder = createYamlTypeEncoder('application/yaml');
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Custom type decoders / encoders
|
|
267
|
+
|
|
268
|
+
A type decoder / encoder is a plain object with a `contentType` and a `decode` / `encode` function. This adds `text/plain`, where the body is a single string:
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
import type { Data } from '@chubbyts/chubbyts-decode-encode/data';
|
|
272
|
+
import { isString } from '@chubbyts/chubbyts-decode-encode/data';
|
|
273
|
+
import type { TypeDecoder } from '@chubbyts/chubbyts-decode-encode/decoder/decoder';
|
|
274
|
+
import { createDecoder } from '@chubbyts/chubbyts-decode-encode/decoder/decoder';
|
|
275
|
+
import type { TypeEncoder } from '@chubbyts/chubbyts-decode-encode/encoder/encoder';
|
|
276
|
+
import { EncodeError, createEncoder } from '@chubbyts/chubbyts-decode-encode/encoder/encoder';
|
|
277
|
+
|
|
278
|
+
const createTextTypeDecoder = (): TypeDecoder => ({
|
|
279
|
+
decode: (encodedData: string): Data => encodedData,
|
|
280
|
+
contentType: 'text/plain',
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const createTextTypeEncoder = (): TypeEncoder => ({
|
|
284
|
+
encode: (data: Data): string => {
|
|
285
|
+
if (!isString(data)) {
|
|
286
|
+
throw new EncodeError(`Unsupported value: ${JSON.stringify(data)}, expected a string`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return data;
|
|
290
|
+
},
|
|
291
|
+
contentType: 'text/plain',
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
const decoder = createDecoder([createTextTypeDecoder()]);
|
|
295
|
+
const encoder = createEncoder([createTextTypeEncoder()]);
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Errors
|
|
299
|
+
|
|
300
|
+
* `decode` throws a `DecodeError` (`@chubbyts/chubbyts-decode-encode/decoder/decoder`) if the encoded data is invalid, for example a json syntax error. In a http context this usually maps to `400 Bad Request`.
|
|
301
|
+
* `encode` of the jsonx and ndjson type encoders throws an `EncodeError` (`@chubbyts/chubbyts-decode-encode/encoder/encoder`) if a value cannot be represented, for ndjson anything but an array.
|
|
302
|
+
* `decode` / `encode` of the decoder / encoder throw an `Error` if the content type is not supported, the message lists the supported content types. In a http context this usually maps to `415 Unsupported Media Type` (decoder) or `406 Not Acceptable` (encoder).
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
import { DecodeError } from '@chubbyts/chubbyts-decode-encode/decoder/decoder';
|
|
306
|
+
|
|
307
|
+
try {
|
|
308
|
+
decoder.decode('{', 'application/json');
|
|
309
|
+
} catch (e) {
|
|
310
|
+
if (e instanceof DecodeError) {
|
|
311
|
+
// invalid request body
|
|
312
|
+
}
|
|
313
|
+
}
|
|
177
314
|
```
|
|
178
315
|
|
|
179
316
|
### Service factories (chubbyts-dic-config)
|
|
180
317
|
|
|
181
|
-
The package ships service factories (abstract factories built on [chubbyts-dic-config-factory][9]) for a [chubbyts-dic-config][8] (or any [chubbyts-dic-types][7] compatible) container within `@chubbyts/chubbyts-decode-encode/
|
|
318
|
+
The package ships service factories (abstract factories built on [chubbyts-dic-config-factory][9]) for a [chubbyts-dic-config][8] (or any [chubbyts-dic-types][7] compatible) container within `@chubbyts/chubbyts-decode-encode/service-factory`:
|
|
182
319
|
|
|
183
|
-
* `typeDecodersServiceFactory`: all shipped type decoders (json, jsonx, urlencoded, yaml)
|
|
184
|
-
* `typeEncodersServiceFactory`: all shipped type encoders (json, jsonx, urlencoded, yaml), `config.debug` enables pretty printing for json / jsonx
|
|
320
|
+
* `typeDecodersServiceFactory`: all shipped type decoders (json, jsonx, ndjson as `application/jsonl`, `application/x-jsonl`, `text/jsonl`, `application/ndjson` and `application/x-ndjson`, urlencoded, yaml as `application/x-yaml` and `application/yaml`)
|
|
321
|
+
* `typeEncodersServiceFactory`: all shipped type encoders (json, jsonx, ndjson as `application/jsonl`, `application/x-jsonl`, `text/jsonl`, `application/ndjson` and `application/x-ndjson`, urlencoded, yaml as `application/x-yaml` and `application/yaml`), `config.debug` enables pretty printing for json / jsonx
|
|
185
322
|
* `decoderServiceFactory`: the decoder, built from the `typeDecoders` service if registered, the shipped type decoders otherwise
|
|
186
323
|
* `encoderServiceFactory`: the encoder, built from the `typeEncoders` service if registered, the shipped type encoders otherwise
|
|
187
324
|
|
|
188
325
|
```ts
|
|
189
326
|
import type { ConfigFactory } from '@chubbyts/chubbyts-dic-config/dist/dic-config';
|
|
190
327
|
import { createContainerByConfigFactory } from '@chubbyts/chubbyts-dic-config/dist/dic-config';
|
|
191
|
-
import type { Decoder } from '@chubbyts/chubbyts-decode-encode/
|
|
192
|
-
import type { Encoder } from '@chubbyts/chubbyts-decode-encode/
|
|
193
|
-
import { decoderServiceFactory, encoderServiceFactory } from '@chubbyts/chubbyts-decode-encode/
|
|
328
|
+
import type { Decoder } from '@chubbyts/chubbyts-decode-encode/decoder/decoder';
|
|
329
|
+
import type { Encoder } from '@chubbyts/chubbyts-decode-encode/encoder/encoder';
|
|
330
|
+
import { decoderServiceFactory, encoderServiceFactory } from '@chubbyts/chubbyts-decode-encode/service-factory';
|
|
194
331
|
|
|
195
332
|
const container = createContainerByConfigFactory({
|
|
196
333
|
debug: false,
|
|
@@ -206,17 +343,17 @@ const decoder = container.get<Decoder>('decoder');
|
|
|
206
343
|
const encoder = container.get<Encoder>('encoder');
|
|
207
344
|
```
|
|
208
345
|
|
|
209
|
-
To replace the type decoders / encoders, register a `typeDecoders` / `typeEncoders` service:
|
|
346
|
+
To replace the type decoders / encoders, register a `typeDecoders` / `typeEncoders` service (`createTextTypeDecoder` from [Custom type decoders / encoders](#custom-type-decoders--encoders)):
|
|
210
347
|
|
|
211
348
|
```ts
|
|
212
|
-
import type { TypeDecoder } from '@chubbyts/chubbyts-decode-encode/
|
|
213
|
-
import { createJsonTypeDecoder } from '@chubbyts/chubbyts-decode-encode/
|
|
349
|
+
import type { TypeDecoder } from '@chubbyts/chubbyts-decode-encode/decoder/decoder';
|
|
350
|
+
import { createJsonTypeDecoder } from '@chubbyts/chubbyts-decode-encode/decoder/json-type-decoder';
|
|
214
351
|
|
|
215
352
|
const container = createContainerByConfigFactory({
|
|
216
353
|
dependencies: {
|
|
217
354
|
factories: new Map<string, ConfigFactory>([
|
|
218
355
|
['decoder', decoderServiceFactory()],
|
|
219
|
-
['typeDecoders', (): Array<TypeDecoder> => [createJsonTypeDecoder(),
|
|
356
|
+
['typeDecoders', (): Array<TypeDecoder> => [createJsonTypeDecoder(), createTextTypeDecoder()]],
|
|
220
357
|
]),
|
|
221
358
|
},
|
|
222
359
|
})();
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { TypeDecoder } from './decoder.js';
|
|
2
|
-
export declare const createJsonTypeDecoder: () => TypeDecoder;
|
|
2
|
+
export declare const createJsonTypeDecoder: (contentType?: string) => TypeDecoder;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { throwableToError } from '@chubbyts/chubbyts-throwable-to-error/dist/throwable-to-error';
|
|
2
2
|
import { DecodeError } from './decoder.js';
|
|
3
|
-
export const createJsonTypeDecoder = () => {
|
|
3
|
+
export const createJsonTypeDecoder = (contentType = 'application/json') => {
|
|
4
4
|
return {
|
|
5
5
|
decode: (encodedData) => {
|
|
6
6
|
try {
|
|
@@ -11,6 +11,6 @@ export const createJsonTypeDecoder = () => {
|
|
|
11
11
|
throw new DecodeError(error.message, error.stack);
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
|
-
contentType
|
|
14
|
+
contentType,
|
|
15
15
|
};
|
|
16
16
|
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { throwableToError } from '@chubbyts/chubbyts-throwable-to-error/dist/throwable-to-error';
|
|
2
|
+
import { DecodeError } from './decoder.js';
|
|
3
|
+
// only json whitespace (RFC 8259), "\n" is the line separator, String.prototype.trim() would also strip a BOM, nbsp, ...
|
|
4
|
+
const EMPTY_LINE = /^[ \t\r]*$/;
|
|
5
|
+
const decodeLine = (line, index) => {
|
|
6
|
+
try {
|
|
7
|
+
return JSON.parse(line);
|
|
8
|
+
}
|
|
9
|
+
catch (e) {
|
|
10
|
+
const error = throwableToError(e);
|
|
11
|
+
throw new DecodeError(`Line ${index + 1}: ${error.message}`, error.stack);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
export const createNdjsonTypeDecoder = (contentType = 'application/jsonl', ignoreEmptyLines = false) => {
|
|
15
|
+
return {
|
|
16
|
+
decode: (encodedData) => {
|
|
17
|
+
const lines = encodedData.split('\n');
|
|
18
|
+
// the line terminator after the last line does not start another line
|
|
19
|
+
return (lines[lines.length - 1] === '' ? lines.slice(0, -1) : lines).flatMap((line, index) => {
|
|
20
|
+
if (EMPTY_LINE.test(line)) {
|
|
21
|
+
if (ignoreEmptyLines) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
throw new DecodeError(`Line ${index + 1}: empty line`);
|
|
25
|
+
}
|
|
26
|
+
return [decodeLine(line, index)];
|
|
27
|
+
});
|
|
28
|
+
},
|
|
29
|
+
contentType,
|
|
30
|
+
};
|
|
31
|
+
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { TypeDecoder } from './decoder.js';
|
|
2
|
-
export declare const createYamlTypeDecoder: () => TypeDecoder;
|
|
2
|
+
export declare const createYamlTypeDecoder: (contentType?: string) => TypeDecoder;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parse } from 'yaml';
|
|
2
2
|
import { throwableToError } from '@chubbyts/chubbyts-throwable-to-error/dist/throwable-to-error';
|
|
3
3
|
import { DecodeError } from './decoder.js';
|
|
4
|
-
export const createYamlTypeDecoder = () => {
|
|
4
|
+
export const createYamlTypeDecoder = (contentType = 'application/x-yaml') => {
|
|
5
5
|
return {
|
|
6
6
|
decode: (encodedData) => {
|
|
7
7
|
try {
|
|
@@ -12,6 +12,6 @@ export const createYamlTypeDecoder = () => {
|
|
|
12
12
|
throw new DecodeError(error.message, error.stack);
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
|
-
contentType
|
|
15
|
+
contentType,
|
|
16
16
|
};
|
|
17
17
|
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { TypeEncoder } from './encoder.js';
|
|
2
|
-
export declare const createJsonTypeEncoder: (prettyPrint?: boolean) => TypeEncoder;
|
|
2
|
+
export declare const createJsonTypeEncoder: (prettyPrint?: boolean, contentType?: string) => TypeEncoder;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export const createJsonTypeEncoder = (prettyPrint = false) => {
|
|
1
|
+
export const createJsonTypeEncoder = (prettyPrint = false, contentType = 'application/json') => {
|
|
2
2
|
return {
|
|
3
3
|
encode: (data) => JSON.stringify(data, undefined, prettyPrint ? 4 : undefined),
|
|
4
|
-
contentType
|
|
4
|
+
contentType,
|
|
5
5
|
};
|
|
6
6
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { isArray } from '../data.js';
|
|
2
|
+
import { EncodeError } from './encoder.js';
|
|
3
|
+
export const createNdjsonTypeEncoder = (contentType = 'application/jsonl') => {
|
|
4
|
+
return {
|
|
5
|
+
encode: (data) => {
|
|
6
|
+
if (!isArray(data)) {
|
|
7
|
+
throw new EncodeError(`Unsupported value: ${JSON.stringify(data)}, expected an array`);
|
|
8
|
+
}
|
|
9
|
+
return data.map((item) => `${JSON.stringify(item)}\n`).join('');
|
|
10
|
+
},
|
|
11
|
+
contentType,
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { TypeEncoder } from './encoder.js';
|
|
2
|
-
export declare const createYamlTypeEncoder: () => TypeEncoder;
|
|
2
|
+
export declare const createYamlTypeEncoder: (contentType?: string) => TypeEncoder;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { stringify } from 'yaml';
|
|
2
|
-
export const createYamlTypeEncoder = () => {
|
|
2
|
+
export const createYamlTypeEncoder = (contentType = 'application/x-yaml') => {
|
|
3
3
|
return {
|
|
4
4
|
encode: (data) => stringify(data, { indent: 4 }),
|
|
5
|
-
contentType
|
|
5
|
+
contentType,
|
|
6
6
|
};
|
|
7
7
|
};
|
package/dist/service-factory.js
CHANGED
|
@@ -2,15 +2,28 @@ import { createAbstractFactory } from '@chubbyts/chubbyts-dic-config-factory/dis
|
|
|
2
2
|
import { createDecoder } from './decoder/decoder.js';
|
|
3
3
|
import { createJsonTypeDecoder } from './decoder/json-type-decoder.js';
|
|
4
4
|
import { createJsonxTypeDecoder } from './decoder/jsonx-type-decoder.js';
|
|
5
|
+
import { createNdjsonTypeDecoder } from './decoder/ndjson-type-decoder.js';
|
|
5
6
|
import { createUrlEncodedTypeDecoder } from './decoder/url-encoded-type-decoder.js';
|
|
6
7
|
import { createYamlTypeDecoder } from './decoder/yaml-type-decoder.js';
|
|
7
8
|
import { createEncoder } from './encoder/encoder.js';
|
|
8
9
|
import { createJsonTypeEncoder } from './encoder/json-type-encoder.js';
|
|
9
10
|
import { createJsonxTypeEncoder } from './encoder/jsonx-type-encoder.js';
|
|
11
|
+
import { createNdjsonTypeEncoder } from './encoder/ndjson-type-encoder.js';
|
|
10
12
|
import { createUrlEncodedTypeEncoder } from './encoder/url-encoded-type-encoder.js';
|
|
11
13
|
import { createYamlTypeEncoder } from './encoder/yaml-type-encoder.js';
|
|
12
14
|
export const typeDecodersServiceFactory = createAbstractFactory(() => {
|
|
13
|
-
return [
|
|
15
|
+
return [
|
|
16
|
+
createJsonTypeDecoder(),
|
|
17
|
+
createJsonxTypeDecoder(),
|
|
18
|
+
createNdjsonTypeDecoder(),
|
|
19
|
+
createNdjsonTypeDecoder('application/x-jsonl'),
|
|
20
|
+
createNdjsonTypeDecoder('text/jsonl'),
|
|
21
|
+
createNdjsonTypeDecoder('application/ndjson'),
|
|
22
|
+
createNdjsonTypeDecoder('application/x-ndjson'),
|
|
23
|
+
createUrlEncodedTypeDecoder(),
|
|
24
|
+
createYamlTypeDecoder(),
|
|
25
|
+
createYamlTypeDecoder('application/yaml'),
|
|
26
|
+
];
|
|
14
27
|
});
|
|
15
28
|
export const decoderServiceFactory = createAbstractFactory((container, { resolveDependency }) => {
|
|
16
29
|
return createDecoder(resolveDependency(container, 'typeDecoders', typeDecodersServiceFactory));
|
|
@@ -20,8 +33,14 @@ export const typeEncodersServiceFactory = createAbstractFactory((container) => {
|
|
|
20
33
|
return [
|
|
21
34
|
createJsonTypeEncoder(debug),
|
|
22
35
|
createJsonxTypeEncoder(debug),
|
|
36
|
+
createNdjsonTypeEncoder(),
|
|
37
|
+
createNdjsonTypeEncoder('application/x-jsonl'),
|
|
38
|
+
createNdjsonTypeEncoder('text/jsonl'),
|
|
39
|
+
createNdjsonTypeEncoder('application/ndjson'),
|
|
40
|
+
createNdjsonTypeEncoder('application/x-ndjson'),
|
|
23
41
|
createUrlEncodedTypeEncoder(),
|
|
24
42
|
createYamlTypeEncoder(),
|
|
43
|
+
createYamlTypeEncoder('application/yaml'),
|
|
25
44
|
];
|
|
26
45
|
});
|
|
27
46
|
export const encoderServiceFactory = createAbstractFactory((container, { resolveDependency }) => {
|
package/package.json
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chubbyts/chubbyts-decode-encode",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.
|
|
5
|
-
"description": "
|
|
4
|
+
"version": "2.7.0",
|
|
5
|
+
"description": "Decode and encode request/response bodies by content type: json, jsonx, ndjson, url-encoded and yaml.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"chubbyts",
|
|
8
|
+
"content-type",
|
|
8
9
|
"decode",
|
|
9
10
|
"encode",
|
|
10
11
|
"json",
|
|
11
12
|
"jsonx",
|
|
13
|
+
"ndjson",
|
|
14
|
+
"serializer",
|
|
12
15
|
"url-encoded",
|
|
13
16
|
"xml",
|
|
14
17
|
"yaml"
|
|
@@ -44,10 +47,15 @@
|
|
|
44
47
|
"import": "./dist/encoder/index.js",
|
|
45
48
|
"default": "./dist/encoder/index.js"
|
|
46
49
|
},
|
|
50
|
+
"./dist/*": {
|
|
51
|
+
"types": "./dist/*.d.ts",
|
|
52
|
+
"import": "./dist/*.js",
|
|
53
|
+
"default": "./dist/*.js"
|
|
54
|
+
},
|
|
47
55
|
"./*": {
|
|
48
|
-
"types": "
|
|
49
|
-
"import": "
|
|
50
|
-
"default": "
|
|
56
|
+
"types": "./dist/*.d.ts",
|
|
57
|
+
"import": "./dist/*.js",
|
|
58
|
+
"default": "./dist/*.js"
|
|
51
59
|
}
|
|
52
60
|
},
|
|
53
61
|
"engines": {
|