arri 0.0.38 → 0.0.42
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/chunks/index.cjs +3 -3
- package/dist/chunks/index.mjs +3 -3
- package/dist/cli.cjs +11 -11
- package/dist/cli.mjs +4 -4
- package/dist/codegen.cjs +43 -56
- package/dist/codegen.d.cts +3 -4
- package/dist/codegen.d.mts +3 -4
- package/dist/codegen.d.ts +3 -4
- package/dist/codegen.mjs +5 -9
- package/dist/index.cjs +656 -25
- package/dist/index.d.cts +105 -5
- package/dist/index.d.mts +105 -5
- package/dist/index.d.ts +105 -5
- package/dist/index.mjs +639 -6
- package/dist/shared/arri.0752aa2b.cjs +192 -0
- package/dist/shared/{arri.edc9d295.cjs → arri.33c6d46f.cjs} +113 -27
- package/dist/shared/arri.4536bc49.d.cts +77 -0
- package/dist/shared/arri.4536bc49.d.mts +77 -0
- package/dist/shared/arri.4536bc49.d.ts +77 -0
- package/dist/shared/{arri.1a30c6b5.mjs → arri.b38c19c1.mjs} +84 -4
- package/dist/shared/arri.ebf86912.mjs +179 -0
- package/package.json +3 -3
- package/dist/shared/arri.8a065f92.cjs +0 -934
- package/dist/shared/arri.a8159363.d.cts +0 -205
- package/dist/shared/arri.a8159363.d.mts +0 -205
- package/dist/shared/arri.a8159363.d.ts +0 -205
- package/dist/shared/arri.f1c922a6.mjs +0 -893
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { TObject } from '@sinclair/typebox';
|
|
2
|
+
|
|
3
|
+
declare const HttpMethodValues: readonly ["get", "post", "put", "patch", "delete", "head"];
|
|
4
|
+
type HttpMethod = (typeof HttpMethodValues)[number];
|
|
5
|
+
|
|
6
|
+
declare const JsonSchemaScalarTypeValues: readonly ["integer", "number", "bigint", "string", "boolean", "Date"];
|
|
7
|
+
declare const JsonSchemaNullTypeValues: readonly ["null", "undefined"];
|
|
8
|
+
type JsonSchemaType = JsonSchemaScalarType | JsonSchemaNullType | JsonSchemaComplexType;
|
|
9
|
+
interface JsonSchemaTypeBase {
|
|
10
|
+
$id?: string;
|
|
11
|
+
title?: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
}
|
|
14
|
+
interface JsonSchemaScalarType extends JsonSchemaTypeBase {
|
|
15
|
+
type: (typeof JsonSchemaScalarTypeValues)[number];
|
|
16
|
+
}
|
|
17
|
+
interface JsonSchemaNullType extends JsonSchemaTypeBase {
|
|
18
|
+
type: (typeof JsonSchemaNullTypeValues)[number];
|
|
19
|
+
}
|
|
20
|
+
type JsonSchemaComplexType = JsonSchemaObject | JsonSchemaArray | JsonSchemaRecord;
|
|
21
|
+
interface JsonSchemaObject extends JsonSchemaTypeBase {
|
|
22
|
+
type: "object";
|
|
23
|
+
properties: Record<string, JsonSchemaType>;
|
|
24
|
+
required?: string[];
|
|
25
|
+
additionalProperties?: boolean;
|
|
26
|
+
}
|
|
27
|
+
interface JsonSchemaRecord extends JsonSchemaTypeBase {
|
|
28
|
+
type: "object";
|
|
29
|
+
patternProperties: Record<string, JsonSchemaType>;
|
|
30
|
+
}
|
|
31
|
+
interface JsonSchemaArray extends JsonSchemaTypeBase {
|
|
32
|
+
type: "array";
|
|
33
|
+
items: JsonSchemaType;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface ApplicationDef {
|
|
37
|
+
arriSchemaVersion: "0.0.1";
|
|
38
|
+
info?: {
|
|
39
|
+
title?: string;
|
|
40
|
+
description?: string;
|
|
41
|
+
version?: string;
|
|
42
|
+
};
|
|
43
|
+
externalDocs?: {
|
|
44
|
+
description?: string;
|
|
45
|
+
url: string;
|
|
46
|
+
};
|
|
47
|
+
procedures: Record<string, ProcedureDef>;
|
|
48
|
+
models: Record<string, JsonSchemaObject>;
|
|
49
|
+
errors: Omit<TObject, symbol>;
|
|
50
|
+
}
|
|
51
|
+
declare function normalizeWhitespace(input: string): string;
|
|
52
|
+
interface ProcedureDef {
|
|
53
|
+
path: string;
|
|
54
|
+
description?: string;
|
|
55
|
+
method: HttpMethod;
|
|
56
|
+
params: string | undefined;
|
|
57
|
+
response: string | undefined;
|
|
58
|
+
}
|
|
59
|
+
declare function isProcedureDef(input: any): input is ProcedureDef;
|
|
60
|
+
interface ServiceDef {
|
|
61
|
+
[key: string]: ProcedureDef | ServiceDef;
|
|
62
|
+
}
|
|
63
|
+
declare function isServiceDef(input: any): input is ServiceDef;
|
|
64
|
+
declare function isApplicationDef(input: any): input is ApplicationDef;
|
|
65
|
+
declare function unflattenObject(data: Record<string, any>): any;
|
|
66
|
+
declare function unflattenProcedures(procedures: ApplicationDef["procedures"]): Record<string, ProcedureDef | ServiceDef>;
|
|
67
|
+
declare function setNestedObjectProperty<T>(targetProp: string, value: T, object: Record<any, any>): Record<any, any>;
|
|
68
|
+
declare const removeDisallowedChars: (input: string, disallowedChars: string) => string;
|
|
69
|
+
|
|
70
|
+
interface ClientGenerator<TOptions extends Record<string, any> | undefined> {
|
|
71
|
+
generator: (def: ApplicationDef) => any;
|
|
72
|
+
options: TOptions;
|
|
73
|
+
}
|
|
74
|
+
type ClientGeneratorPlugin<TOptions extends Record<string, any> | undefined> = (options: TOptions) => ClientGenerator<TOptions>;
|
|
75
|
+
declare function defineClientGeneratorPlugin<TOptions extends Record<string, any> | undefined>(plugin: ClientGeneratorPlugin<TOptions>): ClientGeneratorPlugin<TOptions>;
|
|
76
|
+
|
|
77
|
+
export { type ApplicationDef as A, type ClientGeneratorPlugin as C, type HttpMethod as H, type JsonSchemaObject as J, type ProcedureDef as P, type ServiceDef as S, type ClientGenerator as a, isServiceDef as b, isApplicationDef as c, defineClientGeneratorPlugin as d, unflattenProcedures as e, isProcedureDef as i, normalizeWhitespace as n, removeDisallowedChars as r, setNestedObjectProperty as s, unflattenObject as u };
|
|
@@ -1,11 +1,83 @@
|
|
|
1
1
|
import { execSync } from 'child_process';
|
|
2
2
|
import { writeFileSync } from 'fs';
|
|
3
|
-
import {
|
|
3
|
+
import { b as unflattenProcedures, i as isServiceDef, p as pascalCase, c as camelCase, a as isProcedureDef } from './arri.ebf86912.mjs';
|
|
4
4
|
|
|
5
5
|
function defineClientGeneratorPlugin(plugin) {
|
|
6
6
|
return plugin;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
const JsonSchemaScalarTypeValues = [
|
|
10
|
+
"integer",
|
|
11
|
+
"number",
|
|
12
|
+
"bigint",
|
|
13
|
+
"string",
|
|
14
|
+
"boolean",
|
|
15
|
+
"Date"
|
|
16
|
+
];
|
|
17
|
+
const JsonSchemaNullTypeValues = ["null", "undefined"];
|
|
18
|
+
function isJsonSchemaScalarType(input) {
|
|
19
|
+
if (typeof input !== "object") {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
if (!("type" in input)) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
return JsonSchemaScalarTypeValues.includes(input.type);
|
|
26
|
+
}
|
|
27
|
+
function isJsonSchemaNullType(input) {
|
|
28
|
+
if (typeof input !== "object") {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
if (!("type" in input)) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
return JsonSchemaNullTypeValues.includes(input.type);
|
|
35
|
+
}
|
|
36
|
+
const isJsonSchemaObject = (input) => {
|
|
37
|
+
if (typeof input !== "object") {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
return "type" in input && input.type === "object" && "properties" in input && typeof input.properties === "object";
|
|
41
|
+
};
|
|
42
|
+
const isJsonSchemaRecord = (input) => {
|
|
43
|
+
if (typeof input !== "object") {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
return "type" in input && input.type === "object" && "patternProperties" in input && typeof input.patternProperties === "object";
|
|
47
|
+
};
|
|
48
|
+
function isJsonSchemaArray(input) {
|
|
49
|
+
if (typeof input !== "object") {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
if ("type" in input && input.type === "array") {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
function isJsonSchemaEnum(input) {
|
|
58
|
+
if (typeof input !== "object") {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
if (!("anyOf" in input) || !Array.isArray(input.anyOf)) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
let prevType;
|
|
65
|
+
for (const item of input.anyOf) {
|
|
66
|
+
if (!isJsonSchemaScalarType(item)) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
if (item.type !== "string" && item.type !== "integer" && item.type !== "number") {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
if (!prevType) {
|
|
73
|
+
prevType = item.type;
|
|
74
|
+
} else if (prevType !== item.type) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
|
|
9
81
|
const dartClientGenerator = defineClientGeneratorPlugin(
|
|
10
82
|
(options) => {
|
|
11
83
|
return {
|
|
@@ -276,6 +348,9 @@ function dartModelFromJsonSchema(modelName, schema, opts) {
|
|
|
276
348
|
${fields.map((field) => {
|
|
277
349
|
const isNullable = field.type.endsWith("?");
|
|
278
350
|
const typeName = field.type.replace("?", "");
|
|
351
|
+
if (typeName === "dynamic") {
|
|
352
|
+
return `"${field.jsonKey}": ${field.name}`;
|
|
353
|
+
}
|
|
279
354
|
if (isDartTypeWithNullables(field.type)) {
|
|
280
355
|
const transformer = transformers[typeName];
|
|
281
356
|
return transformer.toJsonBody(
|
|
@@ -303,7 +378,9 @@ function dartModelFromJsonSchema(modelName, schema, opts) {
|
|
|
303
378
|
};
|
|
304
379
|
}
|
|
305
380
|
${modelDisplayName} copyWith({
|
|
306
|
-
${fields.map(
|
|
381
|
+
${fields.map(
|
|
382
|
+
(field) => `${field.type.replace("?", "")}${field.type !== "dynamic" ? "?" : ""} ${field.name},`
|
|
383
|
+
).join("\n ")}
|
|
307
384
|
}) {
|
|
308
385
|
return ${modelDisplayName}(
|
|
309
386
|
${fields.map((field) => `${field.name}: ${field.name} ?? this.${field.name},`).join("\n ")}
|
|
@@ -316,7 +393,7 @@ ${subModelParts.join("\n")}`;
|
|
|
316
393
|
function dartPropertyTypeFromSchema(objectName, propertyName, schema, opts) {
|
|
317
394
|
const prop = schema.properties?.[propertyName];
|
|
318
395
|
const isOptional = !schema.required?.includes(propertyName);
|
|
319
|
-
let finalType = "";
|
|
396
|
+
let finalType = "dynamic";
|
|
320
397
|
const subTypes = [];
|
|
321
398
|
if (isJsonSchemaEnum(prop)) {
|
|
322
399
|
const enumName = pascalCase(`${objectName}_${propertyName}`);
|
|
@@ -443,6 +520,9 @@ function dartPropertyTypeFromSchema(objectName, propertyName, schema, opts) {
|
|
|
443
520
|
return [finalType, subTypes.length ? subTypes : void 0];
|
|
444
521
|
}
|
|
445
522
|
function dartParsedJsonField(fieldName, jsonKey, dartType) {
|
|
523
|
+
if (dartType === "dynamic") {
|
|
524
|
+
return `${fieldName}: json["${jsonKey}"]`;
|
|
525
|
+
}
|
|
446
526
|
if (isDartTypeWithNullables(dartType)) {
|
|
447
527
|
switch (dartType) {
|
|
448
528
|
case "String":
|
|
@@ -679,4 +759,4 @@ function isDartTypeWithNullables(input) {
|
|
|
679
759
|
return false;
|
|
680
760
|
}
|
|
681
761
|
|
|
682
|
-
export {
|
|
762
|
+
export { isJsonSchemaNullType as a, isJsonSchemaObject as b, isJsonSchemaRecord as c, defineClientGeneratorPlugin as d, isJsonSchemaEnum as e, isJsonSchemaArray as f, dartClientGenerator as g, createDartClient as h, isJsonSchemaScalarType as i, dartServiceFromServiceDefinition as j, dartProcedureFromServiceDefinition as k, dartModelFromJsonSchema as l, dartPropertyTypeFromSchema as m, dartParsedJsonField as n, classTransformer as o, classListTransformer as p, isDartType as q, isDartTypeWithNullables as r, transformers as t };
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
const NUMBER_CHAR_RE = /\d/;
|
|
2
|
+
const STR_SPLITTERS = ["-", "_", "/", "."];
|
|
3
|
+
function isUppercase(char = "") {
|
|
4
|
+
if (NUMBER_CHAR_RE.test(char)) {
|
|
5
|
+
return void 0;
|
|
6
|
+
}
|
|
7
|
+
return char.toUpperCase() === char;
|
|
8
|
+
}
|
|
9
|
+
function splitByCase(string_, separators) {
|
|
10
|
+
const splitters = separators ?? STR_SPLITTERS;
|
|
11
|
+
const parts = [];
|
|
12
|
+
if (!string_ || typeof string_ !== "string") {
|
|
13
|
+
return parts;
|
|
14
|
+
}
|
|
15
|
+
let buff = "";
|
|
16
|
+
let previousUpper;
|
|
17
|
+
let previousSplitter;
|
|
18
|
+
for (const char of string_) {
|
|
19
|
+
const isSplitter = splitters.includes(char);
|
|
20
|
+
if (isSplitter === true) {
|
|
21
|
+
parts.push(buff);
|
|
22
|
+
buff = "";
|
|
23
|
+
previousUpper = void 0;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const isUpper = isUppercase(char);
|
|
27
|
+
if (previousSplitter === false) {
|
|
28
|
+
if (previousUpper === false && isUpper === true) {
|
|
29
|
+
parts.push(buff);
|
|
30
|
+
buff = char;
|
|
31
|
+
previousUpper = isUpper;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (previousUpper === true && isUpper === false && buff.length > 1) {
|
|
35
|
+
const lastChar = buff[buff.length - 1];
|
|
36
|
+
parts.push(buff.slice(0, Math.max(0, buff.length - 1)));
|
|
37
|
+
buff = lastChar + char;
|
|
38
|
+
previousUpper = isUpper;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
buff += char;
|
|
43
|
+
previousUpper = isUpper;
|
|
44
|
+
previousSplitter = isSplitter;
|
|
45
|
+
}
|
|
46
|
+
parts.push(buff);
|
|
47
|
+
return parts;
|
|
48
|
+
}
|
|
49
|
+
function upperFirst(string_) {
|
|
50
|
+
return !string_ ? "" : string_[0].toUpperCase() + string_.slice(1);
|
|
51
|
+
}
|
|
52
|
+
function lowerFirst(string_) {
|
|
53
|
+
return !string_ ? "" : string_[0].toLowerCase() + string_.slice(1);
|
|
54
|
+
}
|
|
55
|
+
function pascalCase(string_) {
|
|
56
|
+
return !string_ ? "" : (Array.isArray(string_) ? string_ : splitByCase(string_)).map((p) => upperFirst(p)).join("");
|
|
57
|
+
}
|
|
58
|
+
function camelCase(string_) {
|
|
59
|
+
return lowerFirst(pascalCase(string_));
|
|
60
|
+
}
|
|
61
|
+
function kebabCase(string_, joiner) {
|
|
62
|
+
return !string_ ? "" : (Array.isArray(string_) ? string_ : splitByCase(string_)).map((p) => p.toLowerCase()).join(joiner ?? "-");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const HttpMethodValues = [
|
|
66
|
+
"get",
|
|
67
|
+
"post",
|
|
68
|
+
"put",
|
|
69
|
+
"patch",
|
|
70
|
+
"delete",
|
|
71
|
+
"head"
|
|
72
|
+
];
|
|
73
|
+
const isHttpMethod = (input) => {
|
|
74
|
+
if (typeof input !== "string") {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
return HttpMethodValues.includes(input);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
function normalizeWhitespace(input) {
|
|
81
|
+
if (input.includes("\n\n")) {
|
|
82
|
+
return normalizeWhitespace(input.split("\n\n").join("\n"));
|
|
83
|
+
}
|
|
84
|
+
const lines = [];
|
|
85
|
+
for (const line of input.split("\n")) {
|
|
86
|
+
lines.push(line.trim());
|
|
87
|
+
}
|
|
88
|
+
const result = lines.join("\n").trim();
|
|
89
|
+
if (result.includes("\n\n")) {
|
|
90
|
+
return normalizeWhitespace(result.split("\n\n").join("\n"));
|
|
91
|
+
}
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
function isProcedureDef(input) {
|
|
95
|
+
if (typeof input !== "object") {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
const expectedKeys = ["path", "method"];
|
|
99
|
+
const existingKeys = Object.keys(input);
|
|
100
|
+
for (const key of expectedKeys) {
|
|
101
|
+
if (!existingKeys.includes(key)) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return isHttpMethod(input.method) && typeof input.path === "string" && (typeof input.params === "string" || typeof input.params === "undefined") && (typeof input.response === "string" || typeof input.response === "undefined");
|
|
106
|
+
}
|
|
107
|
+
function isServiceDef(input) {
|
|
108
|
+
if (typeof input !== "object") {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
for (const key of Object.keys(input)) {
|
|
112
|
+
if (typeof input[key] !== "object") {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
function isApplicationDef(input) {
|
|
119
|
+
if (typeof input !== "object") {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
if (!("procedures" in input) || typeof input.procedures !== "object") {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
if (!("models" in input) || typeof input.models !== "object") {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
if (!("errors" in input) || typeof input.errors !== "object") {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
function unflattenObject(data) {
|
|
134
|
+
if (Object(data) !== data || Array.isArray(data))
|
|
135
|
+
return data;
|
|
136
|
+
const regex = /\.?([^.[\]]+)|\[(\d+)\]/g;
|
|
137
|
+
const result = {};
|
|
138
|
+
for (const p in data) {
|
|
139
|
+
let cur = result;
|
|
140
|
+
let prop = "";
|
|
141
|
+
let m;
|
|
142
|
+
while (m = regex.exec(p)) {
|
|
143
|
+
cur = cur[prop] || (cur[prop] = m[2] ? [] : {});
|
|
144
|
+
prop = m[2] || m[1];
|
|
145
|
+
}
|
|
146
|
+
cur[prop] = data[p];
|
|
147
|
+
}
|
|
148
|
+
return result[""] || result;
|
|
149
|
+
}
|
|
150
|
+
function unflattenProcedures(procedures) {
|
|
151
|
+
return unflattenObject(procedures);
|
|
152
|
+
}
|
|
153
|
+
function setNestedObjectProperty(targetProp, value, object) {
|
|
154
|
+
const parts = targetProp.split(".");
|
|
155
|
+
let current = object;
|
|
156
|
+
for (let i = 0; i < parts.length; i++) {
|
|
157
|
+
const key = parts[i];
|
|
158
|
+
if (i === parts.length - 1) {
|
|
159
|
+
current[key] = value;
|
|
160
|
+
} else {
|
|
161
|
+
if (!current[key]) {
|
|
162
|
+
current[key] = {};
|
|
163
|
+
}
|
|
164
|
+
current = current[key];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return object;
|
|
168
|
+
}
|
|
169
|
+
const removeDisallowedChars = (input, disallowedChars) => {
|
|
170
|
+
let result = input;
|
|
171
|
+
for (const char of disallowedChars) {
|
|
172
|
+
if (result.includes(char)) {
|
|
173
|
+
result = result.split(char).join("");
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return result;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export { isProcedureDef as a, unflattenProcedures as b, camelCase as c, isApplicationDef as d, isHttpMethod as e, isServiceDef as i, kebabCase as k, normalizeWhitespace as n, pascalCase as p, removeDisallowedChars as r, setNestedObjectProperty as s, unflattenObject as u };
|
package/package.json
CHANGED
|
@@ -18,14 +18,14 @@
|
|
|
18
18
|
"scule": "^1.0.0",
|
|
19
19
|
"globby": "^13.2.2",
|
|
20
20
|
"pathe": "^1.1.1",
|
|
21
|
-
"citty": "^0.1.
|
|
21
|
+
"citty": "^0.1.4",
|
|
22
22
|
"c12": "^1.4.2",
|
|
23
23
|
"consola": "^3.2.3",
|
|
24
24
|
"ofetch": "^1.3.3",
|
|
25
25
|
"chokidar": "^3.5.3",
|
|
26
|
-
"listhen": "^1.
|
|
26
|
+
"listhen": "^1.5.0"
|
|
27
27
|
},
|
|
28
|
-
"version": "0.0.
|
|
28
|
+
"version": "0.0.42",
|
|
29
29
|
"repository": {
|
|
30
30
|
"url": "https://github.com/modiimedia/arri"
|
|
31
31
|
}
|