seroval 0.5.1 → 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/README.md +51 -1
- package/dist/cjs/development/index.cjs +762 -228
- package/dist/cjs/development/index.cjs.map +4 -4
- package/dist/cjs/production/index.cjs +3 -3
- package/dist/esm/development/index.mjs +762 -228
- package/dist/esm/development/index.mjs.map +4 -4
- package/dist/esm/production/index.mjs +3 -3
- package/dist/types/compat.d.ts +3 -2
- package/dist/types/context.d.ts +14 -9
- package/dist/types/index.d.ts +9 -9
- package/dist/types/string.d.ts +3 -0
- package/dist/types/tree/async.d.ts +1 -2
- package/dist/types/tree/deserialize.d.ts +1 -2
- package/dist/types/tree/primitives.d.ts +7 -3
- package/dist/types/tree/reference.d.ts +6 -0
- package/dist/types/tree/sync.d.ts +1 -2
- package/dist/types/tree/types.d.ts +73 -12
- package/dist/types/tree/web-api.d.ts +6 -0
- package/dist/types/types.d.ts +2 -1
- package/package.json +4 -3
- package/src/compat.ts +2 -1
- package/src/context.ts +19 -12
- package/src/index.ts +10 -16
- package/src/{quote.ts → string.ts} +27 -30
- package/src/tree/async.ts +164 -62
- package/src/tree/deserialize.ts +195 -67
- package/src/tree/primitives.ts +108 -22
- package/src/tree/reference.ts +61 -0
- package/src/tree/serialize.ts +236 -59
- package/src/tree/sync.ts +160 -55
- package/src/tree/types.ts +139 -14
- package/src/tree/web-api.ts +92 -0
- package/src/types.ts +6 -1
- package/dist/types/quote.d.ts +0 -2
package/src/tree/deserialize.ts
CHANGED
|
@@ -1,16 +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 {
|
|
6
|
-
import {
|
|
6
|
+
import { deserializeString } from '../string';
|
|
7
|
+
import { getReference } from './reference';
|
|
7
8
|
import { getErrorConstructor, getTypedArrayConstructor } from './shared';
|
|
8
9
|
import { SYMBOL_REF } from './symbols';
|
|
9
10
|
import {
|
|
10
11
|
SerovalAggregateErrorNode,
|
|
12
|
+
SerovalArrayBufferNode,
|
|
11
13
|
SerovalArrayNode,
|
|
12
14
|
SerovalBigIntTypedArrayNode,
|
|
15
|
+
SerovalBlobNode,
|
|
16
|
+
SerovalDataViewNode,
|
|
17
|
+
SerovalDateNode,
|
|
13
18
|
SerovalErrorNode,
|
|
19
|
+
SerovalFileNode,
|
|
20
|
+
SerovalFormDataNode,
|
|
21
|
+
SerovalHeadersNode,
|
|
14
22
|
SerovalIterableNode,
|
|
15
23
|
SerovalMapNode,
|
|
16
24
|
SerovalNode,
|
|
@@ -19,26 +27,36 @@ import {
|
|
|
19
27
|
SerovalObjectNode,
|
|
20
28
|
SerovalObjectRecordNode,
|
|
21
29
|
SerovalPromiseNode,
|
|
30
|
+
SerovalReferenceNode,
|
|
31
|
+
SerovalRegExpNode,
|
|
22
32
|
SerovalSetNode,
|
|
23
33
|
SerovalTypedArrayNode,
|
|
34
|
+
SerovalURLNode,
|
|
35
|
+
SerovalURLSearchParamsNode,
|
|
24
36
|
} from './types';
|
|
25
37
|
|
|
26
|
-
function
|
|
38
|
+
function assignIndexedValue<T>(
|
|
27
39
|
ctx: SerializationContext,
|
|
28
40
|
index: number,
|
|
29
41
|
value: T,
|
|
30
42
|
) {
|
|
31
|
-
ctx.
|
|
43
|
+
if (ctx.markedRefs.has(index)) {
|
|
44
|
+
ctx.valueMap.set(index, value);
|
|
45
|
+
}
|
|
32
46
|
return value;
|
|
33
47
|
}
|
|
34
48
|
|
|
35
|
-
function
|
|
49
|
+
function deserializeArray(
|
|
36
50
|
ctx: SerializationContext,
|
|
37
|
-
node: SerovalArrayNode
|
|
38
|
-
result: AsyncServerValue[],
|
|
51
|
+
node: SerovalArrayNode,
|
|
39
52
|
) {
|
|
53
|
+
const result: unknown[] = assignIndexedValue(
|
|
54
|
+
ctx,
|
|
55
|
+
node.i,
|
|
56
|
+
new Array<unknown>(node.l),
|
|
57
|
+
);
|
|
40
58
|
let item: SerovalNode;
|
|
41
|
-
for (let i = 0, len = node.
|
|
59
|
+
for (let i = 0, len = node.l; i < len; i++) {
|
|
42
60
|
item = node.a[i];
|
|
43
61
|
if (item) {
|
|
44
62
|
result[i] = deserializeTree(ctx, item);
|
|
@@ -47,31 +65,16 @@ function deserializeNodeList(
|
|
|
47
65
|
return result;
|
|
48
66
|
}
|
|
49
67
|
|
|
50
|
-
function deserializeArray(
|
|
51
|
-
ctx: SerializationContext,
|
|
52
|
-
node: SerovalArrayNode,
|
|
53
|
-
) {
|
|
54
|
-
const result: AsyncServerValue[] = assignRef(
|
|
55
|
-
ctx,
|
|
56
|
-
node.i,
|
|
57
|
-
new Array<AsyncServerValue>(node.l),
|
|
58
|
-
);
|
|
59
|
-
ctx.stack.push(node.i);
|
|
60
|
-
deserializeNodeList(ctx, node, result);
|
|
61
|
-
ctx.stack.pop();
|
|
62
|
-
return result;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
68
|
function deserializeProperties(
|
|
66
69
|
ctx: SerializationContext,
|
|
67
70
|
node: SerovalObjectRecordNode,
|
|
68
|
-
result: Record<string,
|
|
71
|
+
result: Record<string, unknown>,
|
|
69
72
|
) {
|
|
70
73
|
if (node.s === 0) {
|
|
71
74
|
return {};
|
|
72
75
|
}
|
|
73
76
|
for (let i = 0; i < node.s; i++) {
|
|
74
|
-
result[node.k[i]] = deserializeTree(ctx, node.v[i]);
|
|
77
|
+
result[deserializeString(node.k[i])] = deserializeTree(ctx, node.v[i]);
|
|
75
78
|
}
|
|
76
79
|
return result;
|
|
77
80
|
}
|
|
@@ -80,10 +83,12 @@ function deserializeNullConstructor(
|
|
|
80
83
|
ctx: SerializationContext,
|
|
81
84
|
node: SerovalNullConstructorNode,
|
|
82
85
|
) {
|
|
83
|
-
const result =
|
|
84
|
-
|
|
86
|
+
const result = assignIndexedValue(
|
|
87
|
+
ctx,
|
|
88
|
+
node.i,
|
|
89
|
+
Object.create(null) as Record<string, unknown>,
|
|
90
|
+
);
|
|
85
91
|
deserializeProperties(ctx, node.d, result);
|
|
86
|
-
ctx.stack.pop();
|
|
87
92
|
return result;
|
|
88
93
|
}
|
|
89
94
|
|
|
@@ -91,10 +96,8 @@ function deserializeObject(
|
|
|
91
96
|
ctx: SerializationContext,
|
|
92
97
|
node: SerovalObjectNode,
|
|
93
98
|
) {
|
|
94
|
-
const result =
|
|
95
|
-
ctx.stack.push(node.i);
|
|
99
|
+
const result = assignIndexedValue(ctx, node.i, {} as Record<string, unknown>);
|
|
96
100
|
deserializeProperties(ctx, node.d, result);
|
|
97
|
-
ctx.stack.pop();
|
|
98
101
|
return result;
|
|
99
102
|
}
|
|
100
103
|
|
|
@@ -102,12 +105,10 @@ function deserializeSet(
|
|
|
102
105
|
ctx: SerializationContext,
|
|
103
106
|
node: SerovalSetNode,
|
|
104
107
|
) {
|
|
105
|
-
const result =
|
|
106
|
-
|
|
107
|
-
for (let i = 0, len = node.a.length; i < len; i++) {
|
|
108
|
+
const result = assignIndexedValue(ctx, node.i, new Set<unknown>());
|
|
109
|
+
for (let i = 0, len = node.l; i < len; i++) {
|
|
108
110
|
result.add(deserializeTree(ctx, node.a[i]));
|
|
109
111
|
}
|
|
110
|
-
ctx.stack.pop();
|
|
111
112
|
return result;
|
|
112
113
|
}
|
|
113
114
|
|
|
@@ -115,23 +116,21 @@ function deserializeMap(
|
|
|
115
116
|
ctx: SerializationContext,
|
|
116
117
|
node: SerovalMapNode,
|
|
117
118
|
) {
|
|
118
|
-
const result =
|
|
119
|
+
const result = assignIndexedValue(
|
|
119
120
|
ctx,
|
|
120
121
|
node.i,
|
|
121
|
-
new Map<
|
|
122
|
+
new Map<unknown, unknown>(),
|
|
122
123
|
);
|
|
123
|
-
|
|
124
|
-
for (let i = 0; i < node.d.s; i++) {
|
|
124
|
+
for (let i = 0, len = node.d.s; i < len; i++) {
|
|
125
125
|
result.set(
|
|
126
126
|
deserializeTree(ctx, node.d.k[i]),
|
|
127
127
|
deserializeTree(ctx, node.d.v[i]),
|
|
128
128
|
);
|
|
129
129
|
}
|
|
130
|
-
ctx.stack.pop();
|
|
131
130
|
return result;
|
|
132
131
|
}
|
|
133
132
|
|
|
134
|
-
type AssignableValue = AggregateError | Error | Iterable<
|
|
133
|
+
type AssignableValue = AggregateError | Error | Iterable<unknown>
|
|
135
134
|
type AssignableNode = SerovalAggregateErrorNode | SerovalErrorNode | SerovalIterableNode;
|
|
136
135
|
|
|
137
136
|
function deserializeDictionary<T extends AssignableValue>(
|
|
@@ -140,9 +139,7 @@ function deserializeDictionary<T extends AssignableValue>(
|
|
|
140
139
|
result: T,
|
|
141
140
|
) {
|
|
142
141
|
if (node.d) {
|
|
143
|
-
ctx.stack.push(node.i);
|
|
144
142
|
const fields = deserializeProperties(ctx, node.d, {});
|
|
145
|
-
ctx.stack.pop();
|
|
146
143
|
Object.assign(result, fields);
|
|
147
144
|
}
|
|
148
145
|
return result;
|
|
@@ -153,10 +150,11 @@ function deserializeAggregateError(
|
|
|
153
150
|
node: SerovalAggregateErrorNode,
|
|
154
151
|
) {
|
|
155
152
|
// Serialize the required arguments
|
|
156
|
-
const result =
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
153
|
+
const result = assignIndexedValue(
|
|
154
|
+
ctx,
|
|
155
|
+
node.i,
|
|
156
|
+
new AggregateError([], deserializeString(node.m)),
|
|
157
|
+
);
|
|
160
158
|
// `AggregateError` might've been extended
|
|
161
159
|
// either through class or custom properties
|
|
162
160
|
// Make sure to assign extra properties
|
|
@@ -168,13 +166,13 @@ function deserializeError(
|
|
|
168
166
|
node: SerovalErrorNode,
|
|
169
167
|
) {
|
|
170
168
|
const ErrorConstructor = getErrorConstructor(node.c);
|
|
171
|
-
const result =
|
|
169
|
+
const result = assignIndexedValue(ctx, node.i, new ErrorConstructor(deserializeString(node.m)));
|
|
172
170
|
return deserializeDictionary(ctx, node, result);
|
|
173
171
|
}
|
|
174
172
|
|
|
175
173
|
interface Deferred {
|
|
176
|
-
resolve(value:
|
|
177
|
-
promise: Promise<
|
|
174
|
+
resolve(value: unknown): void;
|
|
175
|
+
promise: Promise<unknown>;
|
|
178
176
|
}
|
|
179
177
|
|
|
180
178
|
function createDeferred(): Deferred {
|
|
@@ -194,26 +192,31 @@ function deserializePromise(
|
|
|
194
192
|
node: SerovalPromiseNode,
|
|
195
193
|
) {
|
|
196
194
|
const deferred = createDeferred();
|
|
197
|
-
const result =
|
|
195
|
+
const result = assignIndexedValue(ctx, node.i, deferred.promise);
|
|
198
196
|
deferred.resolve(deserializeTree(ctx, node.f));
|
|
199
197
|
return result;
|
|
200
198
|
}
|
|
201
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
|
+
|
|
202
209
|
function deserializeTypedArray(
|
|
203
210
|
ctx: SerializationContext,
|
|
204
211
|
node: SerovalTypedArrayNode | SerovalBigIntTypedArrayNode,
|
|
205
212
|
) {
|
|
206
213
|
const TypedArray = getTypedArrayConstructor(node.c);
|
|
207
|
-
const
|
|
208
|
-
const result =
|
|
209
|
-
|
|
214
|
+
const source = deserializeTree(ctx, node.f) as ArrayBuffer;
|
|
215
|
+
const result = assignIndexedValue(ctx, node.i, new TypedArray(
|
|
216
|
+
source,
|
|
217
|
+
node.b,
|
|
210
218
|
node.l,
|
|
211
219
|
));
|
|
212
|
-
for (let i = 0, len = node.s.length; i < len; i++) {
|
|
213
|
-
result[i] = node.t === SerovalNodeType.BigIntTypedArray
|
|
214
|
-
? BigInt(node.s[i])
|
|
215
|
-
: Number(node.s[i]);
|
|
216
|
-
}
|
|
217
220
|
return result;
|
|
218
221
|
}
|
|
219
222
|
|
|
@@ -221,24 +224,131 @@ function deserializeIterable(
|
|
|
221
224
|
ctx: SerializationContext,
|
|
222
225
|
node: SerovalIterableNode,
|
|
223
226
|
) {
|
|
224
|
-
const values:
|
|
225
|
-
|
|
226
|
-
|
|
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, {
|
|
227
236
|
[Symbol.iterator]: () => values.values(),
|
|
228
237
|
});
|
|
229
238
|
return deserializeDictionary(ctx, node, result);
|
|
230
239
|
}
|
|
231
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
|
+
|
|
232
342
|
export default function deserializeTree(
|
|
233
343
|
ctx: SerializationContext,
|
|
234
344
|
node: SerovalNode,
|
|
235
|
-
):
|
|
345
|
+
): unknown {
|
|
236
346
|
switch (node.t) {
|
|
237
347
|
case SerovalNodeType.Number:
|
|
238
348
|
case SerovalNodeType.Boolean:
|
|
239
349
|
return node.s;
|
|
240
350
|
case SerovalNodeType.String:
|
|
241
|
-
return
|
|
351
|
+
return deserializeString(node.s);
|
|
242
352
|
case SerovalNodeType.Undefined:
|
|
243
353
|
return undefined;
|
|
244
354
|
case SerovalNodeType.Null:
|
|
@@ -253,7 +363,7 @@ export default function deserializeTree(
|
|
|
253
363
|
return NaN;
|
|
254
364
|
case SerovalNodeType.BigInt:
|
|
255
365
|
return BigInt(node.s);
|
|
256
|
-
case SerovalNodeType.
|
|
366
|
+
case SerovalNodeType.IndexedValue:
|
|
257
367
|
return ctx.valueMap.get(node.i);
|
|
258
368
|
case SerovalNodeType.Array:
|
|
259
369
|
return deserializeArray(ctx, node);
|
|
@@ -262,16 +372,20 @@ export default function deserializeTree(
|
|
|
262
372
|
case SerovalNodeType.NullConstructor:
|
|
263
373
|
return deserializeNullConstructor(ctx, node);
|
|
264
374
|
case SerovalNodeType.Date:
|
|
265
|
-
return
|
|
375
|
+
return deserializeDate(ctx, node);
|
|
266
376
|
case SerovalNodeType.RegExp:
|
|
267
|
-
return
|
|
377
|
+
return deserializeRegExp(ctx, node);
|
|
268
378
|
case SerovalNodeType.Set:
|
|
269
379
|
return deserializeSet(ctx, node);
|
|
270
380
|
case SerovalNodeType.Map:
|
|
271
381
|
return deserializeMap(ctx, node);
|
|
382
|
+
case SerovalNodeType.ArrayBuffer:
|
|
383
|
+
return deserializeArrayBuffer(ctx, node);
|
|
272
384
|
case SerovalNodeType.BigIntTypedArray:
|
|
273
385
|
case SerovalNodeType.TypedArray:
|
|
274
386
|
return deserializeTypedArray(ctx, node);
|
|
387
|
+
case SerovalNodeType.DataView:
|
|
388
|
+
return deserializeDataView(ctx, node);
|
|
275
389
|
case SerovalNodeType.AggregateError:
|
|
276
390
|
return deserializeAggregateError(ctx, node);
|
|
277
391
|
case SerovalNodeType.Error:
|
|
@@ -282,6 +396,20 @@ export default function deserializeTree(
|
|
|
282
396
|
return deserializePromise(ctx, node);
|
|
283
397
|
case SerovalNodeType.WKSymbol:
|
|
284
398
|
return SYMBOL_REF[node.s];
|
|
399
|
+
case SerovalNodeType.URL:
|
|
400
|
+
return deserializeURL(ctx, node);
|
|
401
|
+
case SerovalNodeType.URLSearchParams:
|
|
402
|
+
return deserializeURLSearchParams(ctx, node);
|
|
403
|
+
case SerovalNodeType.Reference:
|
|
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);
|
|
285
413
|
default:
|
|
286
414
|
throw new Error('Unsupported type');
|
|
287
415
|
}
|