UNPKG

pubflow

Version:

Framework agnostic API client for PubFlow services

429 lines (416 loc) 12.5 kB
'use strict'; /** * Detects the current JavaScript runtime environment * @returns The identified runtime type */ function detectRuntime() { // Check for Bun runtime if (typeof process !== 'undefined' && typeof process.versions !== 'undefined' && process.versions.bun) { return 'bun'; } // Check for Node.js runtime if (typeof process !== 'undefined' && typeof process.versions !== 'undefined' && process.versions.node) { return 'node'; } // Check for Cloudflare Workers runtime if (typeof globalThis !== 'undefined' && typeof globalThis.Deno === 'undefined' && typeof globalThis.document === 'undefined' && typeof globalThis.navigator === 'undefined' && typeof globalThis.caches !== 'undefined' && typeof globalThis.fetch === 'function') { return 'cloudflare'; } // Default to browser runtime return 'browser'; } /** * In-memory storage implementation for Node.js */ class NodeStorage { constructor() { this.storage = new Map(); } getItem(key) { return this.storage.get(key) || null; } setItem(key, value) { this.storage.set(key, value); } removeItem(key) { this.storage.delete(key); } } /** * Node.js runtime adapter implementation */ class NodeAdapter { constructor() { this.type = 'node'; this.storage = new NodeStorage(); } async fetch(url, options) { // Use native fetch in Node.js 18+ or fallback to node-fetch if (typeof global.fetch === 'function') { return global.fetch(url, options); } // For older Node.js versions, we would need to use a polyfill // This would typically be handled by a bundler or dependency throw new Error('Fetch API is not available in this Node.js environment. Please use Node.js 18+ or install a fetch polyfill.'); } getStorage() { return this.storage; } supportsFeature(feature) { switch (feature) { case 'fetch': return typeof global.fetch === 'function'; case 'localStorage': return false; // Node.js doesn't have localStorage default: return false; } } } /** * In-memory storage implementation for Bun */ class BunStorage { constructor() { this.storage = new Map(); } getItem(key) { return this.storage.get(key) || null; } setItem(key, value) { this.storage.set(key, value); } removeItem(key) { this.storage.delete(key); } } /** * Bun runtime adapter implementation */ class BunAdapter { constructor() { this.type = 'bun'; this.storage = new BunStorage(); } async fetch(url, options) { // Bun has native fetch implementation return fetch(url, options); } getStorage() { return this.storage; } supportsFeature(feature) { switch (feature) { case 'fetch': return true; // Bun has native fetch case 'localStorage': return false; // Bun doesn't have localStorage in non-browser environments default: return false; } } } /** * KV-based storage implementation for Cloudflare Workers * Note: This is a simplified implementation. In a real Cloudflare Workers environment, * you would use KV namespaces which need to be bound to the worker. */ class CloudflareStorage { constructor() { this.storage = new Map(); } async getItem(key) { // In a real implementation, this would use KV.get(key) return this.storage.get(key) || null; } async setItem(key, value) { // In a real implementation, this would use KV.put(key, value) this.storage.set(key, value); } async removeItem(key) { // In a real implementation, this would use KV.delete(key) this.storage.delete(key); } } /** * Cloudflare Workers runtime adapter implementation */ class CloudflareAdapter { constructor() { this.type = 'cloudflare'; this.storage = new CloudflareStorage(); } async fetch(url, options) { // Cloudflare Workers have native fetch implementation return fetch(url, options); } getStorage() { return this.storage; } supportsFeature(feature) { switch (feature) { case 'fetch': return true; // Cloudflare Workers have native fetch case 'localStorage': return false; // Cloudflare Workers don't have localStorage case 'cache': return typeof caches !== 'undefined'; // Check for Cache API default: return false; } } } /** * LocalStorage-based storage implementation for browsers */ class BrowserStorage { getItem(key) { if (typeof localStorage !== 'undefined') { return localStorage.getItem(key); } return null; } setItem(key, value) { if (typeof localStorage !== 'undefined') { localStorage.setItem(key, value); } } removeItem(key) { if (typeof localStorage !== 'undefined') { localStorage.removeItem(key); } } } /** * Browser runtime adapter implementation */ class BrowserAdapter { constructor() { this.type = 'browser'; this.storage = new BrowserStorage(); } async fetch(url, options) { return fetch(url, options); } getStorage() { return this.storage; } supportsFeature(feature) { switch (feature) { case 'fetch': return typeof fetch === 'function'; case 'localStorage': return typeof localStorage !== 'undefined'; case 'sessionStorage': return typeof sessionStorage !== 'undefined'; default: return false; } } } // src/runtime/index.ts // Default to browser runtime if detection fails let currentRuntime = new BrowserAdapter(); // Detect and set the appropriate runtime adapter const runtime = detectRuntime(); switch (runtime) { case 'node': currentRuntime = new NodeAdapter(); break; case 'bun': currentRuntime = new BunAdapter(); break; case 'cloudflare': currentRuntime = new CloudflareAdapter(); break; case 'browser': default: currentRuntime = new BrowserAdapter(); break; } // src/core/errors.ts class PubFlowError extends Error { constructor(code, message, status, details) { super(message); this.code = code; this.status = status; this.details = details; this.name = 'PubFlowError'; } } class AuthenticationError extends PubFlowError { constructor(message, details) { super('AUTH_ERROR', message, 401, details); } } class ValidationError extends PubFlowError { constructor(message, details) { super('VALIDATION_ERROR', message, 400, details); } } // src/core/http.ts class HttpClient { constructor(baseUrl, defaultHeaders = {}) { this.baseUrl = baseUrl; this.defaultHeaders = defaultHeaders; } async request(endpoint, options = {}) { const { method = 'GET', headers = {}, body, params, timeout } = options; let url = `${this.baseUrl}${endpoint}`; if (params) { const searchParams = new URLSearchParams(); Object.entries(params).forEach(([key, value]) => { if (Array.isArray(value)) { value.forEach(v => searchParams.append(`${key}[]`, v)); } else if (value !== undefined && value !== null) { searchParams.append(key, String(value)); } }); url += `?${searchParams.toString()}`; } // Use the runtime adapter for fetch operations const fetchOptions = { method, headers: { 'Content-Type': 'application/json', ...this.defaultHeaders, ...headers }, body: body ? JSON.stringify(body) : undefined, credentials: 'include', timeout }; const response = await currentRuntime.fetch(url, fetchOptions); const data = await response.json(); if (!response.ok) { throw new PubFlowError('REQUEST_ERROR', data.error || 'Request failed', response.status, data.details); } return data; } } class AuthService { constructor(http, storage) { this.http = http; this.storage = storage; } async login(credentials) { const response = await this.http.request('/auth/login', { method: 'POST', body: credentials }); if (response.success && response.data) { await this.storage.setItem('pubflow_session', JSON.stringify(response.data)); return response.data; } throw new AuthenticationError('Login failed'); } async logout() { await this.http.request('/auth/logout', { method: 'POST' }); await this.storage.removeItem('pubflow_session'); } async getSession() { const stored = await this.storage.getItem('pubflow_session'); return stored ? JSON.parse(stored) : null; } async validateSession(sessionId) { try { const response = await this.http.request('/auth/validation', { method: 'POST', body: sessionId ? { sessionId } : undefined }); return response.success && response.data ? response.data : null; } catch (_a) { return null; } } async hasUserType(userTypes) { const session = await this.getSession(); if (!session) return false; const types = Array.isArray(userTypes) ? userTypes : [userTypes]; return types.includes(session.user.userType); } } class BridgeService { constructor(http) { this.http = http; } async query(resource, params) { return this.http.request(`/bridge/${resource}`, { params }); } async search(resource, query, params) { return this.http.request(`/bridge/${resource}/search`, { params: { ...params, q: query } }); } async create(resource, data) { return this.http.request(`/bridge/${resource}`, { method: 'POST', body: data }); } async update(resource, id, data) { return this.http.request(`/bridge/${resource}/${id}`, { method: 'PUT', body: data }); } async delete(resource, id) { return this.http.request(`/bridge/${resource}/${id}`, { method: 'DELETE' }); } } // src/index.ts // Adapter to bridge between our StorageAdapter interface and the runtime storage class RuntimeStorageAdapter { constructor(customStorage) { this.customStorage = customStorage; } getItem(key) { if (this.customStorage) { try { return this.customStorage.getItem(key); } catch (_a) { return null; } } return currentRuntime.getStorage().getItem(key); } setItem(key, value) { if (this.customStorage) { this.customStorage.setItem(key, value); return; } return currentRuntime.getStorage().setItem(key, value); } removeItem(key) { if (this.customStorage) { this.customStorage.removeItem(key); return; } return currentRuntime.getStorage().removeItem(key); } } class PubFlow { constructor(config) { this.runtime = currentRuntime.type; this.storage = new RuntimeStorageAdapter(config.storage); this.http = new HttpClient(config.baseUrl, config.defaultHeaders); this.auth = new AuthService(this.http, this.storage); this.bridge = new BridgeService(this.http); } } exports.AuthenticationError = AuthenticationError; exports.PubFlow = PubFlow; exports.PubFlowError = PubFlowError; exports.ValidationError = ValidationError; //# sourceMappingURL=index.js.map