@allanfsouza/aether-sdk 1.0.0 → 2.1.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/auth.d.ts +2 -18
- package/dist/auth.js +16 -23
- package/dist/database.d.ts +30 -27
- package/dist/database.js +75 -55
- package/dist/errors.d.ts +12 -0
- package/dist/errors.js +42 -0
- package/dist/functions.d.ts +8 -0
- package/dist/functions.js +17 -0
- package/dist/http-client.d.ts +3 -5
- package/dist/http-client.js +11 -7
- package/dist/index.d.ts +12 -6
- package/dist/index.js +12 -7
- package/dist/storage.d.ts +19 -11
- package/dist/storage.js +50 -25
- package/errors.ts +46 -0
- package/package.json +10 -8
- package/src/auth.ts +22 -31
- package/src/database.ts +100 -70
- package/src/errors.ts +50 -0
- package/src/functions.ts +31 -0
- package/src/http-client.ts +17 -9
- package/src/index.ts +24 -11
- package/src/storage.ts +53 -25
- package/tsconfig.json +16 -9
package/dist/storage.js
CHANGED
|
@@ -1,40 +1,65 @@
|
|
|
1
|
-
import axios from "axios";
|
|
2
|
-
/**
|
|
3
|
-
* Módulo de Storage
|
|
4
|
-
* Lida com upload e download de arquivos.
|
|
5
|
-
*/
|
|
1
|
+
import axios from "axios";
|
|
6
2
|
export class StorageModule {
|
|
7
3
|
constructor(client, http) {
|
|
8
4
|
this.client = client;
|
|
9
5
|
this.http = http;
|
|
10
6
|
}
|
|
11
7
|
/**
|
|
12
|
-
* Faz o upload de um arquivo
|
|
13
|
-
*
|
|
14
|
-
* @param
|
|
15
|
-
* @param
|
|
16
|
-
* @param
|
|
17
|
-
* @returns O objeto do arquivo criado no banco
|
|
8
|
+
* Faz o upload de um arquivo.
|
|
9
|
+
* @param file Arquivo (Browser: File, Node: Buffer)
|
|
10
|
+
* @param fileName Nome do arquivo (ex: 'foto.jpg')
|
|
11
|
+
* @param contentType MIME Type (ex: 'image/jpeg')
|
|
12
|
+
* @param folder (Opcional) Pasta de destino (ex: 'usuarios/123/')
|
|
18
13
|
*/
|
|
19
|
-
async upload(file, fileName, contentType
|
|
20
|
-
|
|
21
|
-
//
|
|
14
|
+
async upload(file, fileName, contentType, folder // [NOVO]
|
|
15
|
+
) {
|
|
16
|
+
// Calcula tamanho de forma segura para Browser e Node
|
|
17
|
+
let size = 0;
|
|
18
|
+
if (typeof File !== "undefined" && file instanceof File) {
|
|
19
|
+
size = file.size;
|
|
20
|
+
}
|
|
21
|
+
else if (typeof Blob !== "undefined" && file instanceof Blob) {
|
|
22
|
+
size = file.size;
|
|
23
|
+
}
|
|
24
|
+
else if (file instanceof Buffer) {
|
|
25
|
+
// Node.js
|
|
26
|
+
size = file.length;
|
|
27
|
+
}
|
|
28
|
+
// 1. Pedir URL assinada
|
|
22
29
|
const { data: presignData } = await this.http.post("/storage/presign", {
|
|
23
|
-
fileName
|
|
24
|
-
contentType
|
|
25
|
-
size
|
|
30
|
+
fileName,
|
|
31
|
+
contentType,
|
|
32
|
+
size,
|
|
33
|
+
prefix: folder || "", // [NOVO] Envia o prefixo para o backend
|
|
26
34
|
});
|
|
27
|
-
// O 'presign' vem dentro de 'data'
|
|
28
35
|
const presign = presignData.data;
|
|
29
|
-
if (!presign?.url)
|
|
30
|
-
throw new Error("
|
|
31
|
-
|
|
32
|
-
// 2. Enviar o arquivo diretamente para o Minio/S3 (sem auth)
|
|
33
|
-
// Usamos o 'axios' global aqui, não o 'this.http' (que adicionaria auth)
|
|
36
|
+
if (!presign?.url)
|
|
37
|
+
throw new Error("Falha ao obter URL de upload.");
|
|
38
|
+
// 2. Upload direto para S3
|
|
34
39
|
await axios.put(presign.url, file, {
|
|
35
40
|
headers: { "Content-Type": contentType },
|
|
36
41
|
});
|
|
37
|
-
|
|
38
|
-
|
|
42
|
+
return {
|
|
43
|
+
id: presign.objectId,
|
|
44
|
+
key: presign.key,
|
|
45
|
+
downloadUrl: presign.downloadUrl,
|
|
46
|
+
url: presign.downloadUrl, // Alias amigável
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Lista arquivos de uma pasta.
|
|
51
|
+
*/
|
|
52
|
+
async list(folder = "") {
|
|
53
|
+
const { data } = await this.http.get("/storage/list", {
|
|
54
|
+
params: { projectId: this.client.projectId, prefix: folder },
|
|
55
|
+
});
|
|
56
|
+
return data.data; // Retorna { files: [], folders: [] }
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Deleta um arquivo pelo ID.
|
|
60
|
+
*/
|
|
61
|
+
async delete(fileId) {
|
|
62
|
+
await this.http.delete("/storage/delete", { data: { objectId: fileId } });
|
|
63
|
+
return true;
|
|
39
64
|
}
|
|
40
65
|
}
|
package/errors.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
export class AetherError extends Error {
|
|
3
|
+
constructor(
|
|
4
|
+
public code: string,
|
|
5
|
+
public message: string,
|
|
6
|
+
public status?: number,
|
|
7
|
+
public details?: any
|
|
8
|
+
) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "AetherError";
|
|
11
|
+
// Garante que instanceof AetherError funcione bem em qualquer bundler (ES5/ES6)
|
|
12
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function handleAxiosError(error: any): never {
|
|
17
|
+
if (error?.response) {
|
|
18
|
+
// Erro vindo da API (4xx, 5xx)
|
|
19
|
+
const data = error.response.data;
|
|
20
|
+
const message =
|
|
21
|
+
data?.error || data?.message || "Erro desconhecido na API";
|
|
22
|
+
const status: number = error.response.status;
|
|
23
|
+
|
|
24
|
+
let code = "api_error";
|
|
25
|
+
if (status === 401) code = "unauthorized";
|
|
26
|
+
if (status === 403) code = "permission_denied";
|
|
27
|
+
if (status === 404) code = "not_found";
|
|
28
|
+
if (status === 409) code = "conflict";
|
|
29
|
+
if (status === 429) code = "rate_limit_exceeded";
|
|
30
|
+
|
|
31
|
+
throw new AetherError(code, message, status, data);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (error?.request && !error.response) {
|
|
35
|
+
// Erro de rede (sem resposta, offline, timeout)
|
|
36
|
+
throw new AetherError(
|
|
37
|
+
"network_error",
|
|
38
|
+
"Não foi possível conectar ao servidor Aether. Verifique sua conexão.",
|
|
39
|
+
0
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Erro na configuração / código cliente
|
|
44
|
+
const fallbackMessage = error?.message || "Erro interno no cliente SDK.";
|
|
45
|
+
throw new AetherError("client_error", fallbackMessage);
|
|
46
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@allanfsouza/aether-sdk",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "SDK do Cliente para a Plataforma
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "SDK do Cliente para a Plataforma Aether",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"type": "module",
|
|
@@ -15,15 +15,17 @@
|
|
|
15
15
|
"build": "npx tsc",
|
|
16
16
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
17
17
|
},
|
|
18
|
-
"keywords": [
|
|
18
|
+
"keywords": [
|
|
19
|
+
"baas",
|
|
20
|
+
"sdk",
|
|
21
|
+
"aether"
|
|
22
|
+
],
|
|
19
23
|
"author": "",
|
|
20
24
|
"license": "ISC",
|
|
21
25
|
"dependencies": {
|
|
22
|
-
"axios": "^1.
|
|
23
|
-
"ws": "^8.18.0"
|
|
26
|
+
"axios": "^1.6.0"
|
|
24
27
|
},
|
|
25
28
|
"devDependencies": {
|
|
26
|
-
"
|
|
27
|
-
"typescript": "^5.9.3"
|
|
29
|
+
"typescript": "^5.3.0"
|
|
28
30
|
}
|
|
29
|
-
}
|
|
31
|
+
}
|
package/src/auth.ts
CHANGED
|
@@ -2,10 +2,6 @@
|
|
|
2
2
|
import type { AxiosInstance } from "axios";
|
|
3
3
|
import type { PlataformaClient } from "./index.js";
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* Módulo de Autenticação
|
|
7
|
-
* Lida com login, registro e gerenciamento de token.
|
|
8
|
-
*/
|
|
9
5
|
export class AuthModule {
|
|
10
6
|
private client: PlataformaClient;
|
|
11
7
|
private http: AxiosInstance;
|
|
@@ -15,48 +11,43 @@ export class AuthModule {
|
|
|
15
11
|
this.http = http;
|
|
16
12
|
}
|
|
17
13
|
|
|
18
|
-
/**
|
|
19
|
-
* Autentica um usuário e armazena o token no SDK.
|
|
20
|
-
* @param email O e-mail do usuário
|
|
21
|
-
* @param password A senha do usuário
|
|
22
|
-
* @returns O objeto do usuário e o token
|
|
23
|
-
*/
|
|
24
14
|
async login(email: string, password: string) {
|
|
25
15
|
try {
|
|
26
16
|
const { data } = await this.http.post("/auth/login", { email, password });
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
17
|
+
if (data.token) {
|
|
18
|
+
this.client.setToken(data.token);
|
|
19
|
+
}
|
|
20
|
+
return data;
|
|
21
|
+
} catch (e) {
|
|
22
|
+
this.client.setToken(null);
|
|
32
23
|
throw e;
|
|
33
24
|
}
|
|
34
25
|
}
|
|
35
26
|
|
|
36
|
-
|
|
37
|
-
* Registra um novo usuário e já o loga.
|
|
38
|
-
* @param credentials Nome, email e senha
|
|
39
|
-
* @returns O objeto do usuário e o token
|
|
40
|
-
*/
|
|
41
|
-
async register(credentials: {
|
|
42
|
-
name: string;
|
|
43
|
-
email: string;
|
|
44
|
-
password: string;
|
|
45
|
-
}) {
|
|
27
|
+
async register(credentials: { name: string; email: string; password: string }) {
|
|
46
28
|
try {
|
|
47
29
|
const { data } = await this.http.post("/auth/register", credentials);
|
|
48
|
-
|
|
30
|
+
if (data.token) {
|
|
31
|
+
this.client.setToken(data.token);
|
|
32
|
+
}
|
|
49
33
|
return data;
|
|
50
|
-
} catch (e
|
|
34
|
+
} catch (e) {
|
|
51
35
|
this.client.setToken(null);
|
|
52
36
|
throw e;
|
|
53
37
|
}
|
|
54
38
|
}
|
|
55
39
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
40
|
+
async forgotPassword(email: string) {
|
|
41
|
+
const { data } = await this.http.post("/auth/forgot-password", { email });
|
|
42
|
+
return data;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async resetPassword(token: string, newPassword: string) {
|
|
46
|
+
const { data } = await this.http.post("/auth/reset-password", { token, newPassword });
|
|
47
|
+
return data;
|
|
48
|
+
}
|
|
49
|
+
|
|
59
50
|
logout() {
|
|
60
51
|
this.client.setToken(null);
|
|
61
52
|
}
|
|
62
|
-
}
|
|
53
|
+
}
|
package/src/database.ts
CHANGED
|
@@ -4,15 +4,25 @@ import type { PlataformaClient } from "./index.js";
|
|
|
4
4
|
import WebSocket from "ws";
|
|
5
5
|
|
|
6
6
|
// Tipo para a mensagem que recebemos do WebSocket
|
|
7
|
-
type WebSocketMessage = {
|
|
7
|
+
type WebSocketMessage<T = any> = {
|
|
8
8
|
action: "create" | "update" | "delete";
|
|
9
9
|
collection: string;
|
|
10
|
-
data:
|
|
10
|
+
data: T;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// [NOVO] Opções de Listagem Avançada
|
|
14
|
+
export type ListOptions<T> = {
|
|
15
|
+
// Filtro parcial (ex: { status: 'active' })
|
|
16
|
+
filter?: Partial<T>;
|
|
17
|
+
// Ordenação
|
|
18
|
+
sort?: {
|
|
19
|
+
field: keyof T & string; // Garante que o campo existe no tipo T
|
|
20
|
+
order: "ASC" | "DESC";
|
|
21
|
+
};
|
|
11
22
|
};
|
|
12
23
|
|
|
13
24
|
/**
|
|
14
|
-
* Módulo de Banco de Dados
|
|
15
|
-
* Ponto de entrada para acessar as coleções.
|
|
25
|
+
* Módulo de Banco de Dados
|
|
16
26
|
*/
|
|
17
27
|
export class DatabaseModule {
|
|
18
28
|
private client: PlataformaClient;
|
|
@@ -24,20 +34,20 @@ export class DatabaseModule {
|
|
|
24
34
|
}
|
|
25
35
|
|
|
26
36
|
/**
|
|
27
|
-
* Seleciona uma coleção de dados
|
|
28
|
-
*
|
|
29
|
-
* @
|
|
37
|
+
* Seleciona uma coleção de dados.
|
|
38
|
+
* [PREMIUM] Suporta Generics <T> para tipagem forte.
|
|
39
|
+
* * @example client.db.collection<Product>('products')
|
|
30
40
|
*/
|
|
31
|
-
collection(collectionName: string) {
|
|
32
|
-
return new CollectionReference(this.client, this.http, collectionName);
|
|
41
|
+
collection<T = any>(collectionName: string) {
|
|
42
|
+
return new CollectionReference<T>(this.client, this.http, collectionName);
|
|
33
43
|
}
|
|
34
44
|
}
|
|
35
45
|
|
|
36
46
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
47
|
+
* Referência a uma coleção específica.
|
|
48
|
+
* O <T> define o formato dos dados (ex: interface User).
|
|
39
49
|
*/
|
|
40
|
-
class CollectionReference {
|
|
50
|
+
class CollectionReference<T> {
|
|
41
51
|
private client: PlataformaClient;
|
|
42
52
|
private http: AxiosInstance;
|
|
43
53
|
private collectionName: string;
|
|
@@ -47,103 +57,123 @@ class CollectionReference {
|
|
|
47
57
|
this.client = client;
|
|
48
58
|
this.http = http;
|
|
49
59
|
this.collectionName = name;
|
|
50
|
-
//
|
|
51
|
-
|
|
60
|
+
// Ajusta protocolo para WS/WSS
|
|
61
|
+
const protocol = client.apiUrl.startsWith("https") ? "wss" : "ws";
|
|
62
|
+
this.wsUrl = client.apiUrl.replace(/^https?/, protocol);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Lista documentos da coleção com filtros opcionais.
|
|
67
|
+
* @param options Filtros e Ordenação
|
|
68
|
+
*/
|
|
69
|
+
async list(options?: ListOptions<T>): Promise<T[]> {
|
|
70
|
+
const params: any = {};
|
|
71
|
+
|
|
72
|
+
// Converte os objetos do SDK para strings que o Backend entende
|
|
73
|
+
if (options?.filter) {
|
|
74
|
+
params.filter = JSON.stringify(options.filter);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (options?.sort) {
|
|
78
|
+
// Backend espera formato array: ["campo", "DESC"]
|
|
79
|
+
params.sort = JSON.stringify([options.sort.field, options.sort.order]);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const { data } = await this.http.get(`/db/${this.collectionName}`, {
|
|
83
|
+
params,
|
|
84
|
+
});
|
|
85
|
+
return data.data;
|
|
52
86
|
}
|
|
53
87
|
|
|
54
88
|
/**
|
|
55
|
-
*
|
|
56
|
-
* (GET /v1/db/tasks)
|
|
89
|
+
* Busca um documento pelo ID.
|
|
57
90
|
*/
|
|
58
|
-
async
|
|
59
|
-
const { data } = await this.http.get(`/db/${this.collectionName}`);
|
|
60
|
-
return data.data;
|
|
91
|
+
async get(id: string): Promise<T> {
|
|
92
|
+
const { data } = await this.http.get(`/db/${this.collectionName}/${id}`);
|
|
93
|
+
return data.data;
|
|
61
94
|
}
|
|
62
95
|
|
|
63
96
|
/**
|
|
64
|
-
* Cria um novo documento
|
|
65
|
-
* (
|
|
66
|
-
* @param newData O objeto de dados a ser criado
|
|
97
|
+
* Cria um novo documento.
|
|
98
|
+
* O Partial<T> permite criar sem passar campos gerados (como id, createdAt).
|
|
67
99
|
*/
|
|
68
|
-
async create(newData:
|
|
100
|
+
async create(newData: Partial<T>): Promise<T> {
|
|
69
101
|
const { data } = await this.http.post(
|
|
70
102
|
`/db/${this.collectionName}`,
|
|
71
103
|
newData
|
|
72
104
|
);
|
|
73
|
-
return data.data;
|
|
105
|
+
return data.data;
|
|
74
106
|
}
|
|
75
107
|
|
|
76
108
|
/**
|
|
77
109
|
* Atualiza um documento existente.
|
|
78
|
-
* (PUT /v1/db/tasks/:id)
|
|
79
|
-
* @param id O ID do documento
|
|
80
|
-
* @param updates Os campos a serem atualizados
|
|
81
110
|
*/
|
|
82
|
-
async update(id: string, updates:
|
|
111
|
+
async update(id: string, updates: Partial<T>): Promise<T> {
|
|
83
112
|
const { data } = await this.http.put(
|
|
84
113
|
`/db/${this.collectionName}/${id}`,
|
|
85
114
|
updates
|
|
86
115
|
);
|
|
87
|
-
return data.data;
|
|
116
|
+
return data.data;
|
|
88
117
|
}
|
|
89
118
|
|
|
90
119
|
/**
|
|
91
120
|
* Deleta um documento.
|
|
92
|
-
* (DELETE /v1/db/tasks/:id)
|
|
93
|
-
* @param id O ID do documento
|
|
94
121
|
*/
|
|
95
|
-
async delete(id: string) {
|
|
96
|
-
|
|
97
|
-
return
|
|
122
|
+
async delete(id: string): Promise<boolean> {
|
|
123
|
+
await this.http.delete(`/db/${this.collectionName}/${id}`);
|
|
124
|
+
return true;
|
|
98
125
|
}
|
|
99
126
|
|
|
100
127
|
/**
|
|
101
|
-
* Inscreve-se para mudanças em tempo real
|
|
102
|
-
*
|
|
103
|
-
* @returns Uma função 'unsubscribe' para fechar a conexão
|
|
128
|
+
* Inscreve-se para mudanças em tempo real.
|
|
129
|
+
* O callback recebe os dados já tipados como T.
|
|
104
130
|
*/
|
|
105
131
|
subscribe(
|
|
106
|
-
callback: (action: "create" | "update" | "delete", data:
|
|
132
|
+
callback: (action: "create" | "update" | "delete", data: T) => void
|
|
107
133
|
) {
|
|
108
134
|
const token = this.client.getToken();
|
|
109
135
|
const projectId = this.client.projectId;
|
|
110
136
|
|
|
111
137
|
if (!token || !projectId) {
|
|
112
|
-
|
|
138
|
+
console.warn("[SDK] Realtime falhou: Token ou ProjectId ausentes.");
|
|
139
|
+
return () => {};
|
|
113
140
|
}
|
|
114
141
|
|
|
115
|
-
// Constrói a URL: ws://localhost:3000/v1/db/subscribe/tasks?token=...&projectId=...
|
|
116
142
|
const url = `${this.wsUrl}/v1/db/subscribe/${this.collectionName}?token=${token}&projectId=${projectId}`;
|
|
117
143
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
144
|
+
let ws: WebSocket | null = null;
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
ws = new WebSocket(url);
|
|
148
|
+
|
|
149
|
+
ws.onopen = () => {
|
|
150
|
+
// Conectado
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
ws.onmessage = (event: any) => {
|
|
154
|
+
try {
|
|
155
|
+
const raw = event.data?.toString() || event.toString();
|
|
156
|
+
if (raw === "pong") return;
|
|
157
|
+
|
|
158
|
+
const payload = JSON.parse(raw) as WebSocketMessage<T>;
|
|
159
|
+
callback(payload.action, payload.data);
|
|
160
|
+
} catch (e) {
|
|
161
|
+
// Erro silencioso de parse
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// Mantém a conexão viva (Heartbeat)
|
|
166
|
+
const pingInterval = setInterval(() => {
|
|
167
|
+
if (ws?.readyState === WebSocket.OPEN) ws.send("ping");
|
|
168
|
+
}, 30000);
|
|
169
|
+
|
|
170
|
+
return () => {
|
|
171
|
+
clearInterval(pingInterval);
|
|
172
|
+
if (ws) ws.close();
|
|
173
|
+
};
|
|
174
|
+
} catch (err) {
|
|
175
|
+
console.error("[SDK] Erro WS:", err);
|
|
176
|
+
return () => {};
|
|
177
|
+
}
|
|
148
178
|
}
|
|
149
179
|
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
export class AetherError extends Error {
|
|
3
|
+
constructor(
|
|
4
|
+
public code: string,
|
|
5
|
+
public message: string,
|
|
6
|
+
public status?: number,
|
|
7
|
+
public details?: any
|
|
8
|
+
) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "AetherError";
|
|
11
|
+
// Garante que instanceof AetherError funcione mesmo em cenários com transpile/bundler
|
|
12
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Converte qualquer erro vindo do Axios em um AetherError padronizado.
|
|
18
|
+
* Essa função é pensada para ser usada no interceptor de responses.
|
|
19
|
+
*/
|
|
20
|
+
export function handleAxiosError(error: any): never {
|
|
21
|
+
// Erro com resposta do servidor (4xx, 5xx)
|
|
22
|
+
if (error && error.response) {
|
|
23
|
+
const data = error.response.data;
|
|
24
|
+
const message =
|
|
25
|
+
data?.error || data?.message || "Erro desconhecido na API";
|
|
26
|
+
const status: number = error.response.status;
|
|
27
|
+
|
|
28
|
+
let code = "api_error";
|
|
29
|
+
if (status === 401) code = "unauthorized";
|
|
30
|
+
if (status === 403) code = "permission_denied";
|
|
31
|
+
if (status === 404) code = "not_found";
|
|
32
|
+
if (status === 429) code = "rate_limit_exceeded";
|
|
33
|
+
|
|
34
|
+
throw new AetherError(code, message, status, data);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Erro de rede (sem resposta do servidor)
|
|
38
|
+
if (error && error.request && !error.response) {
|
|
39
|
+
throw new AetherError(
|
|
40
|
+
"network_error",
|
|
41
|
+
"Não foi possível conectar ao servidor Aether.",
|
|
42
|
+
0
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Erro de configuração / código do cliente
|
|
47
|
+
const fallbackMessage =
|
|
48
|
+
(error && error.message) || "Erro interno no cliente SDK.";
|
|
49
|
+
throw new AetherError("client_error", fallbackMessage);
|
|
50
|
+
}
|
package/src/functions.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// src/functions.ts
|
|
2
|
+
import type { AxiosInstance } from "axios";
|
|
3
|
+
import type { PlataformaClient } from "./index.js";
|
|
4
|
+
|
|
5
|
+
export class FunctionsModule {
|
|
6
|
+
private client: PlataformaClient;
|
|
7
|
+
private http: AxiosInstance;
|
|
8
|
+
|
|
9
|
+
constructor(client: PlataformaClient, http: AxiosInstance) {
|
|
10
|
+
this.client = client;
|
|
11
|
+
this.http = http;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async invoke<T = any>(
|
|
15
|
+
functionName: string,
|
|
16
|
+
body?: any,
|
|
17
|
+
method?: "GET" | "POST" | "PUT" | "DELETE"
|
|
18
|
+
) {
|
|
19
|
+
const projectId = this.client.projectId;
|
|
20
|
+
const cleanName = functionName.replace(/^\//, "");
|
|
21
|
+
const finalMethod = method || (body ? "POST" : "GET");
|
|
22
|
+
|
|
23
|
+
const response = await this.http.request<T>({
|
|
24
|
+
url: `/functions/http/${projectId}/${cleanName}`,
|
|
25
|
+
method: finalMethod,
|
|
26
|
+
data: body,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
return response.data;
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/http-client.ts
CHANGED
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
// src/http-client.ts
|
|
2
2
|
import axios, { type AxiosInstance } from "axios";
|
|
3
|
-
import { PlataformaClient } from "./index.js";
|
|
3
|
+
import type { PlataformaClient } from "./index.js";
|
|
4
|
+
import { handleAxiosError } from "./errors.js";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Cria uma instância do Axios pré-configurada.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* * @param client A instância principal do PlataformaClient
|
|
10
|
-
* @returns Uma instância configurada do Axios
|
|
8
|
+
* - Injeta automaticamente headers de Auth e Projeto.
|
|
9
|
+
* - Converte erros em AetherError.
|
|
11
10
|
*/
|
|
12
11
|
export function createHttpClient(client: PlataformaClient): AxiosInstance {
|
|
13
12
|
const http = axios.create({
|
|
14
|
-
|
|
13
|
+
// Adiciona o /v1 automaticamente em todas as chamadas do SDK
|
|
14
|
+
baseURL: `${client.apiUrl}/v1`,
|
|
15
|
+
headers: {
|
|
16
|
+
"Content-Type": "application/json",
|
|
17
|
+
},
|
|
15
18
|
});
|
|
16
19
|
|
|
20
|
+
// Interceptor de REQUEST
|
|
17
21
|
http.interceptors.request.use((config) => {
|
|
18
|
-
// 1. Pega o token atual do cliente
|
|
19
22
|
const token = client.getToken();
|
|
20
23
|
if (token) {
|
|
21
24
|
config.headers.Authorization = `Bearer ${token}`;
|
|
22
25
|
}
|
|
23
26
|
|
|
24
|
-
// 2. Pega o ID do projeto
|
|
25
27
|
if (client.projectId) {
|
|
26
28
|
config.headers["X-Project-ID"] = client.projectId;
|
|
27
29
|
}
|
|
@@ -29,5 +31,11 @@ export function createHttpClient(client: PlataformaClient): AxiosInstance {
|
|
|
29
31
|
return config;
|
|
30
32
|
});
|
|
31
33
|
|
|
34
|
+
// Interceptor de RESPONSE
|
|
35
|
+
http.interceptors.response.use(
|
|
36
|
+
(response) => response,
|
|
37
|
+
(error) => handleAxiosError(error)
|
|
38
|
+
);
|
|
39
|
+
|
|
32
40
|
return http;
|
|
33
|
-
}
|
|
41
|
+
}
|