@e-techsolutions/e-baas-sdk
Version:
Official E-BaaS TypeScript SDK - Backend as a Service client library
1,440 lines (1,430 loc) • 80 kB
JavaScript
import axios from 'axios';
class EBaaSError extends Error {
constructor(message, status = 0, details) {
super(message);
this.name = 'EBaaSError';
this.status = status;
this.details = details;
if (Error.captureStackTrace) {
Error.captureStackTrace(this, EBaaSError);
}
}
toJSON() {
return {
name: this.name,
message: this.message,
status: this.status,
details: this.details,
stack: this.stack
};
}
toString() {
return `${this.name}: ${this.message} (Status: ${this.status})`;
}
}
class HttpClient {
constructor(config) {
this.baseURL = config.url;
this.apiKey = config.apiKey;
this.client = axios.create({
baseURL: this.baseURL,
timeout: config.timeout || 10000,
headers: {
apikey: this.apiKey,
...config.headers,
},
});
this.setupInterceptors();
}
setupInterceptors() {
// Request interceptor
this.client.interceptors.request.use((config) => {
const isFormData = typeof FormData !== 'undefined' && config.data instanceof FormData;
const headers = config.headers ?? {};
if (isFormData) {
delete headers['Content-Type'];
delete headers['content-type'];
}
else if (config.data && typeof config.data === 'object' && !(config.data instanceof ArrayBuffer)) {
if (!('Content-Type' in headers) && !('content-type' in headers)) {
headers['Content-Type'] = 'application/json';
}
}
config.headers = headers;
return config;
}, (error) => {
return Promise.reject(new EBaaSError('Request failed', 0, error));
});
// Response interceptor
this.client.interceptors.response.use((response) => {
return response;
}, (error) => {
const status = error.response?.status || 0;
const message = error.response?.data?.message || error.message || 'Unknown error';
const details = error.response?.data || error;
return Promise.reject(new EBaaSError(message, status, details));
});
}
async get(url, config) {
try {
const response = await this.client.get(url, config);
return {
data: response.data,
status: response.status,
statusText: response.statusText,
headers: response.headers,
};
}
catch (error) {
throw this.handleError(error);
}
}
async post(url, data, config) {
try {
const response = await this.client.post(url, data, config);
return {
data: response.data,
status: response.status,
statusText: response.statusText,
headers: response.headers,
};
}
catch (error) {
throw this.handleError(error);
}
}
async put(url, data, config) {
try {
const response = await this.client.put(url, data, config);
return {
data: response.data,
status: response.status,
statusText: response.statusText,
headers: response.headers,
};
}
catch (error) {
throw this.handleError(error);
}
}
async patch(url, data, config) {
try {
const response = await this.client.patch(url, data, config);
return {
data: response.data,
status: response.status,
statusText: response.statusText,
headers: response.headers,
};
}
catch (error) {
throw this.handleError(error);
}
}
async delete(url, config) {
try {
const response = await this.client.delete(url, config);
return {
data: response.data,
status: response.status,
statusText: response.statusText,
headers: response.headers,
};
}
catch (error) {
throw this.handleError(error);
}
}
getBaseUrl() {
return this.baseURL;
}
setAuthToken(token) {
this.client.defaults.headers.common['Authorization'] = `Bearer ${token}`;
}
removeAuthToken() {
delete this.client.defaults.headers.common['Authorization'];
this.client.defaults.headers.common['Authorization'] = `Bearer ${this.apiKey}`;
}
handleError(error) {
if (error instanceof EBaaSError) {
return error;
}
return new EBaaSError(error.message || 'HTTP request failed', error.status || 0, error);
}
}
class AuthClient {
constructor(httpClient, workspaceId) {
this.currentSession = null;
this.httpClient = httpClient;
this.workspaceId = workspaceId;
this.loadSession();
}
async signUp(data) {
try {
const headers = {};
if (this.workspaceId) {
headers['x-workspace-id'] = this.workspaceId;
}
const payload = this.buildSignUpPayload(data);
const response = await this.httpClient.post('/auth/v1/signup', payload, {
headers
});
if (response.data.session) {
this.setSessionInternal(response.data.session);
}
// autoSignIn: if requested and no session returned by signUp, automatically sign in
if (data.autoSignIn && !response.data.session) {
try {
const signInResponse = await this.signIn({ email: data.email, password: data.password });
return signInResponse;
}
catch (_) {
// If auto sign-in fails, still return the original signUp response
}
}
return response.data;
}
catch (error) {
throw this.handleAuthError(error);
}
}
async signIn(data) {
try {
const headers = {};
// Incluir x-workspace-id se workspaceId foi fornecido
if (this.workspaceId) {
headers['x-workspace-id'] = this.workspaceId;
}
const response = await this.httpClient.post('/auth/v1/sdk/signin', data, {
headers
});
if (response.data.session) {
this.setSessionInternal(response.data.session);
}
return response.data;
}
catch (error) {
throw this.handleAuthError(error);
}
}
async signOut() {
try {
await this.httpClient.post('/auth/v1/signout');
this.clearSession();
}
catch (error) {
this.clearSession();
throw this.handleAuthError(error);
}
}
async getUser() {
if (!this.currentSession?.access_token) {
return null;
}
try {
const response = await this.httpClient.get('/auth/v1/user');
return response.data.user;
}
catch (error) {
if (error instanceof EBaaSError && error.status === 401) {
this.clearSession();
return null;
}
throw this.handleAuthError(error);
}
}
async refreshSession() {
if (!this.currentSession?.refresh_token) {
return null;
}
try {
const headers = {};
if (this.workspaceId) {
headers['x-workspace-id'] = this.workspaceId;
}
const response = await this.httpClient.post('/auth/v1/refresh', {
refresh_token: this.currentSession.refresh_token,
}, {
headers,
});
if (response.data.session) {
this.setSession(response.data.session);
return response.data.session;
}
return null;
}
catch (error) {
this.clearSession();
throw this.handleAuthError(error);
}
}
async resetPassword(email) {
try {
await this.httpClient.post('/auth/v1/reset-password', { email });
}
catch (error) {
throw this.handleAuthError(error);
}
}
async updateUser(data) {
try {
const response = await this.httpClient.put('/auth/v1/user', data);
return response.data.user;
}
catch (error) {
throw this.handleAuthError(error);
}
}
getSession() {
return this.currentSession;
}
setSession(session) {
this.currentSession = session;
this.httpClient.setAuthToken(session.access_token);
// Persist session
if (typeof localStorage !== 'undefined') {
localStorage.setItem('ebaas.auth.session', JSON.stringify(session));
}
}
onAuthStateChange(callback) {
// Implementation for auth state change listener
// This would integrate with the actual auth state management system
return () => { };
}
/**
* Atualiza o workspace ID (útil quando o usuário troca de workspace)
*/
setWorkspaceId(workspaceId) {
this.workspaceId = workspaceId;
}
buildSignUpPayload(data) {
const { metadata, ...rest } = data;
const payload = { ...rest };
if (metadata) {
payload.metadata = metadata;
}
const { firstName, lastName } = this.resolveNamesFromMetadata(rest.firstName, rest.lastName, metadata);
if (firstName && !payload.firstName) {
payload.firstName = firstName;
}
if (lastName && !payload.lastName) {
payload.lastName = lastName;
}
return payload;
}
resolveNamesFromMetadata(baseFirstName, baseLastName, metadata) {
const result = {};
if (baseFirstName) {
result.firstName = this.getStringValue(baseFirstName);
}
if (baseLastName) {
result.lastName = this.getStringValue(baseLastName);
}
const metadataFirstName = this.getStringValue(metadata?.['first_name'] ?? metadata?.firstName);
const metadataLastName = this.getStringValue(metadata?.['last_name'] ?? metadata?.lastName);
const metadataName = this.getStringValue(metadata?.name);
if (!result.firstName) {
result.firstName = metadataFirstName ?? (metadataName ? metadataName.split(/\s+/)[0] : undefined);
}
if (!result.lastName) {
if (metadataLastName) {
result.lastName = metadataLastName;
}
else if (metadataName) {
const parts = metadataName.split(/\s+/);
if (parts.length > 1) {
result.lastName = parts.slice(1).join(' ');
}
}
}
return result;
}
getStringValue(value) {
if (typeof value !== 'string') {
return undefined;
}
const trimmed = value.trim();
return trimmed.length > 0 ? trimmed : undefined;
}
setSessionInternal(session) {
this.currentSession = session;
this.httpClient.setAuthToken(session.access_token);
// Persist session
if (typeof localStorage !== 'undefined') {
localStorage.setItem('ebaas.auth.session', JSON.stringify(session));
}
}
clearSession() {
this.currentSession = null;
this.httpClient.removeAuthToken();
// Clear persisted session
if (typeof localStorage !== 'undefined') {
localStorage.removeItem('ebaas.auth.session');
}
}
loadSession() {
if (typeof localStorage !== 'undefined') {
const storedSession = localStorage.getItem('ebaas.auth.session');
if (storedSession) {
try {
const session = JSON.parse(storedSession);
this.setSessionInternal(session);
}
catch (error) {
this.clearSession();
}
}
}
}
handleAuthError(error) {
if (error instanceof EBaaSError) {
return error;
}
return new EBaaSError(error.message || 'Authentication failed', error.status || 0, error);
}
}
/**
* Returns session expiration as milliseconds timestamp.
* Handles both seconds and milliseconds formats from the API.
*/
function getExpiresAtMs(session) {
if (typeof session.expires_at === 'number' && session.expires_at < 1e12) {
return session.expires_at * 1000; // seconds -> ms
}
return Number(session.expires_at); // already ms
}
class QueryBuilder {
constructor(httpClient, table, schema, workspaceId, restUrl) {
this.httpClient = httpClient;
this.table = table;
this.schema = schema;
this.workspaceId = workspaceId;
this.restUrl = restUrl || '/sdk/v1';
this.query = {
filters: []
};
}
select(columns = '*', options) {
this.query.select = columns;
return this;
}
insert(data, options) {
this.query.insertData = data;
this.query.insertOptions = options;
return this;
}
update(data, options) {
this.query.updateData = data;
this.query.updateOptions = options;
return this;
}
upsert(data, options) {
return this.executeQuery('POST', `${this.restUrl}/${this.table}?operation=upsert`, {
data,
options: { ...options, upsert: true }
});
}
delete(options) {
this.query.deleteOptions = options;
this.query.isDelete = true;
return this;
}
// Filter methods
eq(column, value) {
this.query.filters.push({ column, operator: 'eq', value });
return this;
}
neq(column, value) {
this.query.filters.push({ column, operator: 'neq', value });
return this;
}
gt(column, value) {
this.query.filters.push({ column, operator: 'gt', value });
return this;
}
gte(column, value) {
this.query.filters.push({ column, operator: 'gte', value });
return this;
}
lt(column, value) {
this.query.filters.push({ column, operator: 'lt', value });
return this;
}
lte(column, value) {
this.query.filters.push({ column, operator: 'lte', value });
return this;
}
like(column, pattern) {
this.query.filters.push({ column, operator: 'like', value: pattern });
return this;
}
ilike(column, pattern) {
this.query.filters.push({ column, operator: 'ilike', value: pattern });
return this;
}
is(column, value) {
this.query.filters.push({ column, operator: 'is', value });
return this;
}
in(column, values) {
this.query.filters.push({ column, operator: 'in', value: values });
return this;
}
contains(column, value) {
this.query.filters.push({ column, operator: 'contains', value });
return this;
}
match(query) {
Object.entries(query).forEach(([column, value]) => {
this.eq(column, value);
});
return this;
}
// Ordering methods
order(column, options) {
if (!this.query.order) {
this.query.order = [];
}
this.query.order.push({
column,
ascending: options?.ascending !== false
});
return this;
}
// Limiting methods
limit(count) {
this.query.limit = count;
return this;
}
range(from, to) {
this.query.range = { from, to };
return this;
}
// Execution method for SELECT, UPDATE, or DELETE
async execute() {
// Detectar tipo de operação baseado nos dados do query
if (this.query.insertData) {
// INSERT operation
return this.executeQuery('POST', `${this.restUrl}/${this.table}?operation=insert`, {
data: this.query.insertData,
options: this.query.insertOptions
});
}
else if (this.query.updateData) {
// UPDATE operation
return this.executeQuery('PATCH', `${this.restUrl}/${this.table}`, {
data: this.query.updateData,
options: this.query.updateOptions,
filters: this.query.filters
});
}
else if (this.query.isDelete) {
// DELETE operation
return this.executeQuery('DELETE', `${this.restUrl}/${this.table}?operation=delete`, {
options: this.query.deleteOptions,
filters: this.query.filters
});
}
else {
// SELECT operation (default)
return this.executeQuery('GET', `${this.restUrl}/${this.table}?operation=select`, {
select: this.query.select,
filters: this.query.filters,
order: this.query.order,
limit: this.query.limit,
range: this.query.range
});
}
}
then(onfulfilled, onrejected) {
return this.execute().then(onfulfilled, onrejected);
}
// Single row execution
async single() {
this.limit(1);
const result = await this.execute();
return {
data: result.data?.[0] || null,
error: result.error,
};
}
async executeQuery(method, url, payload) {
try {
let response;
// Configurar headers obrigatórios
const headers = {};
if (this.workspaceId) {
headers['x-workspace-id'] = this.workspaceId;
}
if (method === 'GET') {
// Extrair parâmetros existentes da URL (como operation=select)
const urlParts = url.split('?');
const baseUrl = urlParts[0];
const existingParams = new URLSearchParams(urlParts[1] || '');
// Adicionar novos parâmetros de query
if (payload.select)
existingParams.append('select', payload.select);
// Converter filtros para formato PostgREST
if (payload.filters?.length) {
payload.filters.forEach((filter) => {
existingParams.append(filter.column, `${filter.operator}.${filter.value}`);
});
}
// Converter ordenação para formato PostgREST
if (payload.order?.length) {
const orderStr = payload.order.map((ord) => `${ord.column}${ord.ascending ? '.asc' : '.desc'}`).join(',');
existingParams.append('order', orderStr);
}
if (payload.limit)
existingParams.append('limit', payload.limit.toString());
if (payload.range) {
existingParams.append('offset', payload.range.from.toString());
existingParams.append('limit', (payload.range.to - payload.range.from + 1).toString());
}
response = await this.httpClient.get(`${baseUrl}?${existingParams.toString()}`, { headers });
}
else if (method === 'DELETE') {
// Para DELETE, os filtros devem estar na URL (query string), não no body
// Extrair parâmetros existentes da URL (como operation=delete)
const urlParts = url.split('?');
const baseUrl = urlParts[0];
const existingParams = new URLSearchParams(urlParts[1] || '');
// Adicionar parâmetro operation=delete se não estiver presente
if (!existingParams.has('operation')) {
existingParams.append('operation', 'delete');
}
if (payload.filters?.length) {
// Converter filtros para formato PostgREST
payload.filters.forEach((filter) => {
existingParams.append(filter.column, `${filter.operator}.${filter.value}`);
});
}
const deleteUrl = `${baseUrl}?${existingParams.toString()}`;
response = await this.httpClient.delete(deleteUrl, { headers });
}
else if (method === 'PATCH') {
// Para UPDATE (PATCH), os filtros também devem estar na URL
// Extrair parâmetros existentes da URL (como operation=update)
const urlParts = url.split('?');
const baseUrl = urlParts[0];
const existingParams = new URLSearchParams(urlParts[1] || '');
// Adicionar parâmetro operation=update se não estiver presente
if (!existingParams.has('operation')) {
existingParams.append('operation', 'update');
}
if (payload.filters?.length) {
// Converter filtros para formato PostgREST
payload.filters.forEach((filter) => {
existingParams.append(filter.column, `${filter.operator}.${filter.value}`);
});
}
// Adicionar headers Prefer se necessário
if (payload.options?.prefer) {
headers['Prefer'] = payload.options.prefer;
}
const updateUrl = `${baseUrl}?${existingParams.toString()}`;
response = await this.httpClient.patch(updateUrl, payload.data, { headers });
}
else {
// POST mantém o payload no body (para INSERT)
let postData = payload.data;
// Sempre retornar dados inseridos por padrão
if (!headers['Prefer']) {
headers['Prefer'] = 'return=representation';
}
// Se for upsert, adicionar header Prefer
if (payload.options?.upsert) {
headers['Prefer'] = 'resolution=merge-duplicates,return=representation';
}
// Se for inserção múltipla, usar dataArray
if (payload.dataArray) {
postData = payload.dataArray;
}
response = await this.httpClient.post(url, postData, { headers });
}
return {
data: response.data,
error: null,
count: response.headers?.['x-total-count'] ? parseInt(response.headers['x-total-count']) : undefined
};
}
catch (error) {
return {
data: null,
error: error instanceof EBaaSError ? error : new EBaaSError(error instanceof Error ? error.message : 'Database operation failed', 0, error)
};
}
}
}
class CustomDatabaseManager {
constructor(httpClient, workspaceId) {
this.httpClient = httpClient;
this.workspaceId = workspaceId;
}
/**
* Detecta automaticamente se o workspace usa banco customizado
*/
async detectCustomDatabase() {
if (!this.workspaceId) {
return { hasCustomDatabase: false };
}
try {
// Busca a configuração do workspace
const response = await this.httpClient.get('/admin/v1/workspace/custom-database', {
headers: {
'x-workspace-id': this.workspaceId,
},
});
if (response.status === 200 && response.data?.config) {
const config = response.data.config;
// Converte para formato de credenciais do SDK
const credentials = {
host: config.postgresHost,
port: config.postgresPort,
database: config.postgresDatabase,
username: config.postgresUsername,
password: '', // Senha não é retornada pela API por segurança
schema: config.postgresSchema,
sslEnabled: config.sslEnabled,
};
this.detectedConfig = {
hasCustomDatabase: true,
config,
credentials,
};
return this.detectedConfig;
}
}
catch (error) {
// Se erro 404, significa que não há configuração customizada
if (error?.status === 404) {
this.detectedConfig = { hasCustomDatabase: false };
return this.detectedConfig;
}
console.warn('Failed to detect custom database configuration:', error);
}
this.detectedConfig = { hasCustomDatabase: false };
return this.detectedConfig;
}
/**
* Testa conexão com banco customizado
*/
async testConnection(credentials) {
try {
const response = await this.httpClient.post('/admin/v1/workspace/custom-database/test-connection', credentials, {
headers: this.workspaceId ? { 'X-Workspace-ID': this.workspaceId } : {},
});
return response.data;
}
catch (error) {
return {
success: false,
message: error?.message || 'Failed to test connection',
};
}
}
/**
* Configura credenciais customizadas para o workspace
*/
async configureCustomDatabase(credentials) {
if (!this.workspaceId) {
throw new Error('Workspace ID is required to configure custom database');
}
const response = await this.httpClient.post('/admin/v1/workspace/custom-database', {
workspaceId: this.workspaceId,
...credentials,
}, {
headers: {
'X-Workspace-ID': this.workspaceId,
},
});
const config = response.data.config;
// Atualiza a configuração detectada
this.detectedConfig = {
hasCustomDatabase: true,
config,
credentials,
};
return config;
}
/**
* Remove configuração de banco customizado
*/
async removeCustomDatabase() {
if (!this.workspaceId) {
throw new Error('Workspace ID is required to remove custom database');
}
await this.httpClient.delete('/admin/v1/workspace/custom-database', {
headers: {
'X-Workspace-ID': this.workspaceId,
},
});
// Limpa a configuração detectada
this.detectedConfig = { hasCustomDatabase: false };
}
/**
* Retorna a configuração detectada em cache
*/
getDetectedConfig() {
return this.detectedConfig;
}
/**
* Verifica se tem configuração customizada em cache
*/
hasCustomDatabase() {
return this.detectedConfig?.hasCustomDatabase ?? false;
}
/**
* Retorna as credenciais customizadas se disponíveis
*/
getCustomCredentials() {
return this.detectedConfig?.credentials;
}
/**
* Atualiza o workspace ID
*/
setWorkspaceId(workspaceId) {
this.workspaceId = workspaceId;
// Limpa configuração anterior quando workspace muda
this.detectedConfig = undefined;
}
/**
* Retorna o workspace ID atual
*/
getWorkspaceId() {
return this.workspaceId;
}
/**
* Retorna informações sobre o status do banco customizado
*/
getStatus() {
return {
workspaceId: this.workspaceId,
hasConfig: this.hasCustomDatabase(),
configDetails: this.detectedConfig?.credentials ? {
host: this.detectedConfig.credentials.host,
port: this.detectedConfig.credentials.port,
database: this.detectedConfig.credentials.database,
schema: this.detectedConfig.credentials.schema || 'public',
sslEnabled: this.detectedConfig.credentials.sslEnabled || false,
} : undefined,
};
}
}
class DatabaseClient {
constructor(httpClient, workspaceId, restUrl) {
this.httpClient = httpClient;
this.customDatabaseManager = new CustomDatabaseManager(httpClient, workspaceId);
this.restUrl = restUrl || '/sdk/v1';
}
from(table) {
return new QueryBuilder(this.httpClient, table, this.currentSchema, this.customDatabaseManager.getWorkspaceId(), this.restUrl);
}
rpc(fn, args) {
return this.httpClient.post(`/rpc/${fn}`, args);
}
schema(schema) {
const client = new DatabaseClient(this.httpClient, this.customDatabaseManager.getWorkspaceId(), this.restUrl);
client.currentSchema = schema;
client.customDatabaseManager = this.customDatabaseManager;
return client;
}
// Métodos para gerenciar banco customizado
/**
* Detecta automaticamente se o workspace usa banco customizado
*/
async detectCustomDatabase() {
return this.customDatabaseManager.detectCustomDatabase();
}
/**
* Testa conexão com credenciais customizadas
*/
async testCustomConnection(credentials) {
return this.customDatabaseManager.testConnection(credentials);
}
/**
* Configura banco customizado para o workspace
*/
async configureCustomDatabase(credentials) {
return this.customDatabaseManager.configureCustomDatabase(credentials);
}
/**
* Remove configuração de banco customizado
*/
async removeCustomDatabase() {
return this.customDatabaseManager.removeCustomDatabase();
}
/**
* Verifica se o workspace está usando banco customizado
*/
isUsingCustomDatabase() {
return this.customDatabaseManager.hasCustomDatabase();
}
/**
* Retorna informações sobre o banco customizado
*/
getCustomDatabaseStatus() {
return this.customDatabaseManager.getStatus();
}
/**
* Atualiza o workspace ID (útil quando o usuário troca de workspace)
*/
setWorkspaceId(workspaceId) {
this.customDatabaseManager.setWorkspaceId(workspaceId);
}
/**
* Inicializa a detecção automática do banco customizado
* Deve ser chamado após a autenticação se autoDetectCustomDatabase estiver habilitado
*/
async initialize() {
try {
await this.customDatabaseManager.detectCustomDatabase();
}
catch (error) {
console.warn('Failed to initialize custom database detection:', error);
}
}
}
const STORAGE_BASE_PATH$1 = '/storage/v1';
class StorageBucket {
constructor(httpClient, bucketId, workspaceId) {
this.httpClient = httpClient;
this.bucketId = bucketId;
this.workspaceId = workspaceId;
}
setWorkspaceId(workspaceId) {
this.workspaceId = workspaceId;
}
getWorkspaceHeaders() {
if (!this.workspaceId) {
throw new EBaaSError('Workspace ID is required for storage operations', 400);
}
return {
'x-workspace-id': this.workspaceId,
};
}
normalizePath(path) {
return path
.trim()
.replace(/^\/+/, '')
.replace(/\/+$/, '')
.replace(/\/+/g, '/');
}
encodePath(path) {
const normalized = this.normalizePath(path);
return normalized
.split('/')
.filter(Boolean)
.map((segment) => encodeURIComponent(segment))
.join('/');
}
extractPathParts(path) {
const normalized = this.normalizePath(path);
if (!normalized) {
throw new EBaaSError('Path must include the file name', 400);
}
const segments = normalized.split('/').filter(Boolean);
if (segments.length === 0) {
throw new EBaaSError('Path must include the file name', 400);
}
const fileName = segments.pop();
const directory = segments.length ? segments.join('/') : undefined;
return { fileName, directory };
}
serializeTransform(transform) {
if (!transform) {
return undefined;
}
const parts = Object.entries(transform)
.filter(([, value]) => value !== undefined && value !== null)
.map(([key, value]) => `${key}=${value}`);
return parts.length ? parts.join(',') : undefined;
}
appendFileToFormData(formData, file, fileName, contentType) {
if (file instanceof File) {
if (file.name !== fileName) {
formData.append('file', file, fileName);
}
else {
formData.append('file', file);
}
return;
}
if (file instanceof Blob) {
formData.append('file', file, fileName);
return;
}
if (file instanceof ArrayBuffer) {
formData.append('file', new Blob([file], { type: contentType ?? 'application/octet-stream' }), fileName);
return;
}
if (typeof file === 'string') {
formData.append('file', new Blob([file], { type: contentType ?? 'text/plain' }), fileName);
return;
}
throw new EBaaSError('Unsupported file type for upload', 400);
}
async upload(path, file, options) {
try {
const headers = this.getWorkspaceHeaders();
const normalizedPath = this.normalizePath(path);
const { fileName, directory } = this.extractPathParts(normalizedPath);
const formData = new FormData();
this.appendFileToFormData(formData, file, fileName, options?.contentType);
if (directory) {
formData.append('path', directory);
}
if (options?.cacheControl) {
formData.append('cacheControl', options.cacheControl);
}
if (options?.metadata) {
formData.append('metadata', JSON.stringify(options.metadata));
}
if (options?.contentType) {
formData.append('contentType', options.contentType);
}
if (options?.expiresIn) {
formData.append('expiresIn', String(options.expiresIn));
}
if (options?.upsert) {
formData.append('overwrite', 'true');
}
const encodedPath = this.encodePath(normalizedPath);
const response = await this.httpClient.post(`${STORAGE_BASE_PATH$1}/object/${this.bucketId}/${encodedPath}`, formData, {
headers,
});
return {
data: response.data,
error: null,
};
}
catch (error) {
return {
data: null,
error: error instanceof EBaaSError
? error
: new EBaaSError(error instanceof Error ? error.message : 'Failed to upload file', 0, error),
};
}
}
async download(path, options) {
try {
const headers = this.getWorkspaceHeaders();
const normalizedPath = this.normalizePath(path);
const encodedPath = this.encodePath(normalizedPath);
const transformString = this.serializeTransform(options?.transform);
const query = transformString ? `?transform=${encodeURIComponent(transformString)}` : '';
const response = await this.httpClient.get(`${STORAGE_BASE_PATH$1}/object/authenticated/${this.bucketId}/${encodedPath}${query}`, {
responseType: 'blob',
headers,
});
return {
data: response.data,
error: null,
};
}
catch (error) {
return {
data: null,
error: error instanceof EBaaSError
? error
: new EBaaSError(error instanceof Error ? error.message : 'Failed to download file', 0, error),
};
}
}
async list(prefix, options) {
try {
const headers = this.getWorkspaceHeaders();
const params = new URLSearchParams();
if (prefix) {
const normalizedPrefix = prefix.replace(/^\/+/, '');
if (normalizedPrefix) {
params.append('prefix', normalizedPrefix);
}
}
if (options?.limit) {
params.append('limit', String(options.limit));
}
if (options?.offset) {
params.append('offset', options.offset);
}
if (options?.search) {
params.append('search', options.search);
}
if (options?.sortBy && options.sortBy.length > 0) {
const sortValue = options.sortBy
.map((item) => {
const order = item.order ?? 'asc';
return `${item.column}:${order}`;
})
.join(',');
params.append('sortBy', sortValue);
}
const query = params.toString();
const response = await this.httpClient.get(`${STORAGE_BASE_PATH$1}/object/list/${this.bucketId}${query ? `?${query}` : ''}`, {
headers,
});
return {
data: response.data,
error: null,
};
}
catch (error) {
return {
data: null,
error: error instanceof EBaaSError
? error
: new EBaaSError(error instanceof Error ? error.message : 'Failed to list files', 0, error),
};
}
}
async remove(paths) {
try {
const headers = this.getWorkspaceHeaders();
for (const path of paths) {
const normalizedPath = this.normalizePath(path);
const encodedPath = this.encodePath(normalizedPath);
await this.httpClient.delete(`${STORAGE_BASE_PATH$1}/object/${this.bucketId}/${encodedPath}`, {
headers,
});
}
return {
data: { message: 'Files removed successfully' },
error: null,
};
}
catch (error) {
return {
data: null,
error: error instanceof EBaaSError
? error
: new EBaaSError(error instanceof Error ? error.message : 'Failed to remove files', 0, error),
};
}
}
async move(_fromPath, _toPath) {
return {
data: null,
error: new EBaaSError('Move operation is not supported by the current E-BaaS backend', 501),
};
}
async copy(_fromPath, _toPath) {
return {
data: null,
error: new EBaaSError('Copy operation is not supported by the current E-BaaS backend', 501),
};
}
async createSignedUrl(path, expiresIn, options) {
try {
const headers = this.getWorkspaceHeaders();
const normalizedPath = this.normalizePath(path);
const response = await this.httpClient.post(`${STORAGE_BASE_PATH$1}/object/sign`, {
bucketName: this.bucketId,
filePath: normalizedPath,
operation: options?.operation ?? 'download',
expiresIn: options?.expiresIn ?? expiresIn,
}, {
headers,
});
return {
data: response.data,
error: null,
};
}
catch (error) {
return {
data: null,
error: error instanceof EBaaSError
? error
: new EBaaSError(error instanceof Error ? error.message : 'Failed to create signed URL', 0, error),
};
}
}
getPublicUrl(path, options) {
const baseUrl = this.httpClient.baseURL;
if (!baseUrl) {
throw new EBaaSError('HTTP client base URL is not configured', 400);
}
if (!this.workspaceId) {
throw new EBaaSError('Workspace ID is required to generate public URLs', 400);
}
const normalizedPath = this.normalizePath(path);
const encodedPath = this.encodePath(normalizedPath);
const params = new URLSearchParams();
params.append('workspaceId', this.workspaceId);
const transformString = this.serializeTransform(options?.transform);
if (transformString) {
params.append('transform', transformString);
}
const publicUrl = `${baseUrl.replace(/\/+$/, '')}${STORAGE_BASE_PATH$1}/object/public/${this.bucketId}/${encodedPath}`;
const query = params.toString();
return {
data: {
publicUrl: query ? `${publicUrl}?${query}` : publicUrl,
},
};
}
async getFileInfo(_path) {
return {
data: null,
error: new EBaaSError('File info retrieval is not supported by the current E-BaaS backend', 501),
};
}
async updateFileMetadata(_path, _metadata) {
return {
data: null,
error: new EBaaSError('Updating file metadata is not supported by the current E-BaaS backend', 501),
};
}
}
const STORAGE_BASE_PATH = '/storage/v1';
class StorageClient {
constructor(httpClient, workspaceId) {
this.httpClient = httpClient;
this.workspaceId = workspaceId;
}
from(bucketId) {
return new StorageBucket(this.httpClient, bucketId, this.workspaceId);
}
setWorkspaceId(workspaceId) {
this.workspaceId = workspaceId;
}
getWorkspaceHeaders() {
if (!this.workspaceId) {
throw new EBaaSError('Workspace ID is required for storage operations', 400);
}
return {
'x-workspace-id': this.workspaceId,
};
}
mapBucketOptions(options) {
if (!options) {
return {};
}
const visibility = options.visibility ?? (options.public ? 'public' : undefined);
const maxFileSize = options.maxFileSize ?? options.fileSizeLimit;
const enableVersioning = options.enableVersioning ?? options.versioningEnabled;
return {
visibility,
description: options.description,
allowedMimeTypes: options.allowedMimeTypes,
maxFileSize,
enableVersioning,
enableCors: options.enableCors,
corsOrigins: options.corsOrigins,
};
}
async createBucket(id, options) {
try {
const payload = {
name: id,
...this.mapBucketOptions(options),
};
const response = await this.httpClient.post(`${STORAGE_BASE_PATH}/buckets`, payload, {
headers: this.getWorkspaceHeaders(),
});
return {
data: response.data,
error: null
};
}
catch (error) {
return {
data: null,
error: error instanceof EBaaSError ? error : new EBaaSError(error instanceof Error ? error.message : 'Failed to create bucket', 0, error)
};
}
}
async getBucket(id) {
try {
const response = await this.httpClient.get(`${STORAGE_BASE_PATH}/buckets/${id}`, {
headers: this.getWorkspaceHeaders(),
});
return {
data: response.data,
error: null
};
}
catch (error) {
return {
data: null,
error: error instanceof EBaaSError ? error : new EBaaSError(error instanceof Error ? error.message : 'Failed to get bucket', 0, error)
};
}
}
async listBuckets() {
try {
const response = await this.httpClient.get(`${STORAGE_BASE_PATH}/buckets`, {
headers: this.getWorkspaceHeaders(),
});
return {
data: response.data,
error: null
};
}
catch (error) {
return {
data: null,
error: error instanceof EBaaSError ? error : new EBaaSError(error instanceof Error ? error.message : 'Failed to list buckets', 0, error)
};
}
}
async updateBucket(id, options) {
try {
const payload = this.mapBucketOptions(options);
const response = await this.httpClient.put(`${STORAGE_BASE_PATH}/buckets/${id}`, payload, {
headers: this.getWorkspaceHeaders(),
});
return {
data: response.data,
error: null
};
}
catch (error) {
return {
data: null,
error: error instanceof EBaaSError ? error : new EBaaSError(error instanceof Error ? error.message : 'Failed to update bucket', 0, error)
};
}
}
async deleteBucket(id) {
try {
const response = await this.httpClient.delete(`${STORAGE_BASE_PATH}/buckets/${id}`, {
headers: this.getWorkspaceHeaders(),
});
return {
data: response.data,
error: null
};
}
catch (error) {
return {
data: null,
error: error instanceof EBaaSError ? error : new EBaaSError(error instanceof Error ? error.message : 'Failed to delete bucket', 0, error)
};
}
}
async getUsage() {
try {
const response = await this.httpClient.get(`${STORAGE_BASE_PATH}/usage`, {
headers: this.getWorkspaceHeaders(),
});
return {
data: response.data,
error: null
};
}
catch (error) {
return {
data: null,
error: error instanceof EBaaSError ? error : new EBaaSError(error instanceof Error ? error.message : 'Failed to get storage usage', 0, error)
};
}
}
}
class EventManager {
constructor() {
this.events = new Map();
this.maxListeners = 10;
}
on(event, callback) {
if (!this.events.has(event)) {
this.events.set(event, new Set());
}
this.events.get(event).add(callback);
}
off(event, callback) {
if (!this.events.has(event))
return;
const eventCallbacks = this.events.get(event);
if (callback) {
eventCallbacks.delete(callback);
}
else {
eventCallbacks.clear();
}
if (eventCallbacks.size === 0) {
this.events.delete(event);
}
}
emit(event, ...args) {
if (!this.events.has(event))
return false;
const eventCallbacks = this.events.get(event);
for (const callback of eventCallbacks) {
try {
callback(...args);
}
catch (error) {
console.error(`Error in event listener for "${event}":`, error);
}
}
return eventCallbacks.size > 0;
}
removeAllListeners(event) {
if (event) {
this.events.delete(event);
}
else {
this.events.clear();
}
}
listenerCount(event) {
return this.events.get(event)?.size ?? 0;
}
eventNames() {
return Array.from(this.events.keys());
}
listeners(event) {
return Array.from(this.events.get(event) ?? []);
}
addListener(event, callback) {
this.on(event, callback);
}
removeListener(event, callback) {
this.off(event, callback);
}
once(event, callback) {
const onceWrapper = (...args) => {
this.off(event, onceWrapper);
callback(...args);
};
this.on(event, onceWrapper);
}
prependListener(event, callback) {
this.on(event, callback);
}
prependOnceListener(event, callback) {
const onceWrapper = (...args) => {
this.off(event, onceWrapper);
callback(...args);
};
this.on(event, onceWrapper);
}
setMaxListeners(max) {
this.maxListeners = max;
return this;
}
getMaxListeners() {
return this.maxListeners;
}
rawListeners(event) {
return this.listeners(event);
}
}
class RealtimeChannel extends EventManager {
constructor(topic, socket) {
super();
this.joinedOnce = false;
this.state = 'closed';
this.subscriptions = new Map();
this.topic = topic;
this.socket = socket;
}
subscribe(callback) {
if (!this.joinedOnce) {
this.joinedOnce = true;
this._join();
}
if (callback) {
this.on('status', cal