11import { Models } from './models';
2- import { Service } from './service';
32
43/**
54 * Payload type representing a key-value pair with string keys and any values.
@@ -48,7 +47,7 @@ type RealtimeRequest = {
4847/**
4948 * Realtime event response structure with generic payload type.
5049 */
51- export type RealtimeResponseEvent<T extends unknown > = {
50+ type RealtimeResponseEvent<T extends unknown > = {
5251 /**
5352 * List of event names associated with the response.
5453 */
@@ -215,7 +214,7 @@ type Realtime = {
215214/**
216215 * Type representing upload progress information.
217216 */
218- export type UploadProgress = {
217+ type UploadProgress = {
219218 /**
220219 * Identifier for the upload progress.
221220 */
@@ -284,17 +283,18 @@ class {{spec.title | caseUcfirst}}Exception extends Error {
284283 * Client that handles requests to {{spec .title | caseUcfirst }}
285284 */
286285class Client {
286+ static CHUNK_SIZE = 1024 * 1024 * 5;
287+
287288 /**
288289 * Holds configuration such as project.
289290 */
290291 config = {
291292 endpoint: '{{ spec .endpoint }}',
292293 endpointRealtime: '',
293- {% for header in spec .global .headers %}
294+ {%~ for header in spec .global .headers %}
294295 {{ header .key | caseLower }}: '',
295- {% endfor %}
296+ {%~ endfor %}
296297 };
297-
298298 /**
299299 * Custom headers for API requests.
300300 */
@@ -303,9 +303,9 @@ class Client {
303303 'x-sdk-platform': '{{ sdk .platform }}',
304304 'x-sdk-language': '{{ language .name | caseLower }}',
305305 'x-sdk-version': '{{ sdk .version }}',
306- {% for key ,header in spec .global .defaultHeaders %}
306+ {%~ for key ,header in spec .global .defaultHeaders %}
307307 '{{key }}': '{{header }}',
308- {% endfor %}
308+ {%~ endfor %}
309309 };
310310
311311 /**
@@ -337,14 +337,14 @@ class Client {
337337 return this;
338338 }
339339
340- {% for header in spec .global .headers %}
340+ {%~ for header in spec .global .headers %}
341341 /**
342342 * Set {{header .key | caseUcfirst }}
343343 *
344- {% if header .description %}
345- {{header .description | comment2 }}
344+ {%~ if header .description %}
345+ * {{header .description }}
346346 *
347- {% endif %}
347+ {%~ endif %}
348348 * @param value string
349349 *
350350 * @return {this}
@@ -354,8 +354,7 @@ class Client {
354354 this.config.{{ header .key | caseLower }} = value;
355355 return this;
356356 }
357-
358- {% endfor %}
357+ {%~ endfor %}
359358
360359 private realtime: Realtime = {
361360 socket: undefined,
@@ -540,40 +539,18 @@ class Client {
540539 }
541540 }
542541
543- /**
544- * Call API endpoint with the specified method, URL, headers, and parameters.
545- *
546- * @param {string} method - HTTP method (e.g., 'GET', 'POST', 'PUT', 'DELETE').
547- * @param {URL} url - The URL of the API endpoint.
548- * @param {Headers} headers - Custom headers for the API request.
549- * @param {Payload} params - Request parameters.
550- * @returns {Promise<any >} - A promise that resolves with the response data.
551- *
552- * @typedef {Object} Payload - Request payload data.
553- * @property {string} key - The key.
554- * @property {string} value - The value.
555- */
556- async call(method: string, url: URL, headers: Headers = {}, params: Payload = {}): Promise<any > {
542+ prepareRequest(method: string, url: URL, headers: Headers = {}, params: Payload = {}): { uri: string, options: RequestInit } {
557543 method = method.toUpperCase();
558544
559-
560545 headers = Object.assign({}, this.headers, headers);
561546
562547 let options: RequestInit = {
563548 method,
564549 headers,
565- credentials: 'include'
566550 };
567551
568- if (typeof window !== 'undefined' && window.localStorage) {
569- const cookieFallback = window.localStorage.getItem('cookieFallback');
570- if (cookieFallback) {
571- headers['X-Fallback-Cookies'] = cookieFallback;
572- }
573- }
574-
575552 if (method === 'GET') {
576- for (const [key, value] of Object.entries(Service .flatten(params))) {
553+ for (const [key, value] of Object.entries(Client .flatten(params))) {
577554 url.searchParams.append(key, value);
578555 }
579556 } else {
@@ -583,15 +560,17 @@ class Client {
583560 break;
584561
585562 case 'multipart/form-data':
586- let formData = new FormData();
587-
588- for (const key in params) {
589- if (Array.isArray(params[key])) {
590- params[key].forEach((value: any) => {
591- formData.append(key + '[]', value);
592- })
563+ const formData = new FormData();
564+
565+ for (const [key, value] of Object.entries(params)) {
566+ if (value instanceof File) {
567+ formData.append(key, value, value.name);
568+ } else if (Array.isArray(value)) {
569+ for (const nestedValue of value) {
570+ formData.append(`${key}[]`, nestedValue);
571+ }
593572 } else {
594- formData.append(key, params[key] );
573+ formData.append(key, value );
595574 }
596575 }
597576
@@ -601,45 +580,121 @@ class Client {
601580 }
602581 }
603582
604- try {
605- let data = null;
606- const response = await fetch(url.toString(), options);
583+ return { uri: url.toString(), options };
584+ }
585+
586+ async chunkedUpload(method: string, url: URL, headers: Headers = {}, originalPayload: Payload = {}, onProgress: (progress: UploadProgress) => void) {
587+ const file = Object.values(originalPayload).find((value) => value instanceof File);
588+
589+ if (file.size < = Client.CHUNK_SIZE) {
590+ return await this.call(method, url, headers, originalPayload);
591+ }
592+
593+ let start = 0;
594+ let response = null;
607595
608- const warnings = response.headers.get('x-{{ spec .title | lower }}-warning');
609- if (warnings) {
610- warnings.split(';').forEach((warning: string) => console.warn('Warning: ' + warning));
596+ while (start < file.size) {
597+ let end = start + Client.CHUNK_SIZE; // Prepare end for the next chunk
598+ if (end >= file.size) {
599+ end = file.size; // Adjust for the last chunk to include the last byte
611600 }
612601
613- if (response.headers.get('content-type')?.includes('application/json')) {
614- data = await response.json();
615- } else {
616- data = {
617- message: await response.text()
618- };
602+ headers['content-range'] = `bytes ${start}-${end-1}/${file.size}`;
603+ const chunk = file.slice(start, end);
604+
605+ let payload = { ...originalPayload, file: new File([chunk], file.name)};
606+
607+ response = await this.call(method, url, headers, payload);
608+
609+ if (onProgress && typeof onProgress === 'function') {
610+ onProgress({
611+ $id: response.$id,
612+ progress: Math.round((end / file.size) * 100),
613+ sizeUploaded: end,
614+ chunksTotal: Math.ceil(file.size / Client.CHUNK_SIZE),
615+ chunksUploaded: Math.ceil(end / Client.CHUNK_SIZE)
616+ });
619617 }
620618
621- if (400 < = response.status ) {
622- throw new {{spec .title | caseUcfirst }}Exception(data?.message, response.status, data?.type, data) ;
619+ if (response && response.$id ) {
620+ headers['x- {{spec .title | caseLower }}-id'] = response.$id ;
623621 }
624622
625- const cookieFallback = response.headers.get('X-Fallback-Cookies');
623+ start = end;
624+ }
626625
627- if (typeof window !== 'undefined' && window.localStorage && cookieFallback) {
628- window.console.warn('{{spec .title | caseUcfirst }} is using localStorage for session management. Increase your security by adding a custom domain as your API endpoint.');
629- window.localStorage.setItem('cookieFallback', cookieFallback);
630- }
626+ return response;
627+ }
628+
629+ async redirect(method: string, url: URL, headers: Headers = {}, params: Payload = {}): Promise<string > {
630+ const { uri, options } = this.prepareRequest(method, url, headers, params);
631+
632+ const response = await fetch(uri, {
633+ ...options,
634+ redirect: 'manual'
635+ });
636+
637+ if (response.status !== 301 && response.status !== 302) {
638+ throw new {{spec .title | caseUcfirst }}Exception('Invalid redirect', response.status);
639+ }
640+
641+ return response.headers.get('location') || '';
642+ }
631643
632- return data;
633- } catch (e) {
634- if (e instanceof {{spec .title | caseUcfirst }}Exception) {
635- throw e;
644+ async call(method: string, url: URL, headers: Headers = {}, params: Payload = {}, responseType = 'json'): Promise<any > {
645+ const { uri, options } = this.prepareRequest(method, url, headers, params);
646+
647+ let data: any = null;
648+
649+ const response = await fetch(uri, options);
650+
651+ const warnings = response.headers.get('x-{{ spec .title | lower }}-warning');
652+ if (warnings) {
653+ warnings.split(';').forEach((warning: string) => console.warn('Warning: ' + warning));
654+ }
655+
656+ if (response.headers.get('content-type')?.includes('application/json')) {
657+ data = await response.json();
658+ } else if (responseType === 'arrayBuffer') {
659+ data = await response.arrayBuffer();
660+ } else {
661+ data = {
662+ message: await response.text()
663+ };
664+ }
665+
666+ if (400 < = response.status) {
667+ throw new {{spec .title | caseUcfirst }}Exception(data?.message, response.status, data?.type, data);
668+ }
669+
670+ const cookieFallback = response.headers.get('X-Fallback-Cookies');
671+
672+ if (typeof window !== 'undefined' && window.localStorage && cookieFallback) {
673+ window.console.warn('{{spec .title | caseUcfirst }} is using localStorage for session management. Increase your security by adding a custom domain as your API endpoint.');
674+ window.localStorage.setItem('cookieFallback', cookieFallback);
675+ }
676+
677+ return data;
678+ }
679+
680+ static flatten(data: Payload, prefix = ''): Payload {
681+ let output: Payload = {};
682+
683+ for (const [key, value] of Object.entries(data)) {
684+ let finalKey = prefix ? prefix + '[' + key +']' : key;
685+ if (Array.isArray(value)) {
686+ output = { ...output, ...Client.flatten(value, finalKey) };
687+ } else {
688+ output[finalKey] = value;
636689 }
637- throw new {{spec .title | caseUcfirst }}Exception((<Error >e).message);
638690 }
691+
692+ return output;
639693 }
640694}
641695
642696export { Client, {{spec .title | caseUcfirst }}Exception };
643697export { Query } from './query';
644- export type { Models, Payload };
698+ export type { Models, Payload, UploadProgress };
699+ export type { RealtimeResponseEvent };
645700export type { QueryTypes, QueryTypesList } from './query';
0 commit comments