@allanfsouza/aether-sdk 2.0.0 → 2.2.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/src/database.ts CHANGED
@@ -12,15 +12,130 @@ type WebSocketMessage<T = any> = {
12
12
 
13
13
  // [NOVO] Opções de Listagem Avançada
14
14
  export type ListOptions<T> = {
15
- // Filtro parcial (ex: { status: 'active' })
16
- filter?: Partial<T>;
17
- // Ordenação
15
+ filter?: Partial<T> | Record<string, any>; // Suporta operadores avançados
18
16
  sort?: {
19
- field: keyof T & string; // Garante que o campo existe no tipo T
17
+ field: keyof T & string;
20
18
  order: "ASC" | "DESC";
21
19
  };
20
+ limit?: number;
21
+ offset?: number;
22
22
  };
23
23
 
24
+ /**
25
+ * Builder para construção fluente de queries.
26
+ */
27
+ export class QueryBuilder<T> {
28
+ private collectionRef: CollectionReference<T>;
29
+ private filter: Record<string, any> = {};
30
+ private sort?: { field: string; order: "ASC" | "DESC" };
31
+ private limitVal?: number;
32
+ private offsetVal?: number;
33
+
34
+ constructor(collectionRef: CollectionReference<T>) {
35
+ this.collectionRef = collectionRef;
36
+ }
37
+
38
+ /**
39
+ * Adiciona um filtro de igualdade.
40
+ */
41
+ eq(column: keyof T & string, value: any): this {
42
+ this.filter[column] = value;
43
+ return this;
44
+ }
45
+
46
+ /**
47
+ * Adiciona um filtro de desigualdade ($ne).
48
+ */
49
+ neq(column: keyof T & string, value: any): this {
50
+ this.filter[column] = { ...this.filter[column], $ne: value };
51
+ return this;
52
+ }
53
+
54
+ /**
55
+ * Adiciona um filtro maior que ($gt).
56
+ */
57
+ gt(column: keyof T & string, value: number | string): this {
58
+ this.filter[column] = { ...this.filter[column], $gt: value };
59
+ return this;
60
+ }
61
+
62
+ /**
63
+ * Adiciona um filtro maior ou igual ($gte).
64
+ */
65
+ gte(column: keyof T & string, value: number | string): this {
66
+ this.filter[column] = { ...this.filter[column], $gte: value };
67
+ return this;
68
+ }
69
+
70
+ /**
71
+ * Adiciona um filtro menor que ($lt).
72
+ */
73
+ lt(column: keyof T & string, value: number | string): this {
74
+ this.filter[column] = { ...this.filter[column], $lt: value };
75
+ return this;
76
+ }
77
+
78
+ /**
79
+ * Adiciona um filtro menor ou igual ($lte).
80
+ */
81
+ lte(column: keyof T & string, value: number | string): this {
82
+ this.filter[column] = { ...this.filter[column], $lte: value };
83
+ return this;
84
+ }
85
+
86
+ /**
87
+ * Adiciona um filtro LIKE ($like).
88
+ */
89
+ like(column: keyof T & string, value: string): this {
90
+ this.filter[column] = { ...this.filter[column], $like: value };
91
+ return this;
92
+ }
93
+
94
+ /**
95
+ * Define a ordenação.
96
+ */
97
+ order(column: keyof T & string, direction: "ASC" | "DESC" = "ASC"): this {
98
+ this.sort = { field: column, order: direction };
99
+ return this;
100
+ }
101
+
102
+ /**
103
+ * Define o limite de registros.
104
+ */
105
+ limit(count: number): this {
106
+ this.limitVal = count;
107
+ return this;
108
+ }
109
+
110
+ /**
111
+ * Define o deslocamento (paginação).
112
+ */
113
+ offset(count: number): this {
114
+ this.offsetVal = count;
115
+ return this;
116
+ }
117
+
118
+ /**
119
+ * Executa a query e retorna os resultados.
120
+ */
121
+ async get(): Promise<T[]> {
122
+ return this.collectionRef.list({
123
+ filter: this.filter,
124
+ sort: this.sort as any,
125
+ limit: this.limitVal,
126
+ offset: this.offsetVal,
127
+ });
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Operação em lote.
133
+ */
134
+ export type BatchOperation =
135
+ | { type: "create"; collection: string; data: any }
136
+ | { type: "update"; collection: string; id: string; data: any }
137
+ | { type: "delete"; collection: string; id: string };
138
+
24
139
  /**
25
140
  * Módulo de Banco de Dados
26
141
  */
@@ -41,13 +156,22 @@ export class DatabaseModule {
41
156
  collection<T = any>(collectionName: string) {
42
157
  return new CollectionReference<T>(this.client, this.http, collectionName);
43
158
  }
159
+
160
+ /**
161
+ * Executa múltiplas operações em uma única transação.
162
+ * Se uma falhar, todas são revertidas.
163
+ */
164
+ async batch(operations: BatchOperation[]): Promise<any[]> {
165
+ const { data } = await this.http.post("/db/batch", { operations });
166
+ return data.results;
167
+ }
44
168
  }
45
169
 
46
170
  /**
47
171
  * Referência a uma coleção específica.
48
172
  * O <T> define o formato dos dados (ex: interface User).
49
173
  */
50
- class CollectionReference<T> {
174
+ export class CollectionReference<T> {
51
175
  private client: PlataformaClient;
52
176
  private http: AxiosInstance;
53
177
  private collectionName: string;
@@ -62,6 +186,31 @@ class CollectionReference<T> {
62
186
  this.wsUrl = client.apiUrl.replace(/^https?/, protocol);
63
187
  }
64
188
 
189
+ /**
190
+ * Inicia o QueryBuilder.
191
+ * Atalho para .eq()
192
+ */
193
+ eq(column: keyof T & string, value: any): QueryBuilder<T> {
194
+ return new QueryBuilder<T>(this).eq(column, value);
195
+ }
196
+
197
+ /**
198
+ * Inicia o QueryBuilder.
199
+ * Atalho para .gt()
200
+ */
201
+ gt(column: keyof T & string, value: number | string): QueryBuilder<T> {
202
+ return new QueryBuilder<T>(this).gt(column, value);
203
+ }
204
+
205
+ // ... Outros atalhos podem ser adicionados conforme necessidade ...
206
+
207
+ /**
208
+ * Retorna uma nova instância do QueryBuilder.
209
+ */
210
+ query(): QueryBuilder<T> {
211
+ return new QueryBuilder<T>(this);
212
+ }
213
+
65
214
  /**
66
215
  * Lista documentos da coleção com filtros opcionais.
67
216
  * @param options Filtros e Ordenação
@@ -79,6 +228,10 @@ class CollectionReference<T> {
79
228
  params.sort = JSON.stringify([options.sort.field, options.sort.order]);
80
229
  }
81
230
 
231
+ // TODO: Backend precisa implementar limit/offset na rota GET
232
+ // if (options?.limit) params.limit = options.limit;
233
+ // if (options?.offset) params.offset = options.offset;
234
+
82
235
  const { data } = await this.http.get(`/db/${this.collectionName}`, {
83
236
  params,
84
237
  });
@@ -136,7 +289,7 @@ class CollectionReference<T> {
136
289
 
137
290
  if (!token || !projectId) {
138
291
  console.warn("[SDK] Realtime falhou: Token ou ProjectId ausentes.");
139
- return () => {};
292
+ return () => { };
140
293
  }
141
294
 
142
295
  const url = `${this.wsUrl}/v1/db/subscribe/${this.collectionName}?token=${token}&projectId=${projectId}`;
@@ -173,7 +326,7 @@ class CollectionReference<T> {
173
326
  };
174
327
  } catch (err) {
175
328
  console.error("[SDK] Erro WS:", err);
176
- return () => {};
329
+ return () => { };
177
330
  }
178
331
  }
179
332
  }
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 CHANGED
@@ -11,23 +11,13 @@ export class FunctionsModule {
11
11
  this.http = http;
12
12
  }
13
13
 
14
- /**
15
- * Chama uma função HTTP Serverless.
16
- * @param functionName O nome da função (ou rota, ex: 'pedidos/123')
17
- * @param body (Opcional) Dados para enviar no corpo (POST)
18
- * @param method (Opcional) Método HTTP (padrão POST se tiver body, GET se não)
19
- */
20
14
  async invoke<T = any>(
21
15
  functionName: string,
22
16
  body?: any,
23
17
  method?: "GET" | "POST" | "PUT" | "DELETE"
24
18
  ) {
25
19
  const projectId = this.client.projectId;
26
-
27
- // Remove barras iniciais para evitar URL malformada
28
20
  const cleanName = functionName.replace(/^\//, "");
29
-
30
- // Define método automaticamente se não informado
31
21
  const finalMethod = method || (body ? "POST" : "GET");
32
22
 
33
23
  const response = await this.http.request<T>({
@@ -38,4 +28,4 @@ export class FunctionsModule {
38
28
 
39
29
  return response.data;
40
30
  }
41
- }
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
+ }
package/src/index.ts CHANGED
@@ -4,55 +4,62 @@ import { createHttpClient } from "./http-client.js";
4
4
  import { AuthModule } from "./auth.js";
5
5
  import { DatabaseModule } from "./database.js";
6
6
  import { StorageModule } from "./storage.js";
7
- import { FunctionsModule } from "./functions.js"; // [NOVO] Import do módulo
7
+ import { FunctionsModule } from "./functions.js";
8
+ import { PushModule } from "./push.js";
8
9
 
9
- type ClientConfig = {
10
+ /**
11
+ * Configuração usada para criar o cliente principal da plataforma.
12
+ */
13
+ export type ClientConfig = {
10
14
  apiUrl: string;
11
15
  projectId: string;
12
16
  };
13
17
 
14
18
  /**
15
- * O cliente principal da Plataforma API.
16
- * Ponto de entrada para todos os módulos (Auth, DB, Storage, Functions).
19
+ * O cliente principal da Plataforma API (Aether).
20
+ * Ponto de entrada para todos os módulos (Auth, DB, Storage, Functions, Push).
17
21
  */
18
22
  export class PlataformaClient {
19
- // Propriedades públicas (os "módulos")
23
+ // Módulos públicos disponíveis para o usuário do SDK
20
24
  public auth: AuthModule;
21
25
  public db: DatabaseModule;
22
26
  public storage: StorageModule;
23
- public functions: FunctionsModule; // [NOVO] Propriedade pública
27
+ public functions: FunctionsModule;
28
+ public push: PushModule;
24
29
 
25
- // Propriedades de configuração
30
+ // Configurações imutáveis da instância
26
31
  public apiUrl: string;
27
32
  public projectId: string;
28
33
 
29
- // Propriedades internas
30
- private http: AxiosInstance;
34
+ // Infra interna
35
+ public http: AxiosInstance;
31
36
  private _token: string | null = null;
32
37
 
33
38
  constructor(config: ClientConfig) {
34
39
  if (!config.apiUrl || !config.projectId) {
35
40
  throw new Error("apiUrl e projectId são obrigatórios.");
36
41
  }
37
- this.apiUrl = config.apiUrl.replace(/\/$/, ""); // Remove barra final se houver
42
+
43
+ // Normaliza apiUrl removendo barras finais, se houver
44
+ this.apiUrl = config.apiUrl.replace(/\/+$/, "");
38
45
  this.projectId = config.projectId;
39
46
 
40
- // Inicializa o cliente HTTP (passando 'this', a própria instância para injetar token/ID)
47
+ // Inicializa o cliente HTTP (passando a própria instância)
41
48
  this.http = createHttpClient(this);
42
49
 
43
- // Inicializa os módulos passando a referência do cliente e do axios
50
+ // Inicializa os módulos de alto nível
44
51
  this.auth = new AuthModule(this, this.http);
45
52
  this.db = new DatabaseModule(this, this.http);
46
53
  this.storage = new StorageModule(this, this.http);
47
- this.functions = new FunctionsModule(this, this.http); // [NOVO] Inicialização
54
+ this.functions = new FunctionsModule(this, this.http);
55
+ this.push = new PushModule(this, this.http);
48
56
  }
49
57
 
50
58
  // --- Gerenciamento de Token ---
51
59
 
52
60
  /**
53
61
  * Armazena o token de autenticação em memória.
54
- * Chamado automaticamente pelo AuthModule após login.
55
- * @param token O JWT (ou null para logout)
62
+ * Chamado automaticamente pelo AuthModule após login/registro.
56
63
  */
57
64
  setToken(token: string | null) {
58
65
  this._token = token;
@@ -61,9 +68,25 @@ export class PlataformaClient {
61
68
  /**
62
69
  * Recupera o token de autenticação atual.
63
70
  * Usado pelo http-client para injetar o header Authorization.
64
- * @returns O JWT ou null
65
71
  */
66
72
  getToken(): string | null {
67
73
  return this._token;
68
74
  }
69
75
  }
76
+
77
+ // Re-exports convenientes para quem consome o SDK
78
+ export { AetherError } from "./errors.js";
79
+ export type { ListOptions } from "./database.js";
80
+
81
+ // Tipos relacionados a Push
82
+ export type {
83
+ PushPlatform,
84
+ PushEnvironment,
85
+ PushDevice,
86
+ RegisterDeviceParams,
87
+ SendPushResponse,
88
+ PushStatus,
89
+ PushLogEntry,
90
+ ListPushLogsOptions,
91
+ PushStats,
92
+ } from "./push.js";
package/src/push.ts ADDED
@@ -0,0 +1,157 @@
1
+ // src/push.ts
2
+ import type { AxiosInstance } from "axios";
3
+ import type { PlataformaClient } from "./index.js";
4
+
5
+ export type PushPlatform = "android" | "ios" | "web";
6
+ export type PushEnvironment = "dev" | "staging" | "prod";
7
+
8
+ export interface RegisterDeviceParams {
9
+ platform: PushPlatform;
10
+ token: string;
11
+ environment?: PushEnvironment;
12
+ }
13
+
14
+ export interface PushDevice {
15
+ id: string;
16
+ projectId: string;
17
+ userId?: string | null;
18
+ platform: PushPlatform;
19
+ token: string;
20
+ environment: PushEnvironment;
21
+ createdAt: string;
22
+ lastSeenAt: string;
23
+ }
24
+
25
+ export interface RegisterDeviceResponse {
26
+ device: PushDevice;
27
+ }
28
+
29
+ export interface SendPushResponse {
30
+ sent: number;
31
+ total: number;
32
+ message: string;
33
+ }
34
+
35
+ export type PushStatus = "sent" | "failed";
36
+
37
+ export interface PushLogEntry {
38
+ id: string;
39
+ projectId: string;
40
+ userId?: string | null;
41
+ deviceId?: string | null;
42
+ title: string;
43
+ body: string;
44
+ data: Record<string, string>;
45
+ status: PushStatus;
46
+ errorMessage?: string | null;
47
+ createdAt: string;
48
+ }
49
+
50
+ export interface ListPushLogsOptions {
51
+ limit?: number;
52
+ offset?: number;
53
+ status?: PushStatus;
54
+ }
55
+
56
+ export interface PushStats {
57
+ totalDevices: number;
58
+ sent24h: number;
59
+ successRate: number;
60
+ }
61
+
62
+ export class PushModule {
63
+ constructor(
64
+ private client: PlataformaClient,
65
+ private http: AxiosInstance
66
+ ) { }
67
+
68
+ async registerDevice(params: RegisterDeviceParams): Promise<PushDevice> {
69
+ const projectId = this.client.projectId;
70
+
71
+ const { data } = await this.http.post<RegisterDeviceResponse>(
72
+ `/projects/${projectId}/push/devices`,
73
+ {
74
+ platform: params.platform,
75
+ token: params.token,
76
+ environment: params.environment ?? "prod",
77
+ }
78
+ );
79
+
80
+ return data.device;
81
+ }
82
+
83
+ async sendToToken(params: {
84
+ token: string;
85
+ title: string;
86
+ body: string;
87
+ data?: Record<string, string>;
88
+ environment?: PushEnvironment;
89
+ }): Promise<SendPushResponse> {
90
+ const projectId = this.client.projectId;
91
+
92
+ const { data } = await this.http.post<SendPushResponse>(
93
+ `/projects/${projectId}/push/send`,
94
+ {
95
+ token: params.token,
96
+ title: params.title,
97
+ body: params.body,
98
+ data: params.data ?? {},
99
+ environment: params.environment ?? "prod",
100
+ }
101
+ );
102
+
103
+ return data;
104
+ }
105
+
106
+ async sendToUser(params: {
107
+ userId: string;
108
+ title: string;
109
+ body: string;
110
+ data?: Record<string, string>;
111
+ environment?: PushEnvironment;
112
+ }): Promise<SendPushResponse> {
113
+ const projectId = this.client.projectId;
114
+
115
+ const { data } = await this.http.post<SendPushResponse>(
116
+ `/projects/${projectId}/push/send`,
117
+ {
118
+ userId: params.userId,
119
+ title: params.title,
120
+ body: params.body,
121
+ data: params.data ?? {},
122
+ environment: params.environment ?? "prod",
123
+ }
124
+ );
125
+
126
+ return data;
127
+ }
128
+
129
+ async listLogs(
130
+ options: ListPushLogsOptions = {}
131
+ ): Promise<PushLogEntry[]> {
132
+ const projectId = this.client.projectId;
133
+
134
+ const { data } = await this.http.get<{ data: PushLogEntry[] }>(
135
+ `/projects/${projectId}/push/logs`,
136
+ {
137
+ params: {
138
+ limit: options.limit,
139
+ offset: options.offset,
140
+ status: options.status,
141
+ },
142
+ }
143
+ );
144
+
145
+ return data.data;
146
+ }
147
+
148
+ async getStats(): Promise<PushStats> {
149
+ const projectId = this.client.projectId;
150
+
151
+ const { data } = await this.http.get<PushStats>(
152
+ `/projects/${projectId}/push/stats`
153
+ );
154
+
155
+ return data;
156
+ }
157
+ }
package/tsconfig.json CHANGED
@@ -1,14 +1,21 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "target": "ES2020", // Moderno, mas compatível
4
- "module": "NodeNext", // Suporta tanto 'require' (CJS) quanto 'import' (ESM)
5
- "declaration": true, // [IMPORTANTE] Gera arquivos .d.ts (para autocompletar)
6
- "outDir": "./dist", // Onde o JavaScript compilado irá
7
- "rootDir": "./src", // Onde nosso código TypeScript está
8
- "strict": true, // Força boas práticas
3
+ "target": "ES2020",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "declaration": true,
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
9
10
  "skipLibCheck": true,
10
11
  "forceConsistentCasingInFileNames": true,
11
- "moduleResolution": "NodeNext"
12
+ // Adicionado DOM para reconhecer 'File', 'Blob' e 'WebSocket' nativos
13
+ "lib": [
14
+ "ES2020",
15
+ "DOM"
16
+ ]
12
17
  },
13
- "include": ["src"] // Só compila o que está na pasta 'src'
14
- }
18
+ "include": [
19
+ "src"
20
+ ]
21
+ }