@allanfsouza/aether-sdk 2.4.3 → 2.4.4

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.
@@ -19,49 +19,16 @@ export declare class QueryBuilder<T> {
19
19
  private limitVal?;
20
20
  private offsetVal?;
21
21
  constructor(collectionRef: CollectionReference<T>);
22
- /**
23
- * Adiciona um filtro de igualdade.
24
- */
25
22
  eq(column: keyof T & string, value: any): this;
26
- /**
27
- * Adiciona um filtro de desigualdade ($ne).
28
- */
29
23
  neq(column: keyof T & string, value: any): this;
30
- /**
31
- * Adiciona um filtro maior que ($gt).
32
- */
33
24
  gt(column: keyof T & string, value: number | string): this;
34
- /**
35
- * Adiciona um filtro maior ou igual ($gte).
36
- */
37
25
  gte(column: keyof T & string, value: number | string): this;
38
- /**
39
- * Adiciona um filtro menor que ($lt).
40
- */
41
26
  lt(column: keyof T & string, value: number | string): this;
42
- /**
43
- * Adiciona um filtro menor ou igual ($lte).
44
- */
45
27
  lte(column: keyof T & string, value: number | string): this;
46
- /**
47
- * Adiciona um filtro LIKE ($like).
48
- */
49
28
  like(column: keyof T & string, value: string): this;
50
- /**
51
- * Define a ordenação.
52
- */
53
29
  order(column: keyof T & string, direction?: "ASC" | "DESC"): this;
54
- /**
55
- * Define o limite de registros.
56
- */
57
30
  limit(count: number): this;
58
- /**
59
- * Define o deslocamento (paginação).
60
- */
61
31
  offset(count: number): this;
62
- /**
63
- * Executa a query e retorna os resultados.
64
- */
65
32
  get(): Promise<T[]>;
66
33
  }
67
34
  /**
@@ -91,18 +58,50 @@ export declare class DatabaseModule {
91
58
  /**
92
59
  * Seleciona uma coleção de dados.
93
60
  * [PREMIUM] Suporta Generics <T> para tipagem forte.
94
- * * @example client.db.collection<Product>('products')
61
+ * @example client.db.collection<Product>('products')
95
62
  */
96
63
  collection<T = any>(collectionName: string): CollectionReference<T>;
64
+ /**
65
+ * [NOVO] Método compatível com estilo 'Supabase/Drizzle'.
66
+ * É um alias para .collection() mas retorna interface compatível com o Showcase.
67
+ * @example client.db.from('posts').select('*')
68
+ */
69
+ from<T = any>(tableName: string): {
70
+ select: (columns?: string) => Promise<{
71
+ data: T[];
72
+ error: null;
73
+ } | {
74
+ data: null;
75
+ error: any;
76
+ }>;
77
+ insert: (data: T) => Promise<{
78
+ data: Awaited<T>;
79
+ error: null;
80
+ } | {
81
+ data: null;
82
+ error: any;
83
+ }>;
84
+ update: (data: Partial<T> & {
85
+ id?: string;
86
+ }) => Promise<{
87
+ data: Awaited<T>;
88
+ error: null;
89
+ } | {
90
+ data: null;
91
+ error: any;
92
+ }>;
93
+ delete: () => Promise<{
94
+ data: null;
95
+ error: string;
96
+ }>;
97
+ };
97
98
  /**
98
99
  * Executa múltiplas operações em uma única transação.
99
- * Se uma falhar, todas são revertidas.
100
100
  */
101
101
  batch(operations: BatchOperation[]): Promise<any[]>;
102
102
  }
103
103
  /**
104
104
  * Referência a uma coleção específica.
105
- * O <T> define o formato dos dados (ex: interface User).
106
105
  */
107
106
  export declare class CollectionReference<T> {
108
107
  private client;
@@ -110,45 +109,13 @@ export declare class CollectionReference<T> {
110
109
  private collectionName;
111
110
  private wsUrl;
112
111
  constructor(client: PlataformaClient, http: AxiosInstance, name: string);
113
- /**
114
- * Inicia o QueryBuilder.
115
- * Atalho para .eq()
116
- */
117
112
  eq(column: keyof T & string, value: any): QueryBuilder<T>;
118
- /**
119
- * Inicia o QueryBuilder.
120
- * Atalho para .gt()
121
- */
122
113
  gt(column: keyof T & string, value: number | string): QueryBuilder<T>;
123
- /**
124
- * Retorna uma nova instância do QueryBuilder.
125
- */
126
114
  query(): QueryBuilder<T>;
127
- /**
128
- * Lista documentos da coleção com filtros opcionais.
129
- * @param options Filtros e Ordenação
130
- */
131
115
  list(options?: ListOptions<T>): Promise<T[]>;
132
- /**
133
- * Busca um documento pelo ID.
134
- */
135
116
  get(id: string): Promise<T>;
136
- /**
137
- * Cria um novo documento.
138
- * O Partial<T> permite criar sem passar campos gerados (como id, createdAt).
139
- */
140
117
  create(newData: Partial<T>): Promise<T>;
141
- /**
142
- * Atualiza um documento existente.
143
- */
144
118
  update(id: string, updates: Partial<T>): Promise<T>;
145
- /**
146
- * Deleta um documento.
147
- */
148
119
  delete(id: string): Promise<boolean>;
149
- /**
150
- * Inscreve-se para mudanças em tempo real.
151
- * O callback recebe os dados já tipados como T.
152
- */
153
120
  subscribe(callback: (action: "create" | "update" | "delete", data: T) => void): () => void;
154
121
  }
package/dist/database.js CHANGED
@@ -1,4 +1,5 @@
1
- import WebSocket from "ws";
1
+ // Ajuste para WebSocket funcionar no Browser (Vite) e Node
2
+ const WS = typeof window !== "undefined" ? window.WebSocket : global.WebSocket || null;
2
3
  /**
3
4
  * Builder para construção fluente de queries.
4
5
  */
@@ -7,79 +8,46 @@ export class QueryBuilder {
7
8
  this.filter = {};
8
9
  this.collectionRef = collectionRef;
9
10
  }
10
- /**
11
- * Adiciona um filtro de igualdade.
12
- */
13
11
  eq(column, value) {
14
12
  this.filter[column] = value;
15
13
  return this;
16
14
  }
17
- /**
18
- * Adiciona um filtro de desigualdade ($ne).
19
- */
20
15
  neq(column, value) {
21
16
  this.filter[column] = { ...this.filter[column], $ne: value };
22
17
  return this;
23
18
  }
24
- /**
25
- * Adiciona um filtro maior que ($gt).
26
- */
27
19
  gt(column, value) {
28
20
  this.filter[column] = { ...this.filter[column], $gt: value };
29
21
  return this;
30
22
  }
31
- /**
32
- * Adiciona um filtro maior ou igual ($gte).
33
- */
34
23
  gte(column, value) {
35
24
  this.filter[column] = { ...this.filter[column], $gte: value };
36
25
  return this;
37
26
  }
38
- /**
39
- * Adiciona um filtro menor que ($lt).
40
- */
41
27
  lt(column, value) {
42
28
  this.filter[column] = { ...this.filter[column], $lt: value };
43
29
  return this;
44
30
  }
45
- /**
46
- * Adiciona um filtro menor ou igual ($lte).
47
- */
48
31
  lte(column, value) {
49
32
  this.filter[column] = { ...this.filter[column], $lte: value };
50
33
  return this;
51
34
  }
52
- /**
53
- * Adiciona um filtro LIKE ($like).
54
- */
55
35
  like(column, value) {
56
36
  this.filter[column] = { ...this.filter[column], $like: value };
57
37
  return this;
58
38
  }
59
- /**
60
- * Define a ordenação.
61
- */
62
39
  order(column, direction = "ASC") {
63
40
  this.sort = { field: column, order: direction };
64
41
  return this;
65
42
  }
66
- /**
67
- * Define o limite de registros.
68
- */
69
43
  limit(count) {
70
44
  this.limitVal = count;
71
45
  return this;
72
46
  }
73
- /**
74
- * Define o deslocamento (paginação).
75
- */
76
47
  offset(count) {
77
48
  this.offsetVal = count;
78
49
  return this;
79
50
  }
80
- /**
81
- * Executa a query e retorna os resultados.
82
- */
83
51
  async get() {
84
52
  return this.collectionRef.list({
85
53
  filter: this.filter,
@@ -100,14 +68,63 @@ export class DatabaseModule {
100
68
  /**
101
69
  * Seleciona uma coleção de dados.
102
70
  * [PREMIUM] Suporta Generics <T> para tipagem forte.
103
- * * @example client.db.collection<Product>('products')
71
+ * @example client.db.collection<Product>('products')
104
72
  */
105
73
  collection(collectionName) {
106
74
  return new CollectionReference(this.client, this.http, collectionName);
107
75
  }
76
+ /**
77
+ * [NOVO] Método compatível com estilo 'Supabase/Drizzle'.
78
+ * É um alias para .collection() mas retorna interface compatível com o Showcase.
79
+ * @example client.db.from('posts').select('*')
80
+ */
81
+ from(tableName) {
82
+ const ref = this.collection(tableName);
83
+ return {
84
+ // Mapeia .select() para .list()
85
+ select: async (columns = "*") => {
86
+ try {
87
+ // Nota: O parametro 'columns' poderia ser enviado ao backend se suportado
88
+ const data = await ref.list();
89
+ return { data, error: null };
90
+ }
91
+ catch (err) {
92
+ return { data: null, error: err.response?.data || err.message };
93
+ }
94
+ },
95
+ // Mapeia .insert() para .create()
96
+ insert: async (data) => {
97
+ try {
98
+ const res = await ref.create(data);
99
+ return { data: res, error: null };
100
+ }
101
+ catch (err) {
102
+ return { data: null, error: err.response?.data || err.message };
103
+ }
104
+ },
105
+ // Mapeia .update() para .update() (Requer ID no payload ou logica extra)
106
+ update: async (data) => {
107
+ try {
108
+ if (!data.id)
109
+ throw new Error("ID é obrigatório para update via .from()");
110
+ const { id, ...updates } = data;
111
+ const res = await ref.update(id, updates);
112
+ return { data: res, error: null };
113
+ }
114
+ catch (err) {
115
+ return { data: null, error: err.response?.data || err.message };
116
+ }
117
+ },
118
+ // Mapeia .delete()
119
+ delete: async () => {
120
+ // O showcase não passou argumentos no delete, o que é perigoso.
121
+ // Aqui retornamos erro ou implementamos delete por query se suportado.
122
+ return { data: null, error: "Delete via .from() requer implementação de filtros" };
123
+ }
124
+ };
125
+ }
108
126
  /**
109
127
  * Executa múltiplas operações em uma única transação.
110
- * Se uma falhar, todas são revertidas.
111
128
  */
112
129
  async batch(operations) {
113
130
  const { data } = await this.http.post("/db/batch", { operations });
@@ -116,7 +133,6 @@ export class DatabaseModule {
116
133
  }
117
134
  /**
118
135
  * Referência a uma coleção específica.
119
- * O <T> define o formato dos dados (ex: interface User).
120
136
  */
121
137
  export class CollectionReference {
122
138
  constructor(client, http, name) {
@@ -127,93 +143,69 @@ export class CollectionReference {
127
143
  const protocol = client.apiUrl.startsWith("https") ? "wss" : "ws";
128
144
  this.wsUrl = client.apiUrl.replace(/^https?/, protocol);
129
145
  }
130
- /**
131
- * Inicia o QueryBuilder.
132
- * Atalho para .eq()
133
- */
146
+ // --- Atalhos de Query ---
134
147
  eq(column, value) {
135
148
  return new QueryBuilder(this).eq(column, value);
136
149
  }
137
- /**
138
- * Inicia o QueryBuilder.
139
- * Atalho para .gt()
140
- */
141
150
  gt(column, value) {
142
151
  return new QueryBuilder(this).gt(column, value);
143
152
  }
144
- // ... Outros atalhos podem ser adicionados conforme necessidade ...
145
- /**
146
- * Retorna uma nova instância do QueryBuilder.
147
- */
148
153
  query() {
149
154
  return new QueryBuilder(this);
150
155
  }
151
- /**
152
- * Lista documentos da coleção com filtros opcionais.
153
- * @param options Filtros e Ordenação
154
- */
156
+ // --- Operações CRUD ---
155
157
  async list(options) {
156
158
  const params = {};
157
- // Converte os objetos do SDK para strings que o Backend entende
158
159
  if (options?.filter) {
159
160
  params.filter = JSON.stringify(options.filter);
160
161
  }
161
162
  if (options?.sort) {
162
- // Backend espera formato array: ["campo", "DESC"]
163
163
  params.sort = JSON.stringify([options.sort.field, options.sort.order]);
164
164
  }
165
- // TODO: Backend precisa implementar limit/offset na rota GET
166
- // if (options?.limit) params.limit = options.limit;
167
- // if (options?.offset) params.offset = options.offset;
165
+ if (options?.limit)
166
+ params.limit = options.limit;
167
+ if (options?.offset)
168
+ params.offset = options.offset;
168
169
  const { data } = await this.http.get(`/db/${this.collectionName}`, {
169
170
  params,
170
171
  });
171
172
  return data.data;
172
173
  }
173
- /**
174
- * Busca um documento pelo ID.
175
- */
176
174
  async get(id) {
177
175
  const { data } = await this.http.get(`/db/${this.collectionName}/${id}`);
178
176
  return data.data;
179
177
  }
180
- /**
181
- * Cria um novo documento.
182
- * O Partial<T> permite criar sem passar campos gerados (como id, createdAt).
183
- */
184
178
  async create(newData) {
185
179
  const { data } = await this.http.post(`/db/${this.collectionName}`, newData);
186
180
  return data.data;
187
181
  }
188
- /**
189
- * Atualiza um documento existente.
190
- */
191
182
  async update(id, updates) {
192
183
  const { data } = await this.http.put(`/db/${this.collectionName}/${id}`, updates);
193
184
  return data.data;
194
185
  }
195
- /**
196
- * Deleta um documento.
197
- */
198
186
  async delete(id) {
199
187
  await this.http.delete(`/db/${this.collectionName}/${id}`);
200
188
  return true;
201
189
  }
202
- /**
203
- * Inscreve-se para mudanças em tempo real.
204
- * O callback recebe os dados já tipados como T.
205
- */
190
+ // --- Realtime ---
206
191
  subscribe(callback) {
192
+ if (!WS) {
193
+ console.warn("[SDK] WebSocket não disponível neste ambiente.");
194
+ return () => { };
195
+ }
207
196
  const token = this.client.getToken();
208
197
  const projectId = this.client.projectId;
209
198
  if (!token || !projectId) {
210
199
  console.warn("[SDK] Realtime falhou: Token ou ProjectId ausentes.");
211
200
  return () => { };
212
201
  }
202
+ // URL correta de subscribe
213
203
  const url = `${this.wsUrl}/v1/db/subscribe/${this.collectionName}?token=${token}&projectId=${projectId}`;
214
204
  let ws = null;
215
205
  try {
216
- ws = new WebSocket(url);
206
+ ws = new WS(url);
207
+ if (!ws)
208
+ return () => { };
217
209
  ws.onopen = () => {
218
210
  // Conectado
219
211
  };
@@ -229,10 +221,12 @@ export class CollectionReference {
229
221
  // Erro silencioso de parse
230
222
  }
231
223
  };
232
- // Mantém a conexão viva (Heartbeat)
224
+ // Heartbeat
233
225
  const pingInterval = setInterval(() => {
234
- if (ws?.readyState === WebSocket.OPEN)
226
+ // [CORREÇÃO] Adicionada verificação explicita 'ws &&' para evitar erro 'possibly null'
227
+ if (ws && ws.readyState === 1) { // 1 = OPEN
235
228
  ws.send("ping");
229
+ }
236
230
  }, 30000);
237
231
  return () => {
238
232
  clearInterval(pingInterval);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allanfsouza/aether-sdk",
3
- "version": "2.4.3",
3
+ "version": "2.4.4",
4
4
  "description": "SDK do Cliente para a Plataforma Aether",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/database.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  // src/database.ts
2
2
  import type { AxiosInstance } from "axios";
3
3
  import type { PlataformaClient } from "./index.js";
4
- import WebSocket from "ws";
4
+
5
+ // Ajuste para WebSocket funcionar no Browser (Vite) e Node
6
+ const WS = typeof window !== "undefined" ? window.WebSocket : (global as any).WebSocket || null;
5
7
 
6
8
  // Tipo para a mensagem que recebemos do WebSocket
7
9
  type WebSocketMessage<T = any> = {
@@ -10,7 +12,7 @@ type WebSocketMessage<T = any> = {
10
12
  data: T;
11
13
  };
12
14
 
13
- // [NOVO] Opções de Listagem Avançada
15
+ // Opções de Listagem Avançada
14
16
  export type ListOptions<T> = {
15
17
  filter?: Partial<T> | Record<string, any>; // Suporta operadores avançados
16
18
  sort?: {
@@ -35,89 +37,56 @@ export class QueryBuilder<T> {
35
37
  this.collectionRef = collectionRef;
36
38
  }
37
39
 
38
- /**
39
- * Adiciona um filtro de igualdade.
40
- */
41
40
  eq(column: keyof T & string, value: any): this {
42
41
  this.filter[column] = value;
43
42
  return this;
44
43
  }
45
44
 
46
- /**
47
- * Adiciona um filtro de desigualdade ($ne).
48
- */
49
45
  neq(column: keyof T & string, value: any): this {
50
46
  this.filter[column] = { ...this.filter[column], $ne: value };
51
47
  return this;
52
48
  }
53
49
 
54
- /**
55
- * Adiciona um filtro maior que ($gt).
56
- */
57
50
  gt(column: keyof T & string, value: number | string): this {
58
51
  this.filter[column] = { ...this.filter[column], $gt: value };
59
52
  return this;
60
53
  }
61
54
 
62
- /**
63
- * Adiciona um filtro maior ou igual ($gte).
64
- */
65
55
  gte(column: keyof T & string, value: number | string): this {
66
56
  this.filter[column] = { ...this.filter[column], $gte: value };
67
57
  return this;
68
58
  }
69
59
 
70
- /**
71
- * Adiciona um filtro menor que ($lt).
72
- */
73
60
  lt(column: keyof T & string, value: number | string): this {
74
61
  this.filter[column] = { ...this.filter[column], $lt: value };
75
62
  return this;
76
63
  }
77
64
 
78
- /**
79
- * Adiciona um filtro menor ou igual ($lte).
80
- */
81
65
  lte(column: keyof T & string, value: number | string): this {
82
66
  this.filter[column] = { ...this.filter[column], $lte: value };
83
67
  return this;
84
68
  }
85
69
 
86
- /**
87
- * Adiciona um filtro LIKE ($like).
88
- */
89
70
  like(column: keyof T & string, value: string): this {
90
71
  this.filter[column] = { ...this.filter[column], $like: value };
91
72
  return this;
92
73
  }
93
74
 
94
- /**
95
- * Define a ordenação.
96
- */
97
75
  order(column: keyof T & string, direction: "ASC" | "DESC" = "ASC"): this {
98
76
  this.sort = { field: column, order: direction };
99
77
  return this;
100
78
  }
101
79
 
102
- /**
103
- * Define o limite de registros.
104
- */
105
80
  limit(count: number): this {
106
81
  this.limitVal = count;
107
82
  return this;
108
83
  }
109
84
 
110
- /**
111
- * Define o deslocamento (paginação).
112
- */
113
85
  offset(count: number): this {
114
86
  this.offsetVal = count;
115
87
  return this;
116
88
  }
117
89
 
118
- /**
119
- * Executa a query e retorna os resultados.
120
- */
121
90
  async get(): Promise<T[]> {
122
91
  return this.collectionRef.list({
123
92
  filter: this.filter,
@@ -151,15 +120,65 @@ export class DatabaseModule {
151
120
  /**
152
121
  * Seleciona uma coleção de dados.
153
122
  * [PREMIUM] Suporta Generics <T> para tipagem forte.
154
- * * @example client.db.collection<Product>('products')
123
+ * @example client.db.collection<Product>('products')
155
124
  */
156
125
  collection<T = any>(collectionName: string) {
157
126
  return new CollectionReference<T>(this.client, this.http, collectionName);
158
127
  }
159
128
 
129
+ /**
130
+ * [NOVO] Método compatível com estilo 'Supabase/Drizzle'.
131
+ * É um alias para .collection() mas retorna interface compatível com o Showcase.
132
+ * @example client.db.from('posts').select('*')
133
+ */
134
+ from<T = any>(tableName: string) {
135
+ const ref = this.collection<T>(tableName);
136
+
137
+ return {
138
+ // Mapeia .select() para .list()
139
+ select: async (columns = "*") => {
140
+ try {
141
+ // Nota: O parametro 'columns' poderia ser enviado ao backend se suportado
142
+ const data = await ref.list();
143
+ return { data, error: null };
144
+ } catch (err: any) {
145
+ return { data: null, error: err.response?.data || err.message };
146
+ }
147
+ },
148
+
149
+ // Mapeia .insert() para .create()
150
+ insert: async (data: T) => {
151
+ try {
152
+ const res = await ref.create(data);
153
+ return { data: res, error: null };
154
+ } catch (err: any) {
155
+ return { data: null, error: err.response?.data || err.message };
156
+ }
157
+ },
158
+
159
+ // Mapeia .update() para .update() (Requer ID no payload ou logica extra)
160
+ update: async (data: Partial<T> & { id?: string }) => {
161
+ try {
162
+ if (!data.id) throw new Error("ID é obrigatório para update via .from()");
163
+ const { id, ...updates } = data;
164
+ const res = await ref.update(id, updates as any);
165
+ return { data: res, error: null };
166
+ } catch (err: any) {
167
+ return { data: null, error: err.response?.data || err.message };
168
+ }
169
+ },
170
+
171
+ // Mapeia .delete()
172
+ delete: async () => {
173
+ // O showcase não passou argumentos no delete, o que é perigoso.
174
+ // Aqui retornamos erro ou implementamos delete por query se suportado.
175
+ return { data: null, error: "Delete via .from() requer implementação de filtros" };
176
+ }
177
+ };
178
+ }
179
+
160
180
  /**
161
181
  * Executa múltiplas operações em uma única transação.
162
- * Se uma falhar, todas são revertidas.
163
182
  */
164
183
  async batch(operations: BatchOperation[]): Promise<any[]> {
165
184
  const { data } = await this.http.post("/db/batch", { operations });
@@ -169,7 +188,6 @@ export class DatabaseModule {
169
188
 
170
189
  /**
171
190
  * Referência a uma coleção específica.
172
- * O <T> define o formato dos dados (ex: interface User).
173
191
  */
174
192
  export class CollectionReference<T> {
175
193
  private client: PlataformaClient;
@@ -181,56 +199,41 @@ export class CollectionReference<T> {
181
199
  this.client = client;
182
200
  this.http = http;
183
201
  this.collectionName = name;
202
+
184
203
  // Ajusta protocolo para WS/WSS
185
204
  const protocol = client.apiUrl.startsWith("https") ? "wss" : "ws";
186
205
  this.wsUrl = client.apiUrl.replace(/^https?/, protocol);
187
206
  }
188
207
 
189
- /**
190
- * Inicia o QueryBuilder.
191
- * Atalho para .eq()
192
- */
208
+ // --- Atalhos de Query ---
209
+
193
210
  eq(column: keyof T & string, value: any): QueryBuilder<T> {
194
211
  return new QueryBuilder<T>(this).eq(column, value);
195
212
  }
196
213
 
197
- /**
198
- * Inicia o QueryBuilder.
199
- * Atalho para .gt()
200
- */
201
214
  gt(column: keyof T & string, value: number | string): QueryBuilder<T> {
202
215
  return new QueryBuilder<T>(this).gt(column, value);
203
216
  }
204
217
 
205
- // ... Outros atalhos podem ser adicionados conforme necessidade ...
206
-
207
- /**
208
- * Retorna uma nova instância do QueryBuilder.
209
- */
210
218
  query(): QueryBuilder<T> {
211
219
  return new QueryBuilder<T>(this);
212
220
  }
213
221
 
214
- /**
215
- * Lista documentos da coleção com filtros opcionais.
216
- * @param options Filtros e Ordenação
217
- */
222
+ // --- Operações CRUD ---
223
+
218
224
  async list(options?: ListOptions<T>): Promise<T[]> {
219
225
  const params: any = {};
220
226
 
221
- // Converte os objetos do SDK para strings que o Backend entende
222
227
  if (options?.filter) {
223
228
  params.filter = JSON.stringify(options.filter);
224
229
  }
225
230
 
226
231
  if (options?.sort) {
227
- // Backend espera formato array: ["campo", "DESC"]
228
232
  params.sort = JSON.stringify([options.sort.field, options.sort.order]);
229
233
  }
230
234
 
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;
235
+ if (options?.limit) params.limit = options.limit;
236
+ if (options?.offset) params.offset = options.offset;
234
237
 
235
238
  const { data } = await this.http.get(`/db/${this.collectionName}`, {
236
239
  params,
@@ -238,18 +241,11 @@ export class CollectionReference<T> {
238
241
  return data.data;
239
242
  }
240
243
 
241
- /**
242
- * Busca um documento pelo ID.
243
- */
244
244
  async get(id: string): Promise<T> {
245
245
  const { data } = await this.http.get(`/db/${this.collectionName}/${id}`);
246
246
  return data.data;
247
247
  }
248
248
 
249
- /**
250
- * Cria um novo documento.
251
- * O Partial<T> permite criar sem passar campos gerados (como id, createdAt).
252
- */
253
249
  async create(newData: Partial<T>): Promise<T> {
254
250
  const { data } = await this.http.post(
255
251
  `/db/${this.collectionName}`,
@@ -258,9 +254,6 @@ export class CollectionReference<T> {
258
254
  return data.data;
259
255
  }
260
256
 
261
- /**
262
- * Atualiza um documento existente.
263
- */
264
257
  async update(id: string, updates: Partial<T>): Promise<T> {
265
258
  const { data } = await this.http.put(
266
259
  `/db/${this.collectionName}/${id}`,
@@ -269,21 +262,21 @@ export class CollectionReference<T> {
269
262
  return data.data;
270
263
  }
271
264
 
272
- /**
273
- * Deleta um documento.
274
- */
275
265
  async delete(id: string): Promise<boolean> {
276
266
  await this.http.delete(`/db/${this.collectionName}/${id}`);
277
267
  return true;
278
268
  }
279
269
 
280
- /**
281
- * Inscreve-se para mudanças em tempo real.
282
- * O callback recebe os dados já tipados como T.
283
- */
270
+ // --- Realtime ---
271
+
284
272
  subscribe(
285
273
  callback: (action: "create" | "update" | "delete", data: T) => void
286
274
  ) {
275
+ if (!WS) {
276
+ console.warn("[SDK] WebSocket não disponível neste ambiente.");
277
+ return () => { };
278
+ }
279
+
287
280
  const token = this.client.getToken();
288
281
  const projectId = this.client.projectId;
289
282
 
@@ -292,12 +285,15 @@ export class CollectionReference<T> {
292
285
  return () => { };
293
286
  }
294
287
 
288
+ // URL correta de subscribe
295
289
  const url = `${this.wsUrl}/v1/db/subscribe/${this.collectionName}?token=${token}&projectId=${projectId}`;
296
290
 
297
291
  let ws: WebSocket | null = null;
298
292
 
299
293
  try {
300
- ws = new WebSocket(url);
294
+ ws = new WS(url);
295
+
296
+ if (!ws) return () => { };
301
297
 
302
298
  ws.onopen = () => {
303
299
  // Conectado
@@ -315,9 +311,12 @@ export class CollectionReference<T> {
315
311
  }
316
312
  };
317
313
 
318
- // Mantém a conexão viva (Heartbeat)
314
+ // Heartbeat
319
315
  const pingInterval = setInterval(() => {
320
- if (ws?.readyState === WebSocket.OPEN) ws.send("ping");
316
+ // [CORREÇÃO] Adicionada verificação explicita 'ws &&' para evitar erro 'possibly null'
317
+ if (ws && ws.readyState === 1) { // 1 = OPEN
318
+ ws.send("ping");
319
+ }
321
320
  }, 30000);
322
321
 
323
322
  return () => {
@@ -329,4 +328,4 @@ export class CollectionReference<T> {
329
328
  return () => { };
330
329
  }
331
330
  }
332
- }
331
+ }