pubflow
Version:
Framework agnostic API client for PubFlow services
429 lines (416 loc) • 12.5 kB
JavaScript
'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