arri 0.19.4 → 0.20.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/dist/cli.cjs +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +5 -1
- package/dist/index.d.mts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.mjs +1 -1
- package/dist/shared/{arri.6b7e8945.cjs → arri.b90bc113.cjs} +59 -7
- package/dist/shared/{arri.ea72d98f.mjs → arri.f69ab093.mjs} +60 -8
- package/package.json +9 -9
package/dist/cli.cjs
CHANGED
|
@@ -12,7 +12,7 @@ const esbuild = require('esbuild');
|
|
|
12
12
|
const esbuildPluginReplace = require('esbuild-plugin-replace');
|
|
13
13
|
const path = require('pathe');
|
|
14
14
|
const prettier = require('prettier');
|
|
15
|
-
const config = require('./shared/arri.
|
|
15
|
+
const config = require('./shared/arri.b90bc113.cjs');
|
|
16
16
|
const globby = require('globby');
|
|
17
17
|
const scule = require('scule');
|
|
18
18
|
const arriCodegenDart = require('arri-codegen-dart');
|
package/dist/cli.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { build as build$1 } from 'esbuild';
|
|
|
11
11
|
import { replace } from 'esbuild-plugin-replace';
|
|
12
12
|
import path from 'pathe';
|
|
13
13
|
import prettier from 'prettier';
|
|
14
|
-
import { n as defaultConfig, m as isResolvedArriConfig, a as DEV_DEFINITION_ENDPOINT } from './shared/arri.
|
|
14
|
+
import { n as defaultConfig, m as isResolvedArriConfig, a as DEV_DEFINITION_ENDPOINT } from './shared/arri.f69ab093.mjs';
|
|
15
15
|
import { globby } from 'globby';
|
|
16
16
|
import { camelCase, kebabCase } from 'scule';
|
|
17
17
|
import { dartClientGenerator } from 'arri-codegen-dart';
|
package/dist/index.cjs
CHANGED
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HttpMethod, RpcHttpMethod, RpcDefinition, SchemaFormProperties, SchemaFormDiscriminator, SchemaFormValues, AppDefinition, ClientGenerator } from 'arri-codegen-utils';
|
|
2
|
-
import { AObjectSchema, ASchema, InferType, a, ValidationError, ARecordSchema, ADiscriminatorSchema } from 'arri-validate';
|
|
2
|
+
import { AObjectSchema, ASchema, InferType, a, ValidationError, ValueError, ARecordSchema, ADiscriminatorSchema } from 'arri-validate';
|
|
3
3
|
import { H3EventContext, H3Event, Router, H3Error, App } from 'h3';
|
|
4
4
|
export * from 'h3';
|
|
5
5
|
import { BuildOptions } from 'esbuild';
|
|
@@ -73,6 +73,8 @@ interface EventStreamRpcHandlerContext<TParams = any, TResponse = any> extends R
|
|
|
73
73
|
connection: EventStreamConnection<TResponse>;
|
|
74
74
|
}
|
|
75
75
|
interface EventStreamConnectionOptions<TData> {
|
|
76
|
+
validator: (input: unknown) => input is TData;
|
|
77
|
+
validationErrors: (input: unknown) => ValueError[];
|
|
76
78
|
serializer: (input: TData) => string;
|
|
77
79
|
pingInterval?: number;
|
|
78
80
|
}
|
|
@@ -93,6 +95,8 @@ declare class EventStreamConnection<TData> {
|
|
|
93
95
|
private readonly writer;
|
|
94
96
|
private writerIsClosed;
|
|
95
97
|
private readonly encoder;
|
|
98
|
+
private readonly validationErrors;
|
|
99
|
+
private readonly validator;
|
|
96
100
|
private readonly serializer;
|
|
97
101
|
private readonly h3Event;
|
|
98
102
|
private pingInterval;
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HttpMethod, RpcHttpMethod, RpcDefinition, SchemaFormProperties, SchemaFormDiscriminator, SchemaFormValues, AppDefinition, ClientGenerator } from 'arri-codegen-utils';
|
|
2
|
-
import { AObjectSchema, ASchema, InferType, a, ValidationError, ARecordSchema, ADiscriminatorSchema } from 'arri-validate';
|
|
2
|
+
import { AObjectSchema, ASchema, InferType, a, ValidationError, ValueError, ARecordSchema, ADiscriminatorSchema } from 'arri-validate';
|
|
3
3
|
import { H3EventContext, H3Event, Router, H3Error, App } from 'h3';
|
|
4
4
|
export * from 'h3';
|
|
5
5
|
import { BuildOptions } from 'esbuild';
|
|
@@ -73,6 +73,8 @@ interface EventStreamRpcHandlerContext<TParams = any, TResponse = any> extends R
|
|
|
73
73
|
connection: EventStreamConnection<TResponse>;
|
|
74
74
|
}
|
|
75
75
|
interface EventStreamConnectionOptions<TData> {
|
|
76
|
+
validator: (input: unknown) => input is TData;
|
|
77
|
+
validationErrors: (input: unknown) => ValueError[];
|
|
76
78
|
serializer: (input: TData) => string;
|
|
77
79
|
pingInterval?: number;
|
|
78
80
|
}
|
|
@@ -93,6 +95,8 @@ declare class EventStreamConnection<TData> {
|
|
|
93
95
|
private readonly writer;
|
|
94
96
|
private writerIsClosed;
|
|
95
97
|
private readonly encoder;
|
|
98
|
+
private readonly validationErrors;
|
|
99
|
+
private readonly validator;
|
|
96
100
|
private readonly serializer;
|
|
97
101
|
private readonly h3Event;
|
|
98
102
|
private pingInterval;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HttpMethod, RpcHttpMethod, RpcDefinition, SchemaFormProperties, SchemaFormDiscriminator, SchemaFormValues, AppDefinition, ClientGenerator } from 'arri-codegen-utils';
|
|
2
|
-
import { AObjectSchema, ASchema, InferType, a, ValidationError, ARecordSchema, ADiscriminatorSchema } from 'arri-validate';
|
|
2
|
+
import { AObjectSchema, ASchema, InferType, a, ValidationError, ValueError, ARecordSchema, ADiscriminatorSchema } from 'arri-validate';
|
|
3
3
|
import { H3EventContext, H3Event, Router, H3Error, App } from 'h3';
|
|
4
4
|
export * from 'h3';
|
|
5
5
|
import { BuildOptions } from 'esbuild';
|
|
@@ -73,6 +73,8 @@ interface EventStreamRpcHandlerContext<TParams = any, TResponse = any> extends R
|
|
|
73
73
|
connection: EventStreamConnection<TResponse>;
|
|
74
74
|
}
|
|
75
75
|
interface EventStreamConnectionOptions<TData> {
|
|
76
|
+
validator: (input: unknown) => input is TData;
|
|
77
|
+
validationErrors: (input: unknown) => ValueError[];
|
|
76
78
|
serializer: (input: TData) => string;
|
|
77
79
|
pingInterval?: number;
|
|
78
80
|
}
|
|
@@ -93,6 +95,8 @@ declare class EventStreamConnection<TData> {
|
|
|
93
95
|
private readonly writer;
|
|
94
96
|
private writerIsClosed;
|
|
95
97
|
private readonly encoder;
|
|
98
|
+
private readonly validationErrors;
|
|
99
|
+
private readonly validator;
|
|
96
100
|
private readonly serializer;
|
|
97
101
|
private readonly h3Event;
|
|
98
102
|
private pingInterval;
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { A as ArriApp, p as ArriRouter, a as DEV_DEFINITION_ENDPOINT, D as DEV_ENDPOINT_ROOT, E as ErrorResponse, B as EventStreamConnection, c as createRpcDefinition, n as defaultConfig, o as defineConfig, h as defineError, w as defineEventStreamRpc, q as defineRoute, d as defineRpc, j as errorResponseFromValidationErrors, y as formatSse, z as formatSseList, e as getRpcParamName, g as getRpcPath, f as getRpcResponseName, k as handleH3Error, t as handleRoute, l as isArriConfig, x as isEventStreamRpc, m as isResolvedArriConfig, b as isRpc, i as isRpcParamSchema, C as registerEventStreamRpc, s as registerRoute, r as registerRpc, u as setSseHeaders, v as validateRpcRequestInput } from './shared/arri.
|
|
1
|
+
export { A as ArriApp, p as ArriRouter, a as DEV_DEFINITION_ENDPOINT, D as DEV_ENDPOINT_ROOT, E as ErrorResponse, B as EventStreamConnection, c as createRpcDefinition, n as defaultConfig, o as defineConfig, h as defineError, w as defineEventStreamRpc, q as defineRoute, d as defineRpc, j as errorResponseFromValidationErrors, y as formatSse, z as formatSseList, e as getRpcParamName, g as getRpcPath, f as getRpcResponseName, k as handleH3Error, t as handleRoute, l as isArriConfig, x as isEventStreamRpc, m as isResolvedArriConfig, b as isRpc, i as isRpcParamSchema, C as registerEventStreamRpc, s as registerRoute, r as registerRpc, u as setSseHeaders, v as validateRpcRequestInput } from './shared/arri.f69ab093.mjs';
|
|
2
2
|
export * from 'h3';
|
|
3
3
|
import 'arri-validate';
|
|
4
4
|
import 'node:process';
|
|
@@ -312,6 +312,13 @@ function registerRpc(router, path, procedure, opts) {
|
|
|
312
312
|
await opts.onBeforeResponse(event);
|
|
313
313
|
}
|
|
314
314
|
if (typeof response === "object") {
|
|
315
|
+
if (!responseValidator?.validate(response)) {
|
|
316
|
+
const errors = arriValidate.a.errors(procedure.response, response);
|
|
317
|
+
throw defineError(500, {
|
|
318
|
+
statusMessage: "Failed to serialize response. Response does not match specified schema",
|
|
319
|
+
data: errors
|
|
320
|
+
});
|
|
321
|
+
}
|
|
315
322
|
h3.setResponseHeader(event, "Content-Type", "application/json");
|
|
316
323
|
await h3.send(
|
|
317
324
|
event,
|
|
@@ -423,13 +430,17 @@ var __publicField$2 = (obj, key, value) => {
|
|
|
423
430
|
return value;
|
|
424
431
|
};
|
|
425
432
|
function setSseHeaders(event) {
|
|
426
|
-
|
|
433
|
+
const isHttp2 = event._http2 === true;
|
|
434
|
+
const input = {
|
|
427
435
|
"Content-Type": "text/event-stream",
|
|
428
436
|
"Cache-Control": "private, no-cache, no-store, no-transform, must-revalidate, max-age=0",
|
|
429
|
-
Connection: "keep-alive",
|
|
430
437
|
Pragma: "no-cache",
|
|
431
438
|
"X-Accel-Buffering": "no"
|
|
432
|
-
}
|
|
439
|
+
};
|
|
440
|
+
if (!isHttp2) {
|
|
441
|
+
input.Connection = "keep-alive";
|
|
442
|
+
}
|
|
443
|
+
h3.setResponseHeaders(event, input);
|
|
433
444
|
}
|
|
434
445
|
function defineEventStreamRpc(config) {
|
|
435
446
|
return {
|
|
@@ -470,6 +481,8 @@ class EventStreamConnection {
|
|
|
470
481
|
__publicField$2(this, "writer");
|
|
471
482
|
__publicField$2(this, "writerIsClosed", false);
|
|
472
483
|
__publicField$2(this, "encoder");
|
|
484
|
+
__publicField$2(this, "validationErrors");
|
|
485
|
+
__publicField$2(this, "validator");
|
|
473
486
|
__publicField$2(this, "serializer");
|
|
474
487
|
__publicField$2(this, "h3Event");
|
|
475
488
|
__publicField$2(this, "pingInterval");
|
|
@@ -484,6 +497,8 @@ class EventStreamConnection {
|
|
|
484
497
|
this.encoder = new TextEncoder();
|
|
485
498
|
this.pingIntervalMs = opts.pingInterval ?? 6e4;
|
|
486
499
|
this.serializer = opts.serializer;
|
|
500
|
+
this.validator = opts.validator;
|
|
501
|
+
this.validationErrors = opts.validationErrors;
|
|
487
502
|
void this.writer.closed.then(() => {
|
|
488
503
|
this.writerIsClosed = true;
|
|
489
504
|
});
|
|
@@ -508,19 +523,47 @@ class EventStreamConnection {
|
|
|
508
523
|
if (Array.isArray(data)) {
|
|
509
524
|
const events = [];
|
|
510
525
|
for (const item of data) {
|
|
526
|
+
if (this.validator(item)) {
|
|
527
|
+
events.push({
|
|
528
|
+
id: eventId,
|
|
529
|
+
event: "message",
|
|
530
|
+
data: this.serializer(item)
|
|
531
|
+
});
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
const errors2 = this.validationErrors(item);
|
|
535
|
+
const errorResponse2 = {
|
|
536
|
+
statusCode: 500,
|
|
537
|
+
statusMessage: "Failed to serialize response. Response does not match specified schema.",
|
|
538
|
+
data: errors2
|
|
539
|
+
};
|
|
511
540
|
events.push({
|
|
512
541
|
id: eventId,
|
|
513
|
-
event: "
|
|
514
|
-
data:
|
|
542
|
+
event: "error",
|
|
543
|
+
data: JSON.stringify(errorResponse2)
|
|
515
544
|
});
|
|
516
545
|
}
|
|
517
546
|
await this.publishEvents(events);
|
|
518
547
|
return;
|
|
519
548
|
}
|
|
549
|
+
if (this.validator(data)) {
|
|
550
|
+
await this.publishEvent({
|
|
551
|
+
id: eventId,
|
|
552
|
+
event: "message",
|
|
553
|
+
data: this.serializer(data)
|
|
554
|
+
});
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
const errors = this.validationErrors(data);
|
|
558
|
+
const errorResponse = {
|
|
559
|
+
statusCode: 500,
|
|
560
|
+
statusMessage: "Failed to serialize response. Response does not match specified schema.",
|
|
561
|
+
data: errors
|
|
562
|
+
};
|
|
520
563
|
await this.publishEvent({
|
|
521
564
|
id: eventId,
|
|
522
|
-
event: "
|
|
523
|
-
data:
|
|
565
|
+
event: "error",
|
|
566
|
+
data: JSON.stringify(errorResponse)
|
|
524
567
|
});
|
|
525
568
|
}
|
|
526
569
|
// /**
|
|
@@ -651,8 +694,17 @@ function registerEventStreamRpc(router, path, procedure, opts) {
|
|
|
651
694
|
}
|
|
652
695
|
const connection = new EventStreamConnection(event, {
|
|
653
696
|
pingInterval: procedure.pingInterval,
|
|
697
|
+
validator: responseValidator?.validate ?? function() {
|
|
698
|
+
return true;
|
|
699
|
+
},
|
|
654
700
|
serializer: responseValidator?.serialize ?? function(_) {
|
|
655
701
|
return "";
|
|
702
|
+
},
|
|
703
|
+
validationErrors(input) {
|
|
704
|
+
if (procedure.response) {
|
|
705
|
+
return arriValidate.a.errors(procedure.response, input);
|
|
706
|
+
}
|
|
707
|
+
return [];
|
|
656
708
|
}
|
|
657
709
|
});
|
|
658
710
|
event.context.connection = connection;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createError, isError, setResponseStatus, send, eventHandler, isPreflightRequest, setResponseHeader, readRawBody, getValidatedQuery,
|
|
1
|
+
import { createError, isError, setResponseStatus, send, eventHandler, isPreflightRequest, setResponseHeader, readRawBody, getValidatedQuery, setResponseHeaders, getHeader, sendStream, defineEventHandler, getQuery, setHeader, createRouter, createApp, sendError } from 'h3';
|
|
2
2
|
import { a, isAObjectSchema, isARecordSchema, isADiscriminatorSchema } from 'arri-validate';
|
|
3
3
|
import { nextTick } from 'node:process';
|
|
4
4
|
import { isRpcHttpMethod, removeDisallowedChars } from 'arri-codegen-utils';
|
|
@@ -310,6 +310,13 @@ function registerRpc(router, path, procedure, opts) {
|
|
|
310
310
|
await opts.onBeforeResponse(event);
|
|
311
311
|
}
|
|
312
312
|
if (typeof response === "object") {
|
|
313
|
+
if (!responseValidator?.validate(response)) {
|
|
314
|
+
const errors = a.errors(procedure.response, response);
|
|
315
|
+
throw defineError(500, {
|
|
316
|
+
statusMessage: "Failed to serialize response. Response does not match specified schema",
|
|
317
|
+
data: errors
|
|
318
|
+
});
|
|
319
|
+
}
|
|
313
320
|
setResponseHeader(event, "Content-Type", "application/json");
|
|
314
321
|
await send(
|
|
315
322
|
event,
|
|
@@ -421,13 +428,17 @@ var __publicField$2 = (obj, key, value) => {
|
|
|
421
428
|
return value;
|
|
422
429
|
};
|
|
423
430
|
function setSseHeaders(event) {
|
|
424
|
-
|
|
431
|
+
const isHttp2 = event._http2 === true;
|
|
432
|
+
const input = {
|
|
425
433
|
"Content-Type": "text/event-stream",
|
|
426
434
|
"Cache-Control": "private, no-cache, no-store, no-transform, must-revalidate, max-age=0",
|
|
427
|
-
Connection: "keep-alive",
|
|
428
435
|
Pragma: "no-cache",
|
|
429
436
|
"X-Accel-Buffering": "no"
|
|
430
|
-
}
|
|
437
|
+
};
|
|
438
|
+
if (!isHttp2) {
|
|
439
|
+
input.Connection = "keep-alive";
|
|
440
|
+
}
|
|
441
|
+
setResponseHeaders(event, input);
|
|
431
442
|
}
|
|
432
443
|
function defineEventStreamRpc(config) {
|
|
433
444
|
return {
|
|
@@ -468,6 +479,8 @@ class EventStreamConnection {
|
|
|
468
479
|
__publicField$2(this, "writer");
|
|
469
480
|
__publicField$2(this, "writerIsClosed", false);
|
|
470
481
|
__publicField$2(this, "encoder");
|
|
482
|
+
__publicField$2(this, "validationErrors");
|
|
483
|
+
__publicField$2(this, "validator");
|
|
471
484
|
__publicField$2(this, "serializer");
|
|
472
485
|
__publicField$2(this, "h3Event");
|
|
473
486
|
__publicField$2(this, "pingInterval");
|
|
@@ -482,6 +495,8 @@ class EventStreamConnection {
|
|
|
482
495
|
this.encoder = new TextEncoder();
|
|
483
496
|
this.pingIntervalMs = opts.pingInterval ?? 6e4;
|
|
484
497
|
this.serializer = opts.serializer;
|
|
498
|
+
this.validator = opts.validator;
|
|
499
|
+
this.validationErrors = opts.validationErrors;
|
|
485
500
|
void this.writer.closed.then(() => {
|
|
486
501
|
this.writerIsClosed = true;
|
|
487
502
|
});
|
|
@@ -506,19 +521,47 @@ class EventStreamConnection {
|
|
|
506
521
|
if (Array.isArray(data)) {
|
|
507
522
|
const events = [];
|
|
508
523
|
for (const item of data) {
|
|
524
|
+
if (this.validator(item)) {
|
|
525
|
+
events.push({
|
|
526
|
+
id: eventId,
|
|
527
|
+
event: "message",
|
|
528
|
+
data: this.serializer(item)
|
|
529
|
+
});
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
const errors2 = this.validationErrors(item);
|
|
533
|
+
const errorResponse2 = {
|
|
534
|
+
statusCode: 500,
|
|
535
|
+
statusMessage: "Failed to serialize response. Response does not match specified schema.",
|
|
536
|
+
data: errors2
|
|
537
|
+
};
|
|
509
538
|
events.push({
|
|
510
539
|
id: eventId,
|
|
511
|
-
event: "
|
|
512
|
-
data:
|
|
540
|
+
event: "error",
|
|
541
|
+
data: JSON.stringify(errorResponse2)
|
|
513
542
|
});
|
|
514
543
|
}
|
|
515
544
|
await this.publishEvents(events);
|
|
516
545
|
return;
|
|
517
546
|
}
|
|
547
|
+
if (this.validator(data)) {
|
|
548
|
+
await this.publishEvent({
|
|
549
|
+
id: eventId,
|
|
550
|
+
event: "message",
|
|
551
|
+
data: this.serializer(data)
|
|
552
|
+
});
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
const errors = this.validationErrors(data);
|
|
556
|
+
const errorResponse = {
|
|
557
|
+
statusCode: 500,
|
|
558
|
+
statusMessage: "Failed to serialize response. Response does not match specified schema.",
|
|
559
|
+
data: errors
|
|
560
|
+
};
|
|
518
561
|
await this.publishEvent({
|
|
519
562
|
id: eventId,
|
|
520
|
-
event: "
|
|
521
|
-
data:
|
|
563
|
+
event: "error",
|
|
564
|
+
data: JSON.stringify(errorResponse)
|
|
522
565
|
});
|
|
523
566
|
}
|
|
524
567
|
// /**
|
|
@@ -649,8 +692,17 @@ function registerEventStreamRpc(router, path, procedure, opts) {
|
|
|
649
692
|
}
|
|
650
693
|
const connection = new EventStreamConnection(event, {
|
|
651
694
|
pingInterval: procedure.pingInterval,
|
|
695
|
+
validator: responseValidator?.validate ?? function() {
|
|
696
|
+
return true;
|
|
697
|
+
},
|
|
652
698
|
serializer: responseValidator?.serialize ?? function(_) {
|
|
653
699
|
return "";
|
|
700
|
+
},
|
|
701
|
+
validationErrors(input) {
|
|
702
|
+
if (procedure.response) {
|
|
703
|
+
return a.errors(procedure.response, input);
|
|
704
|
+
}
|
|
705
|
+
return [];
|
|
654
706
|
}
|
|
655
707
|
});
|
|
656
708
|
event.context.connection = connection;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arri",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -37,17 +37,17 @@
|
|
|
37
37
|
"h3": "^1.10.0",
|
|
38
38
|
"jiti": "^1.21.0",
|
|
39
39
|
"ofetch": "^1.3.3",
|
|
40
|
-
"pathe": "^1.1.
|
|
41
|
-
"prettier": "^3.
|
|
42
|
-
"scule": "^1.
|
|
40
|
+
"pathe": "^1.1.2",
|
|
41
|
+
"prettier": "^3.2.2",
|
|
42
|
+
"scule": "^1.2.0",
|
|
43
43
|
"uncrypto": "^0.1.3",
|
|
44
|
-
"arri-codegen-dart": "0.
|
|
45
|
-
"arri-
|
|
46
|
-
"arri-codegen-utils": "0.
|
|
47
|
-
"arri-
|
|
44
|
+
"arri-codegen-dart": "0.20.0",
|
|
45
|
+
"arri-validate": "0.20.0",
|
|
46
|
+
"arri-codegen-utils": "0.20.0",
|
|
47
|
+
"arri-codegen-ts": "0.20.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@types/degit": "^2.8.6",
|
|
51
|
-
"bun-types": "^1.0.
|
|
51
|
+
"bun-types": "^1.0.22"
|
|
52
52
|
}
|
|
53
53
|
}
|