@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/storage.js CHANGED
@@ -1,40 +1,65 @@
1
- import axios from "axios"; // [A CORREÇÃO ESTÁ AQUI]
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 para o Storage do projeto.
13
- * Isso lida com o fluxo de "presign" (URL assinada) automaticamente.
14
- * @param file O objeto 'File' (do navegador) ou um Buffer (do Node.js)
15
- * @param fileName O nome do arquivo (ex: "imagem.png")
16
- * @param contentType O tipo (ex: "image/png")
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
- const size = file.size || file.length;
21
- // 1. Pedir a URL de upload para nossa API
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: fileName,
24
- contentType: contentType,
25
- size: 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("API não retornou uma URL de upload assinada.");
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
- // 3. Retorna os detalhes do objeto (downloadUrl, objectId, etc.)
38
- return presign;
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.0",
4
- "description": "SDK do Cliente para a Plataforma API",
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.13.2",
23
- "ws": "^8.18.0"
26
+ "axios": "^1.6.0"
24
27
  },
25
28
  "devDependencies": {
26
- "@types/ws": "^8.5.11",
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
- // Salva o token DENTRO da instância do SDK
28
- this.client.setToken(data.token);
29
- return data; // Retorna { user, token }
30
- } catch (e: unknown) {
31
- this.client.setToken(null); // Limpa o token em caso de falha
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
- this.client.setToken(data.token);
30
+ if (data.token) {
31
+ this.client.setToken(data.token);
32
+ }
49
33
  return data;
50
- } catch (e: unknown) {
34
+ } catch (e) {
51
35
  this.client.setToken(null);
52
36
  throw e;
53
37
  }
54
38
  }
55
39
 
56
- /**
57
- * Desconecta o usuário limpando o token do SDK.
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: any;
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 (Firestore-like)
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 (ex: 'tasks' ou 'posts').
28
- * @param collectionName O nome da coleção (ex: "tasks")
29
- * @returns Uma instância de CollectionReference para operar nela
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
- * Representa uma referência a uma coleção específica no banco.
38
- * Contém os métodos CRUD (create, list, update, delete) e subscribe.
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
- // Constrói a URL do WebSocket (substituindo http por ws)
51
- this.wsUrl = client.apiUrl.replace(/^http/, "ws");
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
- * Lista todos os documentos da coleção.
56
- * (GET /v1/db/tasks)
89
+ * Busca um documento pelo ID.
57
90
  */
58
- async list() {
59
- const { data } = await this.http.get(`/db/${this.collectionName}`);
60
- return data.data; // Retorna o array de documentos
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 na coleção.
65
- * (POST /v1/db/tasks)
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: Record<string, any>) {
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; // Retorna o documento criado
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: Record<string, any>) {
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; // Retorna o documento atualizado
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
- const { data } = await this.http.delete(`/db/${this.collectionName}/${id}`);
97
- return data; // Retorna { ok: true, ... }
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 na coleção.
102
- * @param callback A função que será chamada com (action, data)
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 tipados como T.
104
130
  */
105
131
  subscribe(
106
- callback: (action: "create" | "update" | "delete", data: any) => void
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
- throw new Error("Não é possível se inscrever sem um token e projectId.");
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
- const ws = new WebSocket(url);
119
-
120
- ws.on("open", () => {
121
- console.log(
122
- `[SDK] Inscrito em tempo real na coleção '${this.collectionName}'`
123
- );
124
- });
125
-
126
- ws.on("message", (message: string) => {
127
- try {
128
- const payload = JSON.parse(message) as WebSocketMessage;
129
- // Chama o callback do usuário
130
- callback(payload.action, payload.data);
131
- } catch (e) {
132
- console.error("[SDK] Erro ao processar mensagem WS:", e);
133
- }
134
- });
135
-
136
- ws.on("close", () => {
137
- console.log(`[SDK] Desconectado da coleção '${this.collectionName}'`);
138
- });
139
-
140
- ws.on("error", (err) => {
141
- console.error("[SDK] Erro no WebSocket:", err.message);
142
- });
143
-
144
- // Retorna uma função que o usuário pode chamar para parar de "ouvir"
145
- return () => {
146
- ws.close();
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
+ }
@@ -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
+ }
@@ -1,27 +1,29 @@
1
1
  // src/http-client.ts
2
2
  import axios, { type AxiosInstance } from "axios";
3
- import { PlataformaClient } from "./index.js"; // Importa o tipo da classe principal
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
- * Ela usa um interceptor para adicionar dinamicamente os
8
- * headers 'Authorization' e 'X-Project-ID' em cada requisição.
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
- baseURL: `${client.apiUrl}/v1`, // Adiciona o /v1 automaticamente
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
+ }