Skip to content

Commit 8d308f7

Browse files
Merge pull request #771 from appwrite/feat-replace-axios
Replace Axios with Undici
2 parents 196595b + 7361d91 commit 8d308f7

File tree

11 files changed

+373
-272
lines changed

11 files changed

+373
-272
lines changed

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
fail-fast: false
1414
matrix:
1515
php-version: ['8.1']
16-
sdk: [Android11Java8, Android11Java11, Android12Java8, Android12Java11, CLINode14, CLINode16, DartBeta, DartStable, Deno1193, Deno1303, DotNet60, DotNet70, FlutterStable, FlutterBeta, Go112, Go118, KotlinJava8, KotlinJava11, KotlinJava17, Node12, Node14, Node16, PHP74, PHP80, Python38, Python39, Python310, Ruby27, Ruby30, Ruby31, AppleSwift55, Swift55, WebChromium, WebNode]
16+
sdk: [Android11Java8, Android11Java11, Android12Java8, Android12Java11, CLINode16, CLINode18, DartBeta, DartStable, Deno1193, Deno1303, DotNet60, DotNet70, FlutterStable, FlutterBeta, Go112, Go118, KotlinJava8, KotlinJava11, KotlinJava17, Node12, Node14, Node16, PHP74, PHP80, Python38, Python39, Python310, Ruby27, Ruby30, Ruby31, AppleSwift55, Swift55, WebChromium, WebNode]
1717

1818
steps:
1919
- name: Checkout repository

mock-server/app/http.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,18 @@
5353
'buffer_output_size' => $payloadSize,
5454
]);
5555

56+
// Version Route for CLI
57+
App::get('/v1/health/version')
58+
->desc('Get version')
59+
->groups(['api', 'health'])
60+
->label('scope', 'public')
61+
->label('sdk.response.code', Response::STATUS_CODE_OK)
62+
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
63+
->inject('response')
64+
->action(function (UtopiaSwooleResponse $response) {
65+
$response->json([ 'version' => '1.0.0' ]);
66+
});
67+
5668
// Mock Routes
5769
App::get('/v1/mock/tests/foo')
5870
->desc('Get Foo')

src/SDK/Language/CLI.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,33 @@ public function getFiles(): array
180180
];
181181
}
182182

183+
/**
184+
* @param array $parameter
185+
* @param array $nestedTypes
186+
* @return string
187+
*/
188+
public function getTypeName(array $parameter, array $spec = []): string
189+
{
190+
if (isset($parameter['enumName'])) {
191+
return \ucfirst($parameter['enumName']);
192+
}
193+
if (!empty($parameter['enumValues'])) {
194+
return \ucfirst($parameter['name']);
195+
}
196+
return match ($parameter['type']) {
197+
self::TYPE_INTEGER,
198+
self::TYPE_NUMBER => 'number',
199+
self::TYPE_STRING => 'string',
200+
self::TYPE_FILE => 'string',
201+
self::TYPE_BOOLEAN => 'boolean',
202+
self::TYPE_OBJECT => 'object',
203+
self::TYPE_ARRAY => (!empty(($parameter['array'] ?? [])['type']) && !\is_array($parameter['array']['type']))
204+
? $this->getTypeName($parameter['array']) . '[]'
205+
: 'string[]',
206+
default => $parameter['type'],
207+
};
208+
}
209+
183210
/**
184211
* @param array $param
185212
* @return string

templates/cli/base/params.twig

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
let payload = {};
2+
{% for parameter in method.parameters.query %}
3+
if (typeof {{ parameter.name | caseCamel | escapeKeyword }} !== 'undefined') {
4+
payload['{{ parameter.name }}'] = {{ parameter.name | caseCamel | escapeKeyword }};
5+
}
6+
{% endfor %}
7+
{% for parameter in method.parameters.body %}
8+
{% if parameter.type == 'file' %}
9+
{% if method.packaging %}
10+
const folderPath = fs.realpathSync({{ parameter.name | caseCamel | escapeKeyword }});
11+
if (!fs.lstatSync(folderPath).isDirectory()) {
12+
throw new Error('The path is not a directory.');
13+
}
14+
15+
const ignorer = ignore();
16+
17+
const func = localConfig.getFunction(functionId);
18+
19+
if (func.ignore) {
20+
ignorer.add(func.ignore);
21+
log('Ignoring files using configuration from appwrite.json');
22+
} else if (fs.existsSync(pathLib.join({{ parameter.name | caseCamel | escapeKeyword }}, '.gitignore'))) {
23+
ignorer.add(fs.readFileSync(pathLib.join({{ parameter.name | caseCamel | escapeKeyword }}, '.gitignore')).toString());
24+
log('Ignoring files in .gitignore');
25+
}
26+
27+
const files = getAllFiles({{ parameter.name | caseCamel | escapeKeyword }}).map((file) => pathLib.relative({{ parameter.name | caseCamel | escapeKeyword }}, file)).filter((file) => !ignorer.ignores(file));
28+
29+
await tar
30+
.create({
31+
gzip: true,
32+
sync: true,
33+
cwd: folderPath,
34+
file: 'code.tar.gz'
35+
}, files);
36+
37+
{% endif %}
38+
const filePath = fs.realpathSync({{ parameter.name | caseCamel | escapeKeyword }});
39+
const nodeStream = fs.createReadStream(filePath);
40+
const stream = convertReadStreamToReadableStream(nodeStream);
41+
42+
if (typeof filePath !== 'undefined') {
43+
{{ parameter.name | caseCamel | escapeKeyword }} = { type: 'file', stream, filename: pathLib.basename(filePath), size: fs.statSync(filePath).size };
44+
payload['{{ parameter.name }}'] = {{ parameter.name | caseCamel | escapeKeyword }}
45+
}
46+
{% elseif parameter.type == 'boolean' %}
47+
if (typeof {{ parameter.name | caseCamel | escapeKeyword }} !== 'undefined') {
48+
payload['{{ parameter.name }}'] = {{ parameter.name | caseCamel | escapeKeyword }};
49+
}
50+
{% elseif parameter.type == 'number' %}
51+
if (typeof {{ parameter.name | caseCamel | escapeKeyword }} !== 'undefined') {
52+
payload['{{ parameter.name }}'] = {{ parameter.name | caseCamel | escapeKeyword }};
53+
}
54+
{% elseif parameter.type == 'string' %}
55+
if (typeof {{ parameter.name | caseCamel | escapeKeyword }} !== 'undefined') {
56+
payload['{{ parameter.name }}'] = {{ parameter.name | caseCamel | escapeKeyword }};
57+
}
58+
{% elseif parameter.type == 'object' %}
59+
if (typeof {{ parameter.name | caseCamel | escapeKeyword }} !== 'undefined') {
60+
payload['{{ parameter.name }}'] = JSON.parse({{ parameter.name | caseCamel | escapeKeyword}});
61+
}
62+
{% elseif parameter.type == 'array' %}
63+
{{ parameter.name | caseCamel | escapeKeyword}} = {{ parameter.name | caseCamel | escapeKeyword}} === true ? [] : {{ parameter.name | caseCamel | escapeKeyword}};
64+
if (typeof {{ parameter.name | caseCamel | escapeKeyword }} !== 'undefined') {
65+
payload['{{ parameter.name }}'] = {{ parameter.name | caseCamel | escapeKeyword}};
66+
}
67+
{% else %}
68+
if (typeof {{ parameter.name | caseCamel | escapeKeyword }} !== 'undefined') {
69+
payload['{{ parameter.name }}'] = {{ parameter.name | caseCamel | escapeKeyword}}{% if method.consumes[0] == "multipart/form-data" %}.toString(){% endif %};
70+
}
71+
{% endif %}
72+
{% endfor %}
73+
74+
{% if method.type == 'location' %}
75+
payload['project'] = localConfig.getProject().projectId
76+
payload['key'] = globalConfig.getKey();
77+
const queryParams = new URLSearchParams(payload);
78+
apiPath = `${globalConfig.getEndpoint()}${apiPath}?${queryParams.toString()}`;
79+
{% endif %}

templates/cli/base/requests/api.twig

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
let response = undefined;
2+
3+
response = await client.call('{{ method.method | caseLower }}', apiPath, {
4+
{% for parameter in method.parameters.header %}
5+
'{{ parameter.name }}': ${{ parameter.name | caseCamel | escapeKeyword }},
6+
{% endfor %}
7+
{% for key, header in method.headers %}
8+
'{{ key }}': '{{ header }}',
9+
{% endfor %}
10+
}, payload{% if method.type == 'location' %}, 'arraybuffer'{% endif %});
11+
12+
{% if method.type == 'location' %}
13+
fs.writeFileSync(destination, response);
14+
{% endif %}
15+
16+
if (parseOutput) {
17+
parse(response)
18+
success()
19+
}
20+
21+
return response;

templates/cli/base/requests/file.twig

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
{% for parameter in method.parameters.all %}
2+
{% if parameter.type == 'file' %}
3+
4+
const size = {{ parameter.name | caseCamel | escapeKeyword }}.size;
5+
6+
const apiHeaders = {
7+
{% for parameter in method.parameters.header %}
8+
'{{ parameter.name }}': ${{ parameter.name | caseCamel | escapeKeyword }},
9+
{% endfor %}
10+
{% for key, header in method.headers %}
11+
'{{ key }}': '{{ header }}',
12+
{% endfor %}
13+
};
14+
15+
let id = undefined;
16+
let response = undefined;
17+
18+
let chunksUploaded = 0;
19+
{% for parameter in method.parameters.all %}
20+
{% if parameter.isUploadID %}
21+
22+
if({{ parameter.name | caseCamel | escapeKeyword }} != 'unique()') {
23+
try {
24+
response = await client.call('get', apiPath + '/' + {{ parameter.name }}, apiHeaders);
25+
chunksUploaded = response.chunksUploaded;
26+
} catch(e) {
27+
}
28+
}
29+
{% endif %}
30+
{% endfor %}
31+
32+
let currentChunk = 1;
33+
let currentPosition = 0;
34+
let uploadableChunk = new Uint8Array(client.CHUNK_SIZE);
35+
36+
const uploadChunk = async (lastUpload = false) => {
37+
if(currentChunk <= chunksUploaded) {
38+
return;
39+
}
40+
41+
const start = ((currentChunk - 1) * client.CHUNK_SIZE);
42+
let end = start + currentPosition - 1;
43+
44+
if(!lastUpload || currentChunk !== 1) {
45+
apiHeaders['content-range'] = 'bytes ' + start + '-' + end + '/' + size;
46+
}
47+
48+
let uploadableChunkTrimmed;
49+
50+
if(currentPosition + 1 >= client.CHUNK_SIZE) {
51+
uploadableChunkTrimmed = uploadableChunk;
52+
} else {
53+
uploadableChunkTrimmed = new Uint8Array(currentPosition);
54+
for(let i = 0; i <= currentPosition; i++) {
55+
uploadableChunkTrimmed[i] = uploadableChunk[i];
56+
}
57+
}
58+
59+
if (id) {
60+
apiHeaders['x-{{spec.title | caseLower }}-id'] = id;
61+
}
62+
63+
payload['{{ parameter.name }}'] = { type: 'file', file: new File([uploadableChunkTrimmed], {{ parameter.name | caseCamel | escapeKeyword }}.filename), filename: {{ parameter.name | caseCamel | escapeKeyword }}.filename };
64+
65+
response = await client.call('{{ method.method | caseLower }}', apiPath, apiHeaders, payload{% if method.type == 'location' %}, 'arraybuffer'{% endif %});
66+
67+
if (!id) {
68+
id = response['$id'];
69+
}
70+
71+
if (onProgress !== null) {
72+
onProgress({
73+
$id: response['$id'],
74+
progress: Math.min((currentChunk) * client.CHUNK_SIZE, size) / size * 100,
75+
sizeUploaded: end+1,
76+
chunksTotal: response['chunksTotal'],
77+
chunksUploaded: response['chunksUploaded']
78+
});
79+
}
80+
81+
uploadableChunk = new Uint8Array(client.CHUNK_SIZE);
82+
currentChunk++;
83+
currentPosition = 0;
84+
}
85+
86+
for await (const chunk of {{ parameter.name | caseCamel | escapeKeyword }}.stream) {
87+
for(const b of chunk) {
88+
uploadableChunk[currentPosition] = b;
89+
90+
currentPosition++;
91+
if(currentPosition >= client.CHUNK_SIZE) {
92+
await uploadChunk();
93+
currentPosition = 0;
94+
}
95+
}
96+
}
97+
98+
if (currentPosition > 0) { // Check if there's any remaining data for the last chunk
99+
await uploadChunk(true);
100+
}
101+
102+
{% if method.packaging %}
103+
fs.unlinkSync(filePath);
104+
{% endif %}
105+
{% if method.type == 'location' %}
106+
fs.writeFileSync(destination, response);
107+
{% endif %}
108+
109+
if (parseOutput) {
110+
parse(response)
111+
success()
112+
}
113+
114+
return response;
115+
{% endif %}
116+
{% endfor %}

0 commit comments

Comments
 (0)