@e-techsolutions/e-baas-sdk 1.2.5 → 1.3.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/README.md CHANGED
@@ -246,6 +246,221 @@ const query = ebaas.rpc("get_user_stats", {
246
246
  const { data, error } = await query.execute();
247
247
  ```
248
248
 
249
+ ### Advanced Filtering & Query Operators
250
+
251
+ The E-BaaS SDK supports PostgREST-style operators for powerful filtering capabilities:
252
+
253
+ #### Comparison Operators
254
+
255
+ ```typescript
256
+ // Equality
257
+ const users = await ebaas.from("users")
258
+ .select("*")
259
+ .eq("status", "active") // status = 'active'
260
+ .execute();
261
+
262
+ // Not equal
263
+ const users = await ebaas.from("users")
264
+ .select("*")
265
+ .neq("status", "deleted") // status != 'deleted'
266
+ .execute();
267
+
268
+ // Greater than
269
+ const users = await ebaas.from("users")
270
+ .select("*")
271
+ .gt("age", 18) // age > 18
272
+ .execute();
273
+
274
+ // Greater than or equal
275
+ const users = await ebaas.from("users")
276
+ .select("*")
277
+ .gte("created_at", "2024-01-01") // created_at >= '2024-01-01'
278
+ .execute();
279
+
280
+ // Less than
281
+ const users = await ebaas.from("users")
282
+ .select("*")
283
+ .lt("age", 65) // age < 65
284
+ .execute();
285
+
286
+ // Less than or equal
287
+ const users = await ebaas.from("users")
288
+ .select("*")
289
+ .lte("updated_at", "2024-12-31") // updated_at <= '2024-12-31'
290
+ .execute();
291
+ ```
292
+
293
+ #### Text Search Operators
294
+
295
+ ```typescript
296
+ // Case-sensitive pattern matching
297
+ const users = await ebaas.from("users")
298
+ .select("*")
299
+ .like("name", "%John%") // name LIKE '%John%'
300
+ .execute();
301
+
302
+ // Case-insensitive pattern matching
303
+ const users = await ebaas.from("users")
304
+ .select("*")
305
+ .ilike("email", "%@gmail.com") // email ILIKE '%@gmail.com'
306
+ .execute();
307
+ ```
308
+
309
+ #### Null Checks
310
+
311
+ ```typescript
312
+ // Check for null values
313
+ const users = await ebaas.from("users")
314
+ .select("*")
315
+ .is("deleted_at", null) // deleted_at IS NULL
316
+ .execute();
317
+ ```
318
+
319
+ #### Multiple Filters on Same Field
320
+
321
+ **NEW**: You can now apply multiple filters to the same field for range queries:
322
+
323
+ ```typescript
324
+ // Date range filtering (multiple filters on same field)
325
+ const expenses = await ebaas.from("expenses")
326
+ .select("id, user_id, description, amount, due_date")
327
+ .eq("user_id", "7a79cc66-140e-41e0-9bde-ba51e338adac")
328
+ .gte("due_date", "2025-11-01") // due_date >= '2025-11-01'
329
+ .lt("due_date", "2025-12-01") // due_date < '2025-12-01'
330
+ .order("due_date", { ascending: true })
331
+ .execute();
332
+
333
+ // Numeric range filtering
334
+ const products = await ebaas.from("products")
335
+ .select("*")
336
+ .gte("price", 100) // price >= 100
337
+ .lte("price", 500) // price <= 500
338
+ .execute();
339
+
340
+ // Age range
341
+ const users = await ebaas.from("users")
342
+ .select("*")
343
+ .gt("age", 18) // age > 18
344
+ .lt("age", 65) // age < 65
345
+ .execute();
346
+ ```
347
+
348
+ #### Combining Multiple Filters
349
+
350
+ ```typescript
351
+ // Multiple conditions (AND logic)
352
+ const activeUsers = await ebaas.from("users")
353
+ .select("id, name, email, status, created_at")
354
+ .eq("status", "active")
355
+ .gte("created_at", "2024-01-01")
356
+ .ilike("email", "%@company.com")
357
+ .order("created_at", { ascending: false })
358
+ .limit(50)
359
+ .execute();
360
+
361
+ // Complex date and status filtering
362
+ const recentExpenses = await ebaas.from("expenses")
363
+ .select("*")
364
+ .eq("status", "pending")
365
+ .gte("due_date", "2025-11-01")
366
+ .lt("due_date", "2025-12-01")
367
+ .gt("amount", 0)
368
+ .order("due_date", { ascending: true })
369
+ .execute();
370
+ ```
371
+
372
+ #### Ordering and Pagination
373
+
374
+ ```typescript
375
+ // Single column ordering
376
+ const users = await ebaas.from("users")
377
+ .select("*")
378
+ .order("created_at", { ascending: false }) // ORDER BY created_at DESC
379
+ .execute();
380
+
381
+ // Multiple column ordering
382
+ const expenses = await ebaas.from("expenses")
383
+ .select("*")
384
+ .order("due_date", { ascending: true })
385
+ .order("created_at", { ascending: false }) // Secondary sort
386
+ .execute();
387
+
388
+ // Pagination
389
+ const users = await ebaas.from("users")
390
+ .select("*")
391
+ .order("id", { ascending: true })
392
+ .range(0, 9) // LIMIT 10 OFFSET 0 (first 10 records)
393
+ .execute();
394
+
395
+ const nextPage = await ebaas.from("users")
396
+ .select("*")
397
+ .order("id", { ascending: true })
398
+ .range(10, 19) // LIMIT 10 OFFSET 10 (next 10 records)
399
+ .execute();
400
+ ```
401
+
402
+ #### Performance Tips
403
+
404
+ ```typescript
405
+ // Select only needed columns for better performance
406
+ const users = await ebaas.from("users")
407
+ .select("id, name, email") // Instead of select("*")
408
+ .eq("status", "active")
409
+ .limit(100)
410
+ .execute();
411
+
412
+ // Use indexes for filtering - ensure your database has indexes on:
413
+ // - Frequently filtered columns (status, user_id, etc.)
414
+ // - Date columns used in range queries (created_at, due_date, etc.)
415
+ // - Foreign keys (user_id, category_id, etc.)
416
+ ```
417
+
418
+ #### Query Builder Pattern
419
+
420
+ ```typescript
421
+ // Build queries conditionally
422
+ let query = ebaas.from("expenses")
423
+ .select("id, description, amount, due_date, status");
424
+
425
+ // Add filters conditionally
426
+ if (userId) {
427
+ query = query.eq("user_id", userId);
428
+ }
429
+
430
+ if (startDate) {
431
+ query = query.gte("due_date", startDate);
432
+ }
433
+
434
+ if (endDate) {
435
+ query = query.lt("due_date", endDate);
436
+ }
437
+
438
+ if (status) {
439
+ query = query.eq("status", status);
440
+ }
441
+
442
+ // Execute the final query
443
+ const { data, error } = await query
444
+ .order("due_date", { ascending: true })
445
+ .limit(100)
446
+ .execute();
447
+ ```
448
+
449
+ #### Raw SQL Queries
450
+
451
+ For complex queries beyond the query builder, use RPC functions:
452
+
453
+ ```typescript
454
+ // Create a stored procedure for complex filtering
455
+ const query = ebaas.rpc("get_expenses_by_date_range", {
456
+ p_user_id: "7a79cc66-140e-41e0-9bde-ba51e338adac",
457
+ p_start_date: "2025-11-01",
458
+ p_end_date: "2025-12-01",
459
+ p_status: "pending"
460
+ });
461
+ const { data, error } = await query.execute();
462
+ ```
463
+
249
464
  ### Custom Database
250
465
 
251
466
  ```typescript
package/dist/index.cjs CHANGED
@@ -139,6 +139,9 @@ class HttpClient {
139
139
  throw this.handleError(error);
140
140
  }
141
141
  }
142
+ getBaseUrl() {
143
+ return this.baseURL;
144
+ }
142
145
  setAuthToken(token) {
143
146
  this.client.defaults.headers.common['Authorization'] = `Bearer ${token}`;
144
147
  }
@@ -164,16 +167,9 @@ class AuthClient {
164
167
  async signUp(data) {
165
168
  try {
166
169
  const headers = {};
167
- console.log('🔍 [SDK DEBUG] SignUp - workspaceId:', this.workspaceId);
168
- // Incluir x-workspace-id se workspaceId foi fornecido (igual ao signIn)
169
170
  if (this.workspaceId) {
170
171
  headers['x-workspace-id'] = this.workspaceId;
171
- console.log('✅ [SDK DEBUG] SignUp - header x-workspace-id adicionado:', this.workspaceId);
172
- }
173
- else {
174
- console.log('❌ [SDK DEBUG] SignUp - workspaceId é undefined, header não adicionado');
175
172
  }
176
- console.log('🔍 [SDK DEBUG] SignUp - headers finais:', headers);
177
173
  const payload = this.buildSignUpPayload(data);
178
174
  const response = await this.httpClient.post('/auth/v1/signup', payload, {
179
175
  headers
@@ -181,6 +177,16 @@ class AuthClient {
181
177
  if (response.data.session) {
182
178
  this.setSessionInternal(response.data.session);
183
179
  }
180
+ // autoSignIn: if requested and no session returned by signUp, automatically sign in
181
+ if (data.autoSignIn && !response.data.session) {
182
+ try {
183
+ const signInResponse = await this.signIn({ email: data.email, password: data.password });
184
+ return signInResponse;
185
+ }
186
+ catch (_) {
187
+ // If auto sign-in fails, still return the original signUp response
188
+ }
189
+ }
184
190
  return response.data;
185
191
  }
186
192
  catch (error) {
@@ -382,6 +388,16 @@ class AuthClient {
382
388
  return new EBaaSError(error.message || 'Authentication failed', error.status || 0, error);
383
389
  }
384
390
  }
391
+ /**
392
+ * Returns session expiration as milliseconds timestamp.
393
+ * Handles both seconds and milliseconds formats from the API.
394
+ */
395
+ function getExpiresAtMs(session) {
396
+ if (typeof session.expires_at === 'number' && session.expires_at < 1e12) {
397
+ return session.expires_at * 1000; // seconds -> ms
398
+ }
399
+ return Number(session.expires_at); // already ms
400
+ }
385
401
 
386
402
  class QueryBuilder {
387
403
  constructor(httpClient, table, schema, workspaceId, restUrl) {
@@ -399,7 +415,9 @@ class QueryBuilder {
399
415
  return this;
400
416
  }
401
417
  insert(data, options) {
402
- return this.executeQuery('POST', `${this.restUrl}/${this.table}?operation=insert`, { data, options });
418
+ this.query.insertData = data;
419
+ this.query.insertOptions = options;
420
+ return this;
403
421
  }
404
422
  update(data, options) {
405
423
  this.query.updateData = data;
@@ -491,7 +509,14 @@ class QueryBuilder {
491
509
  // Execution method for SELECT, UPDATE, or DELETE
492
510
  async execute() {
493
511
  // Detectar tipo de operação baseado nos dados do query
494
- if (this.query.updateData) {
512
+ if (this.query.insertData) {
513
+ // INSERT operation
514
+ return this.executeQuery('POST', `${this.restUrl}/${this.table}?operation=insert`, {
515
+ data: this.query.insertData,
516
+ options: this.query.insertOptions
517
+ });
518
+ }
519
+ else if (this.query.updateData) {
495
520
  // UPDATE operation
496
521
  return this.executeQuery('PATCH', `${this.restUrl}/${this.table}`, {
497
522
  data: this.query.updateData,
@@ -517,6 +542,9 @@ class QueryBuilder {
517
542
  });
518
543
  }
519
544
  }
545
+ then(onfulfilled, onrejected) {
546
+ return this.execute().then(onfulfilled, onrejected);
547
+ }
520
548
  // Single row execution
521
549
  async single() {
522
550
  this.limit(1);
@@ -578,9 +606,6 @@ class QueryBuilder {
578
606
  });
579
607
  }
580
608
  const deleteUrl = `${baseUrl}?${existingParams.toString()}`;
581
- console.log('🔧 DELETE URL construída:', deleteUrl);
582
- console.log('🔧 DELETE headers:', headers);
583
- console.log('🔧 DELETE filtros originais:', payload.filters);
584
609
  response = await this.httpClient.delete(deleteUrl, { headers });
585
610
  }
586
611
  else if (method === 'PATCH') {
@@ -1916,8 +1941,13 @@ class RealtimeClient {
1916
1941
  }
1917
1942
 
1918
1943
  class FunctionsClient {
1919
- constructor(httpClient) {
1944
+ constructor(httpClient, workspaceId, defaultUnwrap = false) {
1920
1945
  this.httpClient = httpClient;
1946
+ this.workspaceId = workspaceId;
1947
+ this.defaultUnwrap = defaultUnwrap;
1948
+ }
1949
+ setWorkspaceId(workspaceId) {
1950
+ this.workspaceId = workspaceId;
1921
1951
  }
1922
1952
  async invoke(functionName, options) {
1923
1953
  try {
@@ -1926,33 +1956,43 @@ class FunctionsClient {
1926
1956
  'Content-Type': 'application/json',
1927
1957
  ...options?.headers
1928
1958
  };
1959
+ if (this.workspaceId) {
1960
+ headers['x-workspace-id'] = this.workspaceId;
1961
+ }
1929
1962
  let response;
1930
- const url = `/functions/v1/${functionName}`;
1963
+ const url = `/functions/v1/${functionName}/invoke`;
1964
+ const config = { headers, timeout: 120000 };
1931
1965
  switch (method.toUpperCase()) {
1932
1966
  case 'GET':
1933
1967
  response = await this.httpClient.get(url, {
1934
- headers,
1968
+ ...config,
1935
1969
  params: options?.body
1936
1970
  });
1937
1971
  break;
1938
1972
  case 'POST':
1939
- response = await this.httpClient.post(url, options?.body, { headers });
1973
+ response = await this.httpClient.post(url, options?.body, config);
1940
1974
  break;
1941
1975
  case 'PUT':
1942
- response = await this.httpClient.put(url, options?.body, { headers });
1976
+ response = await this.httpClient.put(url, options?.body, config);
1943
1977
  break;
1944
1978
  case 'PATCH':
1945
- response = await this.httpClient.patch(url, options?.body, { headers });
1979
+ response = await this.httpClient.patch(url, options?.body, config);
1946
1980
  break;
1947
1981
  case 'DELETE':
1948
1982
  response = await this.httpClient.delete(url, {
1949
- headers,
1983
+ ...config,
1950
1984
  data: options?.body
1951
1985
  });
1952
1986
  break;
1953
1987
  default:
1954
1988
  throw new EBaaSError(`Unsupported HTTP method: ${method}`, 400);
1955
1989
  }
1990
+ const shouldUnwrap = options?.unwrap ?? this.defaultUnwrap;
1991
+ if (shouldUnwrap) {
1992
+ const raw = response.data;
1993
+ const unwrapped = raw?.data?.body ?? raw?.body ?? raw;
1994
+ return { data: unwrapped, error: null };
1995
+ }
1956
1996
  return {
1957
1997
  data: response.data,
1958
1998
  error: null
@@ -1965,6 +2005,51 @@ class FunctionsClient {
1965
2005
  };
1966
2006
  }
1967
2007
  }
2008
+ async invokeExternal(functionName, options) {
2009
+ try {
2010
+ const baseUrl = this.httpClient.getBaseUrl();
2011
+ const method = (options.method || 'POST').toUpperCase();
2012
+ const url = `${baseUrl}/functions/v1/public/${functionName}?workspaceId=${options.workspaceId}`;
2013
+ const headers = {
2014
+ 'Content-Type': 'application/json',
2015
+ 'x-api-key': options.apiKey,
2016
+ 'x-workspace-id': options.workspaceId,
2017
+ };
2018
+ let response;
2019
+ const config = { headers, timeout: 120000 };
2020
+ switch (method) {
2021
+ case 'GET':
2022
+ response = await this.httpClient.get(url, { ...config, params: options.body });
2023
+ break;
2024
+ case 'POST':
2025
+ response = await this.httpClient.post(url, options.body, config);
2026
+ break;
2027
+ case 'PUT':
2028
+ response = await this.httpClient.put(url, options.body, config);
2029
+ break;
2030
+ case 'PATCH':
2031
+ response = await this.httpClient.patch(url, options.body, config);
2032
+ break;
2033
+ case 'DELETE':
2034
+ response = await this.httpClient.delete(url, { ...config, data: options.body });
2035
+ break;
2036
+ default:
2037
+ throw new EBaaSError(`Unsupported HTTP method: ${method}`, 400);
2038
+ }
2039
+ if (options.unwrap) {
2040
+ const raw = response.data;
2041
+ const unwrapped = raw?.data?.body ?? raw?.body ?? raw;
2042
+ return { data: unwrapped, error: null };
2043
+ }
2044
+ return { data: response.data, error: null };
2045
+ }
2046
+ catch (error) {
2047
+ return {
2048
+ data: null,
2049
+ error: error instanceof EBaaSError ? error : new EBaaSError(error instanceof Error ? error.message : 'External function invocation failed', 0, error)
2050
+ };
2051
+ }
2052
+ }
1968
2053
  async list() {
1969
2054
  try {
1970
2055
  const response = await this.httpClient.get('/functions/v1');
@@ -2126,7 +2211,7 @@ class EBaaSClient {
2126
2211
  this.database = new DatabaseClient(this.httpClient, options?.database?.workspaceId, options?.rest?.url);
2127
2212
  this.storage = new StorageClient(this.httpClient, options?.database?.workspaceId);
2128
2213
  this.realtime = new RealtimeClient(supabaseUrl, supabaseKey, options?.realtime);
2129
- this.functions = new FunctionsClient(this.httpClient);
2214
+ this.functions = new FunctionsClient(this.httpClient, options?.database?.workspaceId, options?.functions?.defaultUnwrap);
2130
2215
  // Initialize custom database detection if enabled
2131
2216
  this.initializeCustomDatabase();
2132
2217
  }
@@ -2150,6 +2235,19 @@ class EBaaSClient {
2150
2235
  invoke(functionName, options) {
2151
2236
  return this.functions.invoke(functionName, options);
2152
2237
  }
2238
+ /**
2239
+ * Creates a new EBaaSClient instance configured for a different workspace.
2240
+ * Reuses the same base URL.
2241
+ */
2242
+ forWorkspace(workspaceId, apiKey) {
2243
+ return new EBaaSClient(this.httpClient.getBaseUrl(), apiKey, {
2244
+ ...this.options,
2245
+ database: {
2246
+ ...this.options?.database,
2247
+ workspaceId,
2248
+ },
2249
+ });
2250
+ }
2153
2251
  /**
2154
2252
  * Inicializa a detecção automática de banco customizado
2155
2253
  */
@@ -2170,6 +2268,7 @@ class EBaaSClient {
2170
2268
  this.auth.setWorkspaceId(workspaceId);
2171
2269
  this.database.setWorkspaceId(workspaceId);
2172
2270
  this.storage.setWorkspaceId(workspaceId);
2271
+ this.functions.setWorkspaceId(workspaceId);
2173
2272
  // Se auto-detecção estiver habilitada, re-detecta configuração para o novo workspace
2174
2273
  if (this.options?.database?.autoDetectCustomDatabase) {
2175
2274
  await this.database.initialize();
@@ -2195,7 +2294,7 @@ function createClient(supabaseUrl, supabaseKey, options) {
2195
2294
 
2196
2295
  // Main client export
2197
2296
  // Version
2198
- const version = "1.1.8";
2297
+ const version = "1.3.0";
2199
2298
 
2200
2299
  exports.AuthClient = AuthClient;
2201
2300
  exports.CustomDatabaseManager = CustomDatabaseManager;
@@ -2210,5 +2309,6 @@ exports.RealtimeClient = RealtimeClient;
2210
2309
  exports.StorageBucket = StorageBucket;
2211
2310
  exports.StorageClient = StorageClient;
2212
2311
  exports.createClient = createClient;
2312
+ exports.getExpiresAtMs = getExpiresAtMs;
2213
2313
  exports.version = version;
2214
2314
  //# sourceMappingURL=index.cjs.map