seroval 0.6.0 → 0.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/src/tree/async.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  import assert from '../assert';
4
4
  import { Feature } from '../compat';
5
5
  import { createIndexedValue, getRootID, ParserContext } from '../context';
6
- import quote from '../quote';
6
+ import { serializeString } from '../string';
7
7
  import {
8
8
  BigIntTypedArrayValue,
9
9
  TypedArrayValue,
@@ -27,6 +27,8 @@ import {
27
27
  TRUE_NODE,
28
28
  UNDEFINED_NODE,
29
29
  createReferenceNode,
30
+ createArrayBufferNode,
31
+ createDataViewNode,
30
32
  } from './primitives';
31
33
  import { hasReferenceID } from './reference';
32
34
  import {
@@ -40,6 +42,8 @@ import {
40
42
  SerovalAggregateErrorNode,
41
43
  SerovalArrayNode,
42
44
  SerovalErrorNode,
45
+ SerovalFormDataNode,
46
+ SerovalHeadersNode,
43
47
  SerovalIterableNode,
44
48
  SerovalMapNode,
45
49
  SerovalNode,
@@ -50,7 +54,12 @@ import {
50
54
  SerovalPromiseNode,
51
55
  SerovalSetNode,
52
56
  } from './types';
53
- import { createURLNode, createURLSearchParamsNode } from './web-api';
57
+ import {
58
+ createBlobNode,
59
+ createFileNode,
60
+ createURLNode,
61
+ createURLSearchParamsNode,
62
+ } from './web-api';
54
63
 
55
64
  type ObjectLikeNode =
56
65
  | SerovalObjectNode
@@ -104,6 +113,7 @@ async function generateArrayNode(
104
113
  d: undefined,
105
114
  a: await generateNodeList(ctx, current),
106
115
  f: undefined,
116
+ b: undefined,
107
117
  };
108
118
  }
109
119
 
@@ -146,6 +156,7 @@ async function generateMapNode(
146
156
  d: { k: keyNodes, v: valueNodes, s: len },
147
157
  a: undefined,
148
158
  f: undefined,
159
+ b: undefined,
149
160
  };
150
161
  }
151
162
 
@@ -182,6 +193,7 @@ async function generateSetNode(
182
193
  d: undefined,
183
194
  a: nodes,
184
195
  f: undefined,
196
+ b: undefined,
185
197
  };
186
198
  }
187
199
 
@@ -198,14 +210,16 @@ async function generateProperties(
198
210
  let deferredSize = 0;
199
211
  let nodesSize = 0;
200
212
  let item: unknown;
213
+ let escaped: string;
201
214
  for (const key of keys) {
202
215
  item = properties[key];
216
+ escaped = serializeString(key);
203
217
  if (isIterable(item)) {
204
- deferredKeys[deferredSize] = key;
218
+ deferredKeys[deferredSize] = escaped;
205
219
  deferredValues[deferredSize] = item;
206
220
  deferredSize++;
207
221
  } else {
208
- keyNodes[nodesSize] = key;
222
+ keyNodes[nodesSize] = escaped;
209
223
  valueNodes[nodesSize] = await parse(ctx, item);
210
224
  nodesSize++;
211
225
  }
@@ -242,6 +256,7 @@ async function generateIterableNode(
242
256
  : undefined,
243
257
  a: await generateNodeList(ctx, array),
244
258
  f: undefined,
259
+ b: undefined,
245
260
  };
246
261
  }
247
262
 
@@ -262,6 +277,7 @@ async function generatePromiseNode(
262
277
  d: undefined,
263
278
  a: undefined,
264
279
  f: await parse(ctx, value),
280
+ b: undefined,
265
281
  }));
266
282
  }
267
283
 
@@ -287,6 +303,7 @@ async function generateObjectNode(
287
303
  d: await generateProperties(ctx, current as Record<string, unknown>),
288
304
  a: undefined,
289
305
  f: undefined,
306
+ b: undefined,
290
307
  };
291
308
  }
292
309
 
@@ -303,12 +320,13 @@ async function generateAggregateErrorNode(
303
320
  t: SerovalNodeType.AggregateError,
304
321
  i: id,
305
322
  s: undefined,
306
- l: current.errors.length,
323
+ l: undefined,
307
324
  c: undefined,
308
- m: quote(current.message),
325
+ m: serializeString(current.message),
309
326
  d: optionsNode,
310
- a: await generateNodeList(ctx, current.errors as unknown[]),
327
+ a: undefined,
311
328
  f: undefined,
329
+ b: undefined,
312
330
  };
313
331
  }
314
332
 
@@ -327,10 +345,59 @@ async function generateErrorNode(
327
345
  s: undefined,
328
346
  l: undefined,
329
347
  c: getErrorConstructorName(current),
330
- m: quote(current.message),
348
+ m: serializeString(current.message),
331
349
  d: optionsNode,
332
350
  a: undefined,
333
351
  f: undefined,
352
+ b: undefined,
353
+ };
354
+ }
355
+
356
+ async function generateHeadersNode(
357
+ ctx: ParserContext,
358
+ id: number,
359
+ current: Headers,
360
+ ): Promise<SerovalHeadersNode> {
361
+ assert(ctx.features & Feature.WebAPI, 'Unsupported type "Headers"');
362
+ const items: Record<string, string> = {};
363
+ current.forEach((value, key) => {
364
+ items[key] = value;
365
+ });
366
+ return {
367
+ t: SerovalNodeType.Headers,
368
+ i: id,
369
+ s: undefined,
370
+ l: undefined,
371
+ c: undefined,
372
+ m: undefined,
373
+ d: await generateProperties(ctx, items),
374
+ a: undefined,
375
+ f: undefined,
376
+ b: undefined,
377
+ };
378
+ }
379
+
380
+ async function generateFormDataNode(
381
+ ctx: ParserContext,
382
+ id: number,
383
+ current: FormData,
384
+ ): Promise<SerovalFormDataNode> {
385
+ assert(ctx.features & Feature.WebAPI, 'Unsupported type "FormData"');
386
+ const items: Record<string, FormDataEntryValue> = {};
387
+ current.forEach((value, key) => {
388
+ items[key] = value;
389
+ });
390
+ return {
391
+ t: SerovalNodeType.FormData,
392
+ i: id,
393
+ s: undefined,
394
+ l: undefined,
395
+ c: undefined,
396
+ m: undefined,
397
+ d: await generateProperties(ctx, items),
398
+ a: undefined,
399
+ f: undefined,
400
+ b: undefined,
334
401
  };
335
402
  }
336
403
 
@@ -384,6 +451,8 @@ async function parse<T>(
384
451
  return createRegExpNode(id, current as unknown as RegExp);
385
452
  case Promise:
386
453
  return generatePromiseNode(ctx, id, current as unknown as Promise<unknown>);
454
+ case ArrayBuffer:
455
+ return createArrayBufferNode(id, current as unknown as ArrayBuffer);
387
456
  case Int8Array:
388
457
  case Int16Array:
389
458
  case Int32Array:
@@ -397,6 +466,8 @@ async function parse<T>(
397
466
  case BigInt64Array:
398
467
  case BigUint64Array:
399
468
  return createBigIntTypedArrayNode(ctx, id, current as unknown as BigIntTypedArrayValue);
469
+ case DataView:
470
+ return createDataViewNode(ctx, id, current as unknown as DataView);
400
471
  case Map:
401
472
  return generateMapNode(
402
473
  ctx,
@@ -440,6 +511,14 @@ async function parse<T>(
440
511
  return createURLNode(ctx, id, current as unknown as URL);
441
512
  case URLSearchParams:
442
513
  return createURLSearchParamsNode(ctx, id, current as unknown as URLSearchParams);
514
+ case Blob:
515
+ return createBlobNode(ctx, id, current as unknown as Blob);
516
+ case File:
517
+ return createFileNode(ctx, id, current as unknown as File);
518
+ case Headers:
519
+ return generateHeadersNode(ctx, id, current as unknown as Headers);
520
+ case FormData:
521
+ return generateFormDataNode(ctx, id, current as unknown as FormData);
443
522
  default:
444
523
  break;
445
524
  }
@@ -1,17 +1,24 @@
1
+ /* eslint-disable prefer-spread */
1
2
  /* eslint-disable @typescript-eslint/no-use-before-define */
2
3
  import {
3
4
  SerializationContext,
4
5
  } from '../context';
5
- import { invQuote } from '../quote';
6
- import { AsyncServerValue } from '../types';
6
+ import { deserializeString } from '../string';
7
7
  import { getReference } from './reference';
8
8
  import { getErrorConstructor, getTypedArrayConstructor } from './shared';
9
9
  import { SYMBOL_REF } from './symbols';
10
10
  import {
11
11
  SerovalAggregateErrorNode,
12
+ SerovalArrayBufferNode,
12
13
  SerovalArrayNode,
13
14
  SerovalBigIntTypedArrayNode,
15
+ SerovalBlobNode,
16
+ SerovalDataViewNode,
17
+ SerovalDateNode,
14
18
  SerovalErrorNode,
19
+ SerovalFileNode,
20
+ SerovalFormDataNode,
21
+ SerovalHeadersNode,
15
22
  SerovalIterableNode,
16
23
  SerovalMapNode,
17
24
  SerovalNode,
@@ -20,8 +27,12 @@ import {
20
27
  SerovalObjectNode,
21
28
  SerovalObjectRecordNode,
22
29
  SerovalPromiseNode,
30
+ SerovalReferenceNode,
31
+ SerovalRegExpNode,
23
32
  SerovalSetNode,
24
33
  SerovalTypedArrayNode,
34
+ SerovalURLNode,
35
+ SerovalURLSearchParamsNode,
25
36
  } from './types';
26
37
 
27
38
  function assignIndexedValue<T>(
@@ -29,17 +40,23 @@ function assignIndexedValue<T>(
29
40
  index: number,
30
41
  value: T,
31
42
  ) {
32
- ctx.valueMap.set(index, value);
43
+ if (ctx.markedRefs.has(index)) {
44
+ ctx.valueMap.set(index, value);
45
+ }
33
46
  return value;
34
47
  }
35
48
 
36
- function deserializeNodeList(
49
+ function deserializeArray(
37
50
  ctx: SerializationContext,
38
- node: SerovalArrayNode | SerovalIterableNode | SerovalAggregateErrorNode,
39
- result: unknown[],
51
+ node: SerovalArrayNode,
40
52
  ) {
53
+ const result: unknown[] = assignIndexedValue(
54
+ ctx,
55
+ node.i,
56
+ new Array<unknown>(node.l),
57
+ );
41
58
  let item: SerovalNode;
42
- for (let i = 0, len = node.a.length; i < len; i++) {
59
+ for (let i = 0, len = node.l; i < len; i++) {
43
60
  item = node.a[i];
44
61
  if (item) {
45
62
  result[i] = deserializeTree(ctx, item);
@@ -48,21 +65,6 @@ function deserializeNodeList(
48
65
  return result;
49
66
  }
50
67
 
51
- function deserializeArray(
52
- ctx: SerializationContext,
53
- node: SerovalArrayNode,
54
- ) {
55
- const result: AsyncServerValue[] = assignIndexedValue(
56
- ctx,
57
- node.i,
58
- new Array<AsyncServerValue>(node.l),
59
- );
60
- ctx.stack.push(node.i);
61
- deserializeNodeList(ctx, node, result);
62
- ctx.stack.pop();
63
- return result;
64
- }
65
-
66
68
  function deserializeProperties(
67
69
  ctx: SerializationContext,
68
70
  node: SerovalObjectRecordNode,
@@ -72,7 +74,7 @@ function deserializeProperties(
72
74
  return {};
73
75
  }
74
76
  for (let i = 0; i < node.s; i++) {
75
- result[node.k[i]] = deserializeTree(ctx, node.v[i]);
77
+ result[deserializeString(node.k[i])] = deserializeTree(ctx, node.v[i]);
76
78
  }
77
79
  return result;
78
80
  }
@@ -84,11 +86,9 @@ function deserializeNullConstructor(
84
86
  const result = assignIndexedValue(
85
87
  ctx,
86
88
  node.i,
87
- Object.create(null) as Record<string, AsyncServerValue>,
89
+ Object.create(null) as Record<string, unknown>,
88
90
  );
89
- ctx.stack.push(node.i);
90
91
  deserializeProperties(ctx, node.d, result);
91
- ctx.stack.pop();
92
92
  return result;
93
93
  }
94
94
 
@@ -96,10 +96,8 @@ function deserializeObject(
96
96
  ctx: SerializationContext,
97
97
  node: SerovalObjectNode,
98
98
  ) {
99
- const result = assignIndexedValue(ctx, node.i, {} as Record<string, AsyncServerValue>);
100
- ctx.stack.push(node.i);
99
+ const result = assignIndexedValue(ctx, node.i, {} as Record<string, unknown>);
101
100
  deserializeProperties(ctx, node.d, result);
102
- ctx.stack.pop();
103
101
  return result;
104
102
  }
105
103
 
@@ -108,11 +106,9 @@ function deserializeSet(
108
106
  node: SerovalSetNode,
109
107
  ) {
110
108
  const result = assignIndexedValue(ctx, node.i, new Set<unknown>());
111
- ctx.stack.push(node.i);
112
- for (let i = 0, len = node.a.length; i < len; i++) {
109
+ for (let i = 0, len = node.l; i < len; i++) {
113
110
  result.add(deserializeTree(ctx, node.a[i]));
114
111
  }
115
- ctx.stack.pop();
116
112
  return result;
117
113
  }
118
114
 
@@ -125,18 +121,16 @@ function deserializeMap(
125
121
  node.i,
126
122
  new Map<unknown, unknown>(),
127
123
  );
128
- ctx.stack.push(node.i);
129
- for (let i = 0; i < node.d.s; i++) {
124
+ for (let i = 0, len = node.d.s; i < len; i++) {
130
125
  result.set(
131
126
  deserializeTree(ctx, node.d.k[i]),
132
127
  deserializeTree(ctx, node.d.v[i]),
133
128
  );
134
129
  }
135
- ctx.stack.pop();
136
130
  return result;
137
131
  }
138
132
 
139
- type AssignableValue = AggregateError | Error | Iterable<AsyncServerValue>
133
+ type AssignableValue = AggregateError | Error | Iterable<unknown>
140
134
  type AssignableNode = SerovalAggregateErrorNode | SerovalErrorNode | SerovalIterableNode;
141
135
 
142
136
  function deserializeDictionary<T extends AssignableValue>(
@@ -145,9 +139,7 @@ function deserializeDictionary<T extends AssignableValue>(
145
139
  result: T,
146
140
  ) {
147
141
  if (node.d) {
148
- ctx.stack.push(node.i);
149
142
  const fields = deserializeProperties(ctx, node.d, {});
150
- ctx.stack.pop();
151
143
  Object.assign(result, fields);
152
144
  }
153
145
  return result;
@@ -158,10 +150,11 @@ function deserializeAggregateError(
158
150
  node: SerovalAggregateErrorNode,
159
151
  ) {
160
152
  // Serialize the required arguments
161
- const result = assignIndexedValue(ctx, node.i, new AggregateError([], invQuote(node.m)));
162
- ctx.stack.push(node.i);
163
- result.errors = deserializeNodeList(ctx, node, new Array<AsyncServerValue>(node.l));
164
- ctx.stack.pop();
153
+ const result = assignIndexedValue(
154
+ ctx,
155
+ node.i,
156
+ new AggregateError([], deserializeString(node.m)),
157
+ );
165
158
  // `AggregateError` might've been extended
166
159
  // either through class or custom properties
167
160
  // Make sure to assign extra properties
@@ -173,7 +166,7 @@ function deserializeError(
173
166
  node: SerovalErrorNode,
174
167
  ) {
175
168
  const ErrorConstructor = getErrorConstructor(node.c);
176
- const result = assignIndexedValue(ctx, node.i, new ErrorConstructor(invQuote(node.m)));
169
+ const result = assignIndexedValue(ctx, node.i, new ErrorConstructor(deserializeString(node.m)));
177
170
  return deserializeDictionary(ctx, node, result);
178
171
  }
179
172
 
@@ -204,21 +197,26 @@ function deserializePromise(
204
197
  return result;
205
198
  }
206
199
 
200
+ function deserializeArrayBuffer(
201
+ ctx: SerializationContext,
202
+ node: SerovalArrayBufferNode,
203
+ ) {
204
+ const bytes = new Uint8Array(node.s);
205
+ const result = assignIndexedValue(ctx, node.i, bytes.buffer);
206
+ return result;
207
+ }
208
+
207
209
  function deserializeTypedArray(
208
210
  ctx: SerializationContext,
209
211
  node: SerovalTypedArrayNode | SerovalBigIntTypedArrayNode,
210
212
  ) {
211
213
  const TypedArray = getTypedArrayConstructor(node.c);
212
- const dummy = new TypedArray();
214
+ const source = deserializeTree(ctx, node.f) as ArrayBuffer;
213
215
  const result = assignIndexedValue(ctx, node.i, new TypedArray(
214
- dummy.buffer,
216
+ source,
217
+ node.b,
215
218
  node.l,
216
219
  ));
217
- for (let i = 0, len = node.s.length; i < len; i++) {
218
- result[i] = node.t === SerovalNodeType.BigIntTypedArray
219
- ? BigInt(node.s[i])
220
- : Number(node.s[i]);
221
- }
222
220
  return result;
223
221
  }
224
222
 
@@ -226,14 +224,121 @@ function deserializeIterable(
226
224
  ctx: SerializationContext,
227
225
  node: SerovalIterableNode,
228
226
  ) {
229
- const values: AsyncServerValue[] = [];
230
- deserializeNodeList(ctx, node, values);
231
- const result = assignIndexedValue(ctx, node.i, {
227
+ const values: unknown[] = [];
228
+ let item: SerovalNode;
229
+ for (let i = 0, len = node.l; i < len; i++) {
230
+ item = node.a[i];
231
+ if (item) {
232
+ values[i] = deserializeTree(ctx, item);
233
+ }
234
+ }
235
+ const result: Iterable<unknown> = assignIndexedValue(ctx, node.i, {
232
236
  [Symbol.iterator]: () => values.values(),
233
237
  });
234
238
  return deserializeDictionary(ctx, node, result);
235
239
  }
236
240
 
241
+ function deserializeDate(
242
+ ctx: SerializationContext,
243
+ node: SerovalDateNode,
244
+ ) {
245
+ return assignIndexedValue(ctx, node.i, new Date(node.s));
246
+ }
247
+
248
+ function deserializeRegExp(
249
+ ctx: SerializationContext,
250
+ node: SerovalRegExpNode,
251
+ ) {
252
+ return assignIndexedValue(ctx, node.i, new RegExp(node.c, node.m));
253
+ }
254
+
255
+ function deserializeURL(
256
+ ctx: SerializationContext,
257
+ node: SerovalURLNode,
258
+ ) {
259
+ return assignIndexedValue(ctx, node.i, new URL(deserializeString(node.s)));
260
+ }
261
+
262
+ function deserializeURLSearchParams(
263
+ ctx: SerializationContext,
264
+ node: SerovalURLSearchParamsNode,
265
+ ) {
266
+ return assignIndexedValue(ctx, node.i, new URLSearchParams(deserializeString(node.s)));
267
+ }
268
+
269
+ function deserializeReference(
270
+ ctx: SerializationContext,
271
+ node: SerovalReferenceNode,
272
+ ) {
273
+ return assignIndexedValue(ctx, node.i, getReference(deserializeString(node.s)));
274
+ }
275
+
276
+ function deserializeDataView(
277
+ ctx: SerializationContext,
278
+ node: SerovalDataViewNode,
279
+ ) {
280
+ const source = deserializeTree(ctx, node.f) as ArrayBuffer;
281
+ const result = assignIndexedValue(ctx, node.i, new DataView(
282
+ source,
283
+ node.b,
284
+ node.l,
285
+ ));
286
+ return result;
287
+ }
288
+
289
+ function deserializeBlob(
290
+ ctx: SerializationContext,
291
+ node: SerovalBlobNode,
292
+ ) {
293
+ const source = deserializeTree(ctx, node.f) as ArrayBuffer;
294
+ const result = assignIndexedValue(ctx, node.i, new Blob(
295
+ [source],
296
+ { type: deserializeString(node.c) },
297
+ ));
298
+ return result;
299
+ }
300
+
301
+ function deserializeFile(
302
+ ctx: SerializationContext,
303
+ node: SerovalFileNode,
304
+ ) {
305
+ const source = deserializeTree(ctx, node.f) as ArrayBuffer;
306
+ const result = assignIndexedValue(ctx, node.i, new File(
307
+ [source],
308
+ deserializeString(node.m),
309
+ { type: deserializeString(node.c), lastModified: node.b },
310
+ ));
311
+ return result;
312
+ }
313
+
314
+ function deserializeHeaders(
315
+ ctx: SerializationContext,
316
+ node: SerovalHeadersNode,
317
+ ) {
318
+ const result = assignIndexedValue(ctx, node.i, new Headers());
319
+ for (let i = 0, len = node.d.s; i < len; i++) {
320
+ result.set(
321
+ deserializeString(node.d.k[i]),
322
+ deserializeTree(ctx, node.d.v[i]) as string,
323
+ );
324
+ }
325
+ return result;
326
+ }
327
+
328
+ function deserializeFormData(
329
+ ctx: SerializationContext,
330
+ node: SerovalFormDataNode,
331
+ ) {
332
+ const result = assignIndexedValue(ctx, node.i, new FormData());
333
+ for (let i = 0, len = node.d.s; i < len; i++) {
334
+ result.set(
335
+ deserializeString(node.d.k[i]),
336
+ deserializeTree(ctx, node.d.v[i]) as FormDataEntryValue,
337
+ );
338
+ }
339
+ return result;
340
+ }
341
+
237
342
  export default function deserializeTree(
238
343
  ctx: SerializationContext,
239
344
  node: SerovalNode,
@@ -243,7 +348,7 @@ export default function deserializeTree(
243
348
  case SerovalNodeType.Boolean:
244
349
  return node.s;
245
350
  case SerovalNodeType.String:
246
- return invQuote(node.s);
351
+ return deserializeString(node.s);
247
352
  case SerovalNodeType.Undefined:
248
353
  return undefined;
249
354
  case SerovalNodeType.Null:
@@ -267,16 +372,20 @@ export default function deserializeTree(
267
372
  case SerovalNodeType.NullConstructor:
268
373
  return deserializeNullConstructor(ctx, node);
269
374
  case SerovalNodeType.Date:
270
- return assignIndexedValue(ctx, node.i, new Date(node.s));
375
+ return deserializeDate(ctx, node);
271
376
  case SerovalNodeType.RegExp:
272
- return assignIndexedValue(ctx, node.i, new RegExp(node.c, node.m));
377
+ return deserializeRegExp(ctx, node);
273
378
  case SerovalNodeType.Set:
274
379
  return deserializeSet(ctx, node);
275
380
  case SerovalNodeType.Map:
276
381
  return deserializeMap(ctx, node);
382
+ case SerovalNodeType.ArrayBuffer:
383
+ return deserializeArrayBuffer(ctx, node);
277
384
  case SerovalNodeType.BigIntTypedArray:
278
385
  case SerovalNodeType.TypedArray:
279
386
  return deserializeTypedArray(ctx, node);
387
+ case SerovalNodeType.DataView:
388
+ return deserializeDataView(ctx, node);
280
389
  case SerovalNodeType.AggregateError:
281
390
  return deserializeAggregateError(ctx, node);
282
391
  case SerovalNodeType.Error:
@@ -288,11 +397,19 @@ export default function deserializeTree(
288
397
  case SerovalNodeType.WKSymbol:
289
398
  return SYMBOL_REF[node.s];
290
399
  case SerovalNodeType.URL:
291
- return assignIndexedValue(ctx, node.i, new URL(node.s));
400
+ return deserializeURL(ctx, node);
292
401
  case SerovalNodeType.URLSearchParams:
293
- return assignIndexedValue(ctx, node.i, new URLSearchParams(node.s));
402
+ return deserializeURLSearchParams(ctx, node);
294
403
  case SerovalNodeType.Reference:
295
- return assignIndexedValue(ctx, node.i, getReference(invQuote(node.s)));
404
+ return deserializeReference(ctx, node);
405
+ case SerovalNodeType.Blob:
406
+ return deserializeBlob(ctx, node);
407
+ case SerovalNodeType.File:
408
+ return deserializeFile(ctx, node);
409
+ case SerovalNodeType.Headers:
410
+ return deserializeHeaders(ctx, node);
411
+ case SerovalNodeType.FormData:
412
+ return deserializeFormData(ctx, node);
296
413
  default:
297
414
  throw new Error('Unsupported type');
298
415
  }