resolvo-cms
Version:
Headless CMS for Resolvo websites with real-time content management
3,171 lines • 114 kB
JavaScript
import axios from 'axios';
import { io } from 'socket.io-client';
import { z } from 'zod';
import require$$0, { useState, useRef, useEffect, useCallback, Component } from 'react';
class ContentCache {
constructor(cleanupIntervalMs = 60000) {
this.cleanupIntervalMs = cleanupIntervalMs;
this.cache = new Map();
this.cleanupInterval = null;
this.defaultTTL = 5 * 60 * 1000; // 5 minutes
this.maxSize = 1000; // Prevent memory leaks
this.startCleanupInterval();
}
get(key) {
const item = this.cache.get(key);
if (!item)
return null;
if (this.isExpired(item)) {
this.cache.delete(key);
return null;
}
return item.data;
}
set(key, data, ttl = this.defaultTTL) {
// Implement LRU-like behavior when cache is full
if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
this.evictOldest();
}
this.cache.set(key, {
data,
timestamp: Date.now(),
ttl
});
}
delete(key) {
return this.cache.delete(key);
}
clear() {
this.cache.clear();
}
has(key) {
const item = this.cache.get(key);
if (!item)
return false;
if (this.isExpired(item)) {
this.cache.delete(key);
return false;
}
return true;
}
size() {
return this.cache.size;
}
isExpired(item) {
return Date.now() - item.timestamp > item.ttl;
}
evictOldest() {
let oldestKey = null;
let oldestTime = Date.now();
for (const [key, item] of this.cache.entries()) {
if (item.timestamp < oldestTime) {
oldestTime = item.timestamp;
oldestKey = key;
}
}
if (oldestKey) {
this.cache.delete(oldestKey);
}
}
startCleanupInterval() {
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval);
}
this.cleanupInterval = setInterval(() => {
this.cleanup();
}, this.cleanupIntervalMs);
}
cleanup() {
for (const [key, item] of this.cache.entries()) {
if (this.isExpired(item)) {
this.cache.delete(key);
}
}
}
destroy() {
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval);
this.cleanupInterval = null;
}
this.cache.clear();
}
}
class SchemaCache extends ContentCache {
constructor() {
super(...arguments);
this.fieldCache = new Map();
}
getField(schemaId, fieldName) {
const schemaFields = this.fieldCache.get(schemaId);
if (!schemaFields)
return null;
return schemaFields.get(fieldName) || null;
}
setField(schemaId, fieldName, data) {
if (!this.fieldCache.has(schemaId)) {
this.fieldCache.set(schemaId, new Map());
}
this.fieldCache.get(schemaId).set(fieldName, data);
}
clearSchema(schemaId) {
this.fieldCache.delete(schemaId);
}
}
class LRUCache {
constructor(capacity = 100) {
this.cache = new Map();
this.capacity = capacity;
}
get(key) {
const item = this.cache.get(key);
if (!item)
return null;
// Update timestamp for LRU
this.cache.delete(key);
this.cache.set(key, { value: item.value, timestamp: Date.now() });
return item.value;
}
set(key, value) {
if (this.cache.has(key)) {
this.cache.delete(key);
}
else if (this.cache.size >= this.capacity) {
// Remove oldest item
const oldestKey = this.cache.keys().next().value;
if (oldestKey) {
this.cache.delete(oldestKey);
}
}
this.cache.set(key, { value, timestamp: Date.now() });
}
delete(key) {
return this.cache.delete(key);
}
clear() {
this.cache.clear();
}
size() {
return this.cache.size;
}
}
class ValidationError extends Error {
constructor(message, field, value, rule) {
super(message);
this.field = field;
this.value = value;
this.rule = rule;
this.name = 'ValidationError';
}
}
function validateField(field, value) {
const errors = [];
if (!field.validation) {
return { isValid: true, errors: [] };
}
for (const rule of field.validation) {
const error = validateRule(field, value, rule);
if (error) {
errors.push(error);
}
}
return {
isValid: errors.length === 0,
errors
};
}
function validateRule(field, value, rule) {
switch (rule.type) {
case 'required':
if (value === null || value === undefined || value === '') {
return new ValidationError(rule.message || `${field.label} is required`, field.name, value, rule);
}
break;
case 'min':
if (field.type === 'number' && typeof value === 'number') {
if (value < rule.value) {
return new ValidationError(rule.message || `${field.label} must be at least ${rule.value}`, field.name, value, rule);
}
}
else if (typeof value === 'string') {
if (value.length < rule.value) {
return new ValidationError(rule.message || `${field.label} must be at least ${rule.value} characters`, field.name, value, rule);
}
}
break;
case 'max':
if (field.type === 'number' && typeof value === 'number') {
if (value > rule.value) {
return new ValidationError(rule.message || `${field.label} must be at most ${rule.value}`, field.name, value, rule);
}
}
else if (typeof value === 'string') {
if (value.length > rule.value) {
return new ValidationError(rule.message || `${field.label} must be at most ${rule.value} characters`, field.name, value, rule);
}
}
break;
case 'pattern':
if (typeof value === 'string' && rule.value) {
const regex = new RegExp(rule.value);
if (!regex.test(value)) {
return new ValidationError(rule.message || `${field.label} format is invalid`, field.name, value, rule);
}
}
break;
case 'email':
if (typeof value === 'string' && value) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(value)) {
return new ValidationError(rule.message || `${field.label} must be a valid email address`, field.name, value, rule);
}
}
break;
case 'url':
if (typeof value === 'string' && value) {
try {
new URL(value);
}
catch {
return new ValidationError(rule.message || `${field.label} must be a valid URL`, field.name, value, rule);
}
}
break;
}
return null;
}
function validateContent(schema, data) {
const errors = [];
// Validate that all required fields are present
for (const field of schema.fields) {
const value = data[field.name];
// Check if field is required but missing
const isRequired = field.validation?.some(rule => rule.type === 'required');
if (isRequired && (value === undefined || value === null)) {
errors.push(new ValidationError(`${field.label || field.name} is required`, field.name, value, { type: 'required', message: `${field.label || field.name} is required` }));
continue;
}
// Skip validation for undefined/null values unless required
if (value === undefined || value === null) {
continue;
}
const fieldValidation = validateField(field, value);
if (!fieldValidation.isValid) {
errors.push(...fieldValidation.errors);
}
}
return {
isValid: errors.length === 0,
errors
};
}
function createZodSchema(fields) {
const schemaObject = {};
for (const field of fields) {
let fieldSchema;
switch (field.type) {
case 'text':
case 'textarea':
case 'url':
fieldSchema = z.string();
break;
case 'number':
fieldSchema = z.number();
break;
case 'boolean':
fieldSchema = z.boolean();
break;
case 'date':
fieldSchema = z.string().datetime();
break;
case 'datetime':
fieldSchema = z.string().datetime();
break;
case 'color':
fieldSchema = z.string().regex(/^#[0-9A-F]{6}$/i);
break;
case 'array':
fieldSchema = z.array(z.any());
break;
case 'object':
fieldSchema = z.record(z.any());
break;
default:
fieldSchema = z.any();
}
// Apply validation rules
if (field.validation) {
for (const rule of field.validation) {
switch (rule.type) {
case 'required':
fieldSchema = fieldSchema;
break;
case 'min':
if (field.type === 'number') {
fieldSchema = fieldSchema.min(rule.value);
}
else {
fieldSchema = fieldSchema.min(rule.value);
}
break;
case 'max':
if (field.type === 'number') {
fieldSchema = fieldSchema.max(rule.value);
}
else {
fieldSchema = fieldSchema.max(rule.value);
}
break;
case 'email':
fieldSchema = fieldSchema.email();
break;
case 'url':
fieldSchema = fieldSchema.url();
break;
}
}
}
else if (!field.required) {
fieldSchema = fieldSchema.optional();
}
schemaObject[field.name] = fieldSchema;
}
return z.object(schemaObject);
}
function serializeContent(data, fields) {
const serialized = {};
for (const field of fields) {
const value = data[field.name];
serialized[field.name] = serializeField(value, field.type);
}
return serialized;
}
function deserializeContent(data, fields) {
const deserialized = {};
for (const field of fields) {
const value = data[field.name];
deserialized[field.name] = deserializeField(value, field.type);
}
return deserialized;
}
function serializeField(value, type) {
if (value === null || value === undefined) {
return value;
}
switch (type) {
case 'date':
case 'datetime':
return value instanceof Date ? value.toISOString() : value;
case 'number':
return typeof value === 'string' ? parseFloat(value) : value;
case 'boolean':
if (typeof value === 'string') {
return value.toLowerCase() === 'true' || value === '1';
}
return Boolean(value);
case 'array':
return Array.isArray(value) ? value : [value];
case 'object':
return typeof value === 'object' ? value : JSON.parse(value);
case 'image':
case 'file':
return typeof value === 'string' ? value : value?.url || value;
default:
return value;
}
}
function deserializeField(value, type) {
if (value === null || value === undefined) {
return value;
}
switch (type) {
case 'date':
case 'datetime':
return new Date(value);
case 'number':
return typeof value === 'number' ? value : parseFloat(value);
case 'boolean':
return Boolean(value);
case 'array':
return Array.isArray(value) ? value : [value];
case 'object':
return typeof value === 'object' ? value : JSON.parse(value);
default:
return value;
}
}
function formatFieldValue(value, type) {
if (value === null || value === undefined) {
return '';
}
switch (type) {
case 'date':
case 'datetime':
return value instanceof Date ? value.toLocaleDateString() : value;
case 'boolean':
return value ? 'Yes' : 'No';
case 'array':
return Array.isArray(value) ? value.join(', ') : String(value);
case 'object':
return typeof value === 'object' ? JSON.stringify(value) : String(value);
case 'image':
case 'file':
return typeof value === 'string' ? value : value?.url || 'No file';
default:
return String(value);
}
}
function parseFieldValue(value, type) {
if (!value) {
return null;
}
switch (type) {
case 'number':
return parseFloat(value);
case 'boolean':
return value.toLowerCase() === 'true' || value === '1';
case 'array':
return value.split(',').map(v => v.trim());
case 'object':
try {
return JSON.parse(value);
}
catch {
return value;
}
default:
return value;
}
}
function generateFieldId() {
return Math.random().toString(36).substr(2, 9);
}
function sanitizeFieldName(name) {
return name
.toLowerCase()
.replace(/[^a-z0-9]/g, '_')
.replace(/_+/g, '_')
.replace(/^_|_$/g, '');
}
const API_ENDPOINTS = {
SCHEMAS: '/cms/schemas',
CONTENT: '/cms/content',
PUBLIC_CONTENT: '/cms/public/content',
AUTH: '/cms/auth',
VALIDATE_TOKEN: '/cms/validate-token',
};
const DEFAULT_CONFIG = {
apiUrl: 'http://localhost:3000/api',
projectId: 1,
timeout: 10000,
retries: 3,
reconnectAttempts: 5,
reconnectDelay: 1000,
};
const FIELD_TYPES = {
TEXT: 'text',
TEXTAREA: 'textarea',
NUMBER: 'number',
BOOLEAN: 'boolean',
IMAGE: 'image',
FILE: 'file',
SELECT: 'select',
ARRAY: 'array',
OBJECT: 'object',
RICH_TEXT: 'rich-text',
DATE: 'date',
DATETIME: 'datetime',
COLOR: 'color',
URL: 'url',
};
const VALIDATION_TYPES = {
REQUIRED: 'required',
MIN: 'min',
MAX: 'max',
PATTERN: 'pattern',
EMAIL: 'email',
URL: 'url',
CUSTOM: 'custom',
};
const REALTIME_EVENTS = {
CONTENT_UPDATED: 'content:updated',
CONTENT_CREATED: 'content:created',
CONTENT_DELETED: 'content:deleted',
SCHEMA_UPDATED: 'schema:updated',
};
const ERROR_CODES = {
UNAUTHORIZED: 'UNAUTHORIZED',
FORBIDDEN: 'FORBIDDEN',
NOT_FOUND: 'NOT_FOUND',
VALIDATION_ERROR: 'VALIDATION_ERROR',
NETWORK_ERROR: 'NETWORK_ERROR',
TIMEOUT_ERROR: 'TIMEOUT_ERROR',
};
class ResolvoCMSClient {
constructor(config) {
this.socket = null;
this.subscriptions = new Map();
this.reconnectAttempts = 0;
this.reconnectTimeout = null;
this.isConnecting = false;
this.requestQueue = [];
this.isProcessingQueue = false;
// Event handling
this.eventListeners = new Map();
this.config = {
...DEFAULT_CONFIG,
...config
};
this.maxReconnectAttempts = this.config.reconnectAttempts ?? DEFAULT_CONFIG.reconnectAttempts;
this.cache = new ContentCache();
this.httpClient = axios.create({
baseURL: this.config.apiUrl,
timeout: this.config.timeout,
headers: {
'Content-Type': 'application/json',
},
});
this.setupInterceptors();
}
setupInterceptors() {
// Request interceptor
this.httpClient.interceptors.request.use((config) => {
// Add authentication headers
if (this.config.websiteToken) {
config.headers['X-Website-Token'] = this.config.websiteToken;
}
if (this.config.authToken) {
config.headers['Authorization'] = `Bearer ${this.config.authToken}`;
}
return config;
}, (error) => Promise.reject(error));
// Response interceptor
this.httpClient.interceptors.response.use((response) => response, async (error) => {
if (error.response?.status === 401) {
// Handle authentication errors
this.handleAuthError();
}
return Promise.reject(error);
});
}
handleAuthError() {
// Clear cache and disconnect socket
this.cache.clear();
this.disconnect();
// Emit auth error event
this.emit('auth:error', { message: 'Authentication failed' });
}
async retryRequest(requestFn, maxRetries = 3, delay = 1000) {
let lastError;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await requestFn();
}
catch (error) {
lastError = error;
if (attempt === maxRetries) {
break;
}
// Don't retry on authentication errors
if (error.response?.status === 401) {
break;
}
// Exponential backoff
const backoffDelay = delay * Math.pow(2, attempt);
await new Promise(resolve => setTimeout(resolve, backoffDelay));
}
}
throw lastError;
}
async queueRequest(requestFn) {
return new Promise((resolve, reject) => {
this.requestQueue.push(async () => {
try {
const result = await requestFn();
resolve(result);
}
catch (error) {
reject(error);
}
});
this.processQueue();
});
}
async processQueue() {
if (this.isProcessingQueue || this.requestQueue.length === 0) {
return;
}
this.isProcessingQueue = true;
while (this.requestQueue.length > 0) {
const request = this.requestQueue.shift();
if (request) {
await request();
}
}
this.isProcessingQueue = false;
}
// Schema Management
async createSchema(schema) {
return this.retryRequest(async () => {
const response = await this.httpClient.post(API_ENDPOINTS.SCHEMAS, schema);
const result = response.data;
if (!result.success) {
throw new Error(result.message || 'Failed to create schema');
}
// Clear schema cache
this.cache.delete(`schema:${result.data.id}`);
return result.data;
});
}
async getSchema(schemaId) {
// Check cache first
const cached = this.cache.get(`schema:${schemaId}`);
if (cached)
return cached;
return this.retryRequest(async () => {
const response = await this.httpClient.get(`${API_ENDPOINTS.SCHEMAS}/${schemaId}`);
const result = response.data;
if (!result.success) {
throw new Error(result.message || 'Schema not found');
}
// Cache the result
this.cache.set(`schema:${schemaId}`, result.data);
return result.data;
});
}
async updateSchema(schemaId, updates) {
const response = await this.httpClient.put(`${API_ENDPOINTS.SCHEMAS}/${schemaId}`, updates);
const result = response.data;
if (!result.success) {
throw new Error(result.message || 'Failed to update schema');
}
// Clear cache and emit realtime event
this.cache.delete(`schema:${schemaId}`);
this.emitRealtimeEvent(REALTIME_EVENTS.SCHEMA_UPDATED, result.data);
return result.data;
}
async deleteSchema(schemaId) {
const response = await this.httpClient.delete(`${API_ENDPOINTS.SCHEMAS}/${schemaId}`);
const result = response.data;
if (!result.success) {
throw new Error(result.message || 'Failed to delete schema');
}
// Clear cache
this.cache.delete(`schema:${schemaId}`);
}
async listSchemas(query) {
const response = await this.httpClient.get(API_ENDPOINTS.SCHEMAS, { params: query });
const result = response.data;
if (!result.success) {
throw new Error(result.message || 'Failed to fetch schemas');
}
return result.data;
}
// Content Management
async createContent(content) {
// Get schema for validation
const schema = await this.getSchema(content.schemaId);
// Validate content
const validation = validateContent(schema, content.data);
if (!validation.isValid) {
throw new Error(`Validation failed: ${validation.errors.map(e => e.message).join(', ')}`);
}
// Serialize content
const serializedData = serializeContent(content.data, schema.fields);
const response = await this.httpClient.post(API_ENDPOINTS.CONTENT, { ...content, data: serializedData });
const result = response.data;
if (!result.success) {
throw new Error(result.message || 'Failed to create content');
}
// Emit realtime event
this.emitRealtimeEvent(REALTIME_EVENTS.CONTENT_CREATED, result.data);
return result.data;
}
async getContent(contentId) {
// Check cache first
const cached = this.cache.get(`content:${contentId}`);
if (cached)
return cached;
const response = await this.httpClient.get(`${API_ENDPOINTS.CONTENT}/${contentId}`);
const result = response.data;
if (!result.success) {
throw new Error(result.message || 'Content not found');
}
// Cache the result
this.cache.set(`content:${contentId}`, result.data);
return result.data;
}
async getContentBySchema(schemaId, options) {
const query = {
schemaId,
projectId: this.config.projectId,
...options
};
const response = await this.httpClient.get(API_ENDPOINTS.CONTENT, { params: query });
const result = response.data;
if (!result.success) {
throw new Error(result.message || 'Failed to fetch content');
}
return result.data;
}
async updateContent(contentId, updates) {
// Get current content and schema for validation
const currentContent = await this.getContent(contentId);
const schema = await this.getSchema(currentContent.schemaId);
// Merge updates with current data
const updatedData = { ...currentContent.data, ...updates.data };
// Validate content
const validation = validateContent(schema, updatedData);
if (!validation.isValid) {
throw new Error(`Validation failed: ${validation.errors.map(e => e.message).join(', ')}`);
}
// Serialize content
const serializedData = serializeContent(updatedData, schema.fields);
const response = await this.httpClient.put(`${API_ENDPOINTS.CONTENT}/${contentId}`, { ...updates, data: serializedData });
const result = response.data;
if (!result.success) {
throw new Error(result.message || 'Failed to update content');
}
// Clear cache and emit realtime event
this.cache.delete(`content:${contentId}`);
this.emitRealtimeEvent(REALTIME_EVENTS.CONTENT_UPDATED, result.data);
return result.data;
}
async deleteContent(contentId) {
const response = await this.httpClient.delete(`${API_ENDPOINTS.CONTENT}/${contentId}`);
const result = response.data;
if (!result.success) {
throw new Error(result.message || 'Failed to delete content');
}
// Clear cache and emit realtime event
this.cache.delete(`content:${contentId}`);
this.emitRealtimeEvent(REALTIME_EVENTS.CONTENT_DELETED, { id: contentId });
}
async publishContent(contentId) {
const response = await this.httpClient.post(`${API_ENDPOINTS.CONTENT}/${contentId}/publish`);
const result = response.data;
if (!result.success) {
throw new Error(result.message || 'Failed to publish content');
}
// Clear cache and emit realtime event
this.cache.delete(`content:${contentId}`);
this.emitRealtimeEvent(REALTIME_EVENTS.CONTENT_UPDATED, result.data);
return result.data;
}
// Public API (for websites)
async getPublicContent(schemaId) {
const response = await this.httpClient.get(`${API_ENDPOINTS.PUBLIC_CONTENT}/${this.config.projectId}`, {
params: { schemaId, isPublished: true },
headers: {
'X-Website-Token': this.config.websiteToken
}
});
const result = response.data;
if (!result.success) {
throw new Error(result.message || 'Failed to fetch public content');
}
return result.data;
}
// Real-time Features
connect(wsConfig) {
if (this.socket?.connected || this.isConnecting) {
return;
}
this.isConnecting = true;
const wsUrl = wsConfig?.url || this.config.apiUrl.replace('http', 'ws');
this.socket = io(wsUrl, {
auth: {
token: this.config.authToken || this.config.websiteToken
},
reconnectionAttempts: this.maxReconnectAttempts,
reconnectionDelay: this.config.reconnectDelay,
reconnectionDelayMax: 5000,
timeout: 20000,
forceNew: true
});
this.socket.on('connect', () => {
this.reconnectAttempts = 0;
this.isConnecting = false;
this.emit('connected');
// Resubscribe to all subscriptions
this.resubscribeAll();
});
this.socket.on('disconnect', (reason) => {
this.isConnecting = false;
this.emit('disconnected', { reason });
if (reason === 'io server disconnect') {
// Server disconnected us, don't reconnect
return;
}
});
this.socket.on('connect_error', (error) => {
this.isConnecting = false;
this.reconnectAttempts++;
this.emit('connection_error', error);
});
// Handle real-time events
Object.values(REALTIME_EVENTS).forEach(event => {
this.socket.on(event, (data) => {
this.handleRealtimeEvent(event, data);
});
});
}
disconnect() {
if (this.socket) {
this.socket.disconnect();
this.socket = null;
}
}
subscribe(schemaId, callback) {
if (!this.subscriptions.has(schemaId)) {
this.subscriptions.set(schemaId, []);
}
this.subscriptions.get(schemaId).push(callback);
// Subscribe to real-time updates
if (this.socket?.connected) {
this.socket.emit('subscribe:content', { schemaId });
}
}
unsubscribe(schemaId, callback) {
if (!callback) {
this.subscriptions.delete(schemaId);
}
else {
const callbacks = this.subscriptions.get(schemaId);
if (callbacks) {
const index = callbacks.indexOf(callback);
if (index > -1) {
callbacks.splice(index, 1);
}
if (callbacks.length === 0) {
this.subscriptions.delete(schemaId);
}
}
}
}
handleRealtimeEvent(event, data) {
const message = {
type: event,
data,
timestamp: new Date()
};
// Notify subscribers
if (data.schemaId && this.subscriptions.has(data.schemaId)) {
const callbacks = this.subscriptions.get(data.schemaId);
callbacks.forEach(callback => callback(message));
}
// Emit general event
this.emit(event, message);
}
emitRealtimeEvent(event, data) {
if (this.socket?.connected) {
this.socket.emit(event, data);
}
}
resubscribeAll() {
// Resubscribe to all active subscriptions
for (const [schemaId] of this.subscriptions) {
if (this.socket?.connected) {
this.socket.emit('subscribe:content', { schemaId });
}
}
}
on(event, callback) {
if (!this.eventListeners.has(event)) {
this.eventListeners.set(event, []);
}
this.eventListeners.get(event).push(callback);
}
off(event, callback) {
const listeners = this.eventListeners.get(event);
if (listeners) {
const index = listeners.indexOf(callback);
if (index > -1) {
listeners.splice(index, 1);
}
}
}
emit(event, data) {
const listeners = this.eventListeners.get(event);
if (listeners) {
listeners.forEach(callback => callback(data));
}
}
// Utility methods
clearCache() {
this.cache.clear();
}
getCacheStats() {
return {
size: this.cache.size()
};
}
isConnected() {
return this.socket?.connected || false;
}
}
function useContent(client, options = {}) {
const { schemaId, contentId, isPublished = true, autoConnect = true, realtime = true } = options;
const [content, setContent] = useState(null);
const [contentList, setContentList] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [isInitialized, setIsInitialized] = useState(false);
useRef(client);
useRef(options);
const abortControllerRef = useRef(null);
// Initialize and fetch content
useEffect(() => {
if (!client || isInitialized)
return;
// Cancel previous request if still pending
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
abortControllerRef.current = new AbortController();
const initialize = async () => {
try {
setLoading(true);
setError(null);
if (autoConnect && realtime) {
client.connect();
}
if (contentId) {
const contentData = await client.getContent(contentId);
if (!abortControllerRef.current?.signal.aborted) {
setContent(contentData);
}
}
else if (schemaId) {
const contentData = await client.getContentBySchema(schemaId, { isPublished });
if (!abortControllerRef.current?.signal.aborted) {
setContentList(contentData);
}
}
if (!abortControllerRef.current?.signal.aborted) {
setIsInitialized(true);
}
}
catch (err) {
if (!abortControllerRef.current?.signal.aborted) {
setError(err instanceof Error ? err : new Error('Failed to fetch content'));
}
}
finally {
if (!abortControllerRef.current?.signal.aborted) {
setLoading(false);
}
}
};
initialize();
// Cleanup function
return () => {
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
};
}, [client, contentId, schemaId, isPublished, autoConnect, realtime, isInitialized]);
// Real-time updates
useEffect(() => {
if (!client || !realtime || !schemaId)
return;
const handleContentUpdate = (message) => {
if (message.type === 'content:updated' || message.type === 'content:created') {
const updatedContent = message.data;
if (contentId && updatedContent.id === contentId) {
setContent(updatedContent);
}
if (schemaId && updatedContent.schemaId === schemaId) {
setContentList(prev => {
const index = prev.findIndex(c => c.id === updatedContent.id);
if (index >= 0) {
const newList = [...prev];
newList[index] = updatedContent;
return newList;
}
else {
return [...prev, updatedContent];
}
});
}
}
else if (message.type === 'content:deleted') {
const deletedId = message.data.id;
if (contentId && deletedId === contentId) {
setContent(null);
}
setContentList(prev => prev.filter(c => c.id !== deletedId));
}
};
client.subscribe(schemaId, handleContentUpdate);
return () => {
client.unsubscribe(schemaId, handleContentUpdate);
};
}, [client, schemaId, contentId, realtime]);
// Content operations
const createContent = useCallback(async (data) => {
try {
setError(null);
const newContent = await client.createContent(data);
if (schemaId && newContent.schemaId === schemaId) {
setContentList(prev => [...prev, newContent]);
}
return newContent;
}
catch (err) {
const error = err instanceof Error ? err : new Error('Failed to create content');
setError(error);
throw error;
}
}, [client, schemaId]);
const updateContent = useCallback(async (contentId, updates) => {
try {
setError(null);
const updatedContent = await client.updateContent(contentId, updates);
if (content && content.id === contentId) {
setContent(updatedContent);
}
setContentList(prev => prev.map(c => c.id === contentId ? updatedContent : c));
return updatedContent;
}
catch (err) {
const error = err instanceof Error ? err : new Error('Failed to update content');
setError(error);
throw error;
}
}, [client, content]);
const deleteContent = useCallback(async (contentId) => {
try {
setError(null);
await client.deleteContent(contentId);
if (content && content.id === contentId) {
setContent(null);
}
setContentList(prev => prev.filter(c => c.id !== contentId));
}
catch (err) {
const error = err instanceof Error ? err : new Error('Failed to delete content');
setError(error);
throw error;
}
}, [client, content]);
const publishContent = useCallback(async (contentId) => {
try {
setError(null);
const publishedContent = await client.publishContent(contentId);
if (content && content.id === contentId) {
setContent(publishedContent);
}
setContentList(prev => prev.map(c => c.id === contentId ? publishedContent : c));
return publishedContent;
}
catch (err) {
const error = err instanceof Error ? err : new Error('Failed to publish content');
setError(error);
throw error;
}
}, [client, content]);
const refresh = useCallback(async () => {
try {
setLoading(true);
setError(null);
if (contentId) {
const contentData = await client.getContent(contentId);
setContent(contentData);
}
else if (schemaId) {
const contentData = await client.getContentBySchema(schemaId, { isPublished });
setContentList(contentData);
}
}
catch (err) {
setError(err instanceof Error ? err : new Error('Failed to refresh content'));
}
finally {
setLoading(false);
}
}, [client, contentId, schemaId, isPublished]);
return {
content,
contentList,
loading,
error,
createContent,
updateContent,
deleteContent,
publishContent,
refresh
};
}
// Optimistic updates hook
function useOptimisticContent(client, options = {}) {
const [optimisticData, setOptimisticData] = useState(null);
const { content, updateContent, ...rest } = useContent(client, options);
const optimisticUpdate = useCallback(async (contentId, updates) => {
if (!content) {
throw new Error('No content to update');
}
// Set optimistic data immediately
const optimisticContent = {
...content,
data: { ...content.data, ...updates.data }};
setOptimisticData(optimisticContent.data);
try {
// Perform actual update
const result = await updateContent(contentId, updates);
setOptimisticData(null);
return result;
}
catch (error) {
// Revert on error
setOptimisticData(null);
throw error;
}
}, [content, updateContent]);
return {
...rest,
content: optimisticData ? { ...content, data: optimisticData } : content,
updateContent: optimisticUpdate
};
}
function useSchema(client, options = {}) {
const { schemaId, projectId, isActive = true, autoConnect = true, realtime = true } = options;
const [schema, setSchema] = useState(null);
const [schemas, setSchemas] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [isInitialized, setIsInitialized] = useState(false);
// Initialize and fetch schemas
useEffect(() => {
if (!client || isInitialized)
return;
const initialize = async () => {
try {
setLoading(true);
setError(null);
if (autoConnect && realtime) {
client.connect();
}
if (schemaId) {
const schemaData = await client.getSchema(schemaId);
setSchema(schemaData);
}
else {
const schemasData = await client.listSchemas({
projectId: projectId || client['config'].projectId,
isActive
});
setSchemas(schemasData);
}
setIsInitialized(true);
}
catch (err) {
setError(err instanceof Error ? err : new Error('Failed to fetch schemas'));
}
finally {
setLoading(false);
}
};
initialize();
}, [client, schemaId, projectId, isActive, autoConnect, realtime, isInitialized]);
// Real-time updates
useEffect(() => {
if (!client || !realtime)
return;
const handleSchemaUpdate = (message) => {
if (message.type === 'schema:updated') {
const updatedSchema = message.data;
if (schemaId && updatedSchema.id === schemaId) {
setSchema(updatedSchema);
}
setSchemas(prev => {
const index = prev.findIndex(s => s.id === updatedSchema.id);
if (index >= 0) {
const newList = [...prev];
newList[index] = updatedSchema;
return newList;
}
return prev;
});
}
};
client.on('schema:updated', handleSchemaUpdate);
return () => {
client.off('schema:updated', handleSchemaUpdate);
};
}, [client, schemaId, realtime]);
// Schema operations
const createSchema = useCallback(async (data) => {
try {
setError(null);
const newSchema = await client.createSchema(data);
setSchemas(prev => [...prev, newSchema]);
return newSchema;
}
catch (err) {
const error = err instanceof Error ? err : new Error('Failed to create schema');
setError(error);
throw error;
}
}, [client]);
const updateSchema = useCallback(async (schemaId, updates) => {
try {
setError(null);
const updatedSchema = await client.updateSchema(schemaId, updates);
if (schema && schema.id === schemaId) {
setSchema(updatedSchema);
}
setSchemas(prev => prev.map(s => s.id === schemaId ? updatedSchema : s));
return updatedSchema;
}
catch (err) {
const error = err instanceof Error ? err : new Error('Failed to update schema');
setError(error);
throw error;
}
}, [client, schema]);
const deleteSchema = useCallback(async (schemaId) => {
try {
setError(null);
await client.deleteSchema(schemaId);
if (schema && schema.id === schemaId) {
setSchema(null);
}
setSchemas(prev => prev.filter(s => s.id !== schemaId));
}
catch (err) {
const error = err instanceof Error ? err : new Error('Failed to delete schema');
setError(error);
throw error;
}
}, [client, schema]);
const refresh = useCallback(async () => {
try {
setLoading(true);
setError(null);
if (schemaId) {
const schemaData = await client.getSchema(schemaId);
setSchema(schemaData);
}
else {
const schemasData = await client.listSchemas({
projectId: projectId || client['config'].projectId,
isActive
});
setSchemas(schemasData);
}
}
catch (err) {
setError(err instanceof Error ? err : new Error('Failed to refresh schemas'));
}
finally {
setLoading(false);
}
}, [client, schemaId, projectId, isActive]);
return {
schema,
schemas,
loading,
error,
createSchema,
updateSchema,
deleteSchema,
refresh
};
}
function useRealtime(client, options = {}) {
const { autoConnect = true, reconnectAttempts = 5, reconnectDelay = 1000 } = options;
const [isConnected, setIsConnected] = useState(false);
const [isConnecting, setIsConnecting] = useState(false);
const [error, setError] = useState(null);
const [reconnectCount, setReconnectCount] = useState(0);
const reconnectTimeoutRef = useRef(null);
// Auto-connect on mount
useEffect(() => {
if (autoConnect && client) {
connect();
}
return () => {
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current);
}
};
}, [autoConnect, client]);
// Connection event handlers
useEffect(() => {
if (!client)
return;
const handleConnect = () => {
setIsConnected(true);
setIsConnecting(false);
setError(null);
setReconnectCount(0);
};
const handleDisconnect = () => {
setIsConnected(false);
setIsConnecting(false);
};
const handleConnectError = (err) => {
setIsConnecting(false);
setError(err);
// Auto-reconnect logic
if (reconnectCount < reconnectAttempts) {
setReconnectCount(prev => prev + 1);
reconnectTimeoutRef.current = setTimeout(() => {
connect();
}, reconnectDelay);
}
};
const handleReconnect = (attempt) => {
setIsConnecting(true);
setError(null);
};
const handleReconnectAttempt = (attempt) => {
setIsConnecting(true);
};
const handleReconnectError = () => {
setIsConnecting(false);
setError(new Error('Failed to reconnect'));
};
// Set up event listeners
client.on('connected', handleConnect);
client.on('disconnected', handleDisconnect);
client.on('connection_error', handleConnectError);
client.on('reconnect', handleReconnect);
client.on('reconnect_attempt', handleReconnectAttempt);
client.on('reconnect_error', handleReconnectError);
return () => {
client.off('connected', handleConnect);
client.off('disconnected', handleDisconnect);
client.off('connection_error', handleConnectError);
client.off('reconnect', handleReconnect);
client.off('reconnect_attempt', handleReconnectAttempt);
client.off('reconnect_error', handleReconnectError);
};
}, [client, reconnectCount, reconnectAttempts, reconnectDelay]);
const connect = useCallback(() => {
if (!client || isConnected || isConnecting)
return;
setIsConnecting(true);
setError(null);
client.connect();
}, [client, isConnected, isConnecting]);
const disconnect = useCallback(() => {
if (!client)
return;
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current);
}
setIsConnecting(false);
setReconnectCount(0);
client.disconnect();
}, [client]);
const subscribe = useCallback((schemaId, callback) => {
if (!client)
return;
client.subscribe(schemaId, callback);
}, [client]);
const unsubscribe = useCallback((schemaId, callback) => {
if (!client)
return;
client.unsubscribe(schemaId, callback);
}, [client]);
const sendMessage = useCallback((event, data) => {
if (!client || !isConnected)
return;
// This would need to be implemented in the client if needed
// For now, we'll just emit events through the client
client.on(event, data);
}, [client, isConnected]);
return {
isConnected,
isConnecting,
error,
connect,
disconnect,
subscribe,
unsubscribe,
sendMessage
};
}
// Hook for subscribing to specific content updates
function useContentSubscription(client, schemaId, options = {}) {
const [messages, setMessages] = useState([]);
const [lastMessage, setLastMessage] = useState(null);
const { isConnected, subscribe, unsubscribe } = useRealtime(client);
useEffect(() => {
if (!isConnected || !schemaId)
return;
const handleMessage = (message) => {
setMessages(prev => [...prev, message]);
setLastMessage(message);
};
subscribe(schemaId, handleMessage);
return () => {
unsubscribe(schemaId, handleMessage);
};
}, [isConnected, schemaId, subscribe, unsubscribe]);
const clearMessages = useCallback(() => {
setMessages([]);
setLastMessage(null);
}, []);
return {
messages,
lastMessage,
isConnected,
clearMessages
};
}
// Hook for optimistic updates with real-time sync
function useOptimisticRealtime(client, schemaId, initialData) {
const [optimisticData, setOptimisticData] = useState(initialData);
const [serverData, setServerData] = useState(initialData);
const { isConnected, subscribe, unsubscribe } = useRealtime(client);
useEffect(() => {
if (!isConnected || !schemaId)
return;
const handleUpdate = (message) => {
if (message.type === 'content:updated' || message.type === 'content:created') {
setServerData(message.data);
// Only update optimistic data if it hasn't been modified locally
if (JSON.stringify(optimisticData) === JSON.stringify(serverData)) {
setOptimisticData(message.data);
}
}
};
subscribe(schemaId, handleUpdate);
return () => {
unsubscribe(schemaId, handleUpdate);
};
}, [isConnected, schemaId, subscribe, unsubscribe, optimisticData, serverData]);
const updateOptimistic = useCallback((updates) => {
setOptimisticData(prev => ({ ...prev, ...updates }));
}, []);
const syncWithServer = useCallback(() => {
setOptimisticData(serverData);
}, [serverData]);
return {
optimisticData,
serverData,
isConnected,
updateOptimistic,
syncWithServer
};
}
var jsxRuntime = {exports: {}};
var reactJsxRuntime_production_min = {};
/**
* @license React
* react-jsx-runtime.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
var hasRequiredReactJsxRuntime_production_min;
function requireReactJsxRuntime_production_min () {
if (hasRequiredReactJsxRuntime_production_min) return reactJsxRuntime_production_min;
hasRequiredReactJsxRuntime_production_min = 1;
var f=require$$0,k=Symbol.for("react.element"),l=Symbol.for("react.fragment"),m=Object.prototype.hasOwnProperty,n=f.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,p={key:true,ref:true,__self:true,__source:true};
function q(c,a,g){var b,d={},e=null,h=null;void 0!==g&&(e=""+g);void 0!==a.key&&(e=""+a.key);void 0!==a.ref&&(h=a.ref);for(b in a)m.call(a,b)&&!p.hasOwnProperty(b)&&(d[b]=a[b]);if(c&&c.defaultProps)for(b in a=c.defaultProps,a) void 0===d[b]&&(d[b]=a[b]);return {$$typeof:k,type:c,key:e,ref:h,props:d,_owner:n.current}}reactJsxRuntime_production_min.Fragment=l;reactJsxRuntime_production_min.jsx=q;reactJsxRuntime_production_min.jsxs=q;
return reactJsxRuntime_production_min;
}
var reactJsxRuntime_development = {};
/**
* @license React
* react-jsx-runtime.development.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
var hasRequiredReactJsxRuntime_development;
function requireReactJsxRuntime_development () {
if (hasRequiredReactJsxRuntime_development) return reactJsxRuntime_development;
hasRequiredReactJsxRuntime_development = 1;
if (process.env.NODE_ENV !== "production") {
(function() {
var React = require$$0;
// ATTENTION
// When adding new symbols to this file,
// Please consider also adding to 'react-devtools-shared/src/backend/ReactSymbols'
// The Symbol used to tag the ReactElement-like types.
var REACT_ELEMENT_TYPE = Symbol.for('react.element');
var REACT_PORTAL_TYPE = Symbol.for('react.portal');
var REACT_FRAGMENT_TYPE = Symbol.for('react.fragment');
var REACT_STRICT_MODE_TYPE = Symbol.for('react.strict_mode');
var REACT_PROFILER_TYPE = Symbol.for('react.profiler');
var REACT_PROVIDER_TYPE = Symbol.for('react.provider');
var REACT_CONTEXT_TYPE = Symbol.for('react.context');
var REACT_FORWARD_REF_TYPE = Symbol.for('react.forward_ref');
var REACT_SUSPENSE_TYPE = Symbol.for('react.suspense');
var REACT_SUSPENSE_LIST_TYPE = Symbol.for('react.suspense_list');
var REACT_MEMO_TYPE = Symbol.for('react.memo');
var REACT_LAZY_TYPE = Symbol.for('react.lazy');
var REACT_OFFSCREEN_TYPE = Symbol.for('react.offscreen');
var MAYBE_ITERATOR_SYMBOL = Symbol.iterator;
var FAUX_ITERATOR_SYMBOL = '@@iterator';
function getIteratorFn(maybeIterable) {
if (maybeIterable === null || typeof maybeIterable !== 'object') {
return null;
}
var maybeIterator = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL];
if (typeof maybeIterator === 'function') {
return maybeIterator;
}
return null;
}
var ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
function error(format) {
{
{
for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
args[_key2 - 1] = arguments[_key2];
}
printWarning('error', format, args);
}
}
}
function printWarning(level, format, args) {
// When changing this logic, you might want to also
// update consoleWithStackDev.www.js as well.
{
var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
var stack = ReactDebugCurrentFrame.getStackAddendum();
if (stack !== '') {
format += '%s';
args = args.concat([stack]);
} // eslint-disable-next-line react-internal/safe-string-coercion
var argsWithFormat = args.map(function (item) {
return String(item);
}); // Careful: RN currently depends on this prefix
argsWithFormat.unshift('Warning: ' + format); // We intentionally don't use spread (or .apply) directly because it
// breaks IE9: https://github.com/facebook/react/issues/13610
// eslint-disable-next-line react-internal/no-production-logging
Function.prototype.apply.call(console[level], console, argsWithFormat);
}
}
// -----------------------------------------------------------------------------
var enableScopeAPI = false; // Experimental Create Event Handle API.
var enableCacheElement = false;
var enableTransitionTracing = false; // No known bugs, but needs performance testing
var enableLegacyHidden = false; // Enables unstable_avoidThisFallback feature in Fiber
// stuff. Intended to enable React core members to more easily debug scheduling
// issues in DEV builds.
var enableDebugTracing = false; // Track which Fiber(s) schedule render work.
var REACT_MODULE_REFERENCE;
{
REACT_MODULE_REFERENCE = Symbol.for('react.module.reference');
}
function isValidElementType(type) {
if (typeof type === 'string' || typeof type === 'function') {
return true;
} // Note: typeof might be other than 'symbol' or 'number' (e.g. if it's a polyfill).
if (type === REACT_FRAGMENT_TYPE || type === REACT_PROFILER_TYPE || enableDebugTracing || type === REACT_STRICT_MODE_TYPE || type === REACT_SUSPENSE_TYPE || type === REACT_SUSPENSE_LIST_TYPE || enableLegacyHidden || type === REACT_OFFSCREEN_TYPE || enableScopeAPI || enableCacheElement || enableTransitionTracing ) {
return true;
}
if (typeof type === 'object' && type !== null) {
if (type.$$typeof === REACT_LAZY_TYPE || type.$$typeof === REACT_MEMO_TYPE || type.$$typeof === REACT_PROVIDER_TYPE || type.$$typeof === REACT_CONTEXT_TYPE || type.$$typeof === REACT_FORWARD_REF_TYPE || // This needs to include all possible module reference object
// types supported by any Flight configuration anywhere since
// we don't know which Flight build this will end up being used
// with.
type.$$typeof === REACT_MODULE_REFERENCE || type.getModuleId !== undefined) {
return true;
}
}
return false;
}
function getWrappedName(outerType, innerType, wrapperName) {
var displayName = outerType.displayName;
if (displayName) {
return displayName;
}
var functionName = innerType.displayName || innerType.name || '';
return functionName !== '' ? wrapperName + "(" + functionName + ")" : wrapperName;
} // Keep in sync with react-reconciler/getComponentNameFromFiber
function getContextName(type) {
return type.displayName || 'Context';
} // Note that the reconciler package should generally prefer to use getComponentNameFromFiber() instead.
function getComponentNameFromType(type) {
if (type == null) {
// Host root, text node or just invalid type.
return null;
}
{
if (typeof type.tag === 'number') {
error('Received an unexpected object in getComponentNameFromType(). ' + 'This is likely a bug in React. Please file an issue.');
}
}
if (typeof type === 'function') {
return type.displayName || type.name || null;
}
if (typeof type === 'string') {
return type;
}
switch (type) {
case REACT_FRAGMENT_TYPE:
return 'Fragment';
case REACT_PORTAL_TYPE:
return 'Portal';
case REACT_PROFILER_TYPE:
return 'Profiler';
case REACT_STRICT_MODE_TYPE:
return 'StrictMode';
case REACT_SUSPENSE_TYPE:
return 'Suspense';
case REACT_SUSPENSE_LIST_TYPE:
return 'SuspenseList';
}
if (typeof type === 'object') {
switch (type.$$typeof) {
case REACT_CONTEXT_TYPE:
var context = type;
return getContextName(context) + '.Consumer';
case REACT_PROVIDER_TYPE:
var provider = type;
return getContextName(provider._context) + '.Provider';
case REACT_FORWARD_REF_TYPE:
return getWrappedName(type, type.render, 'ForwardRef');
case REACT_MEMO_TYPE:
var outerName = type.displayName || null;
if (outerName !== null) {
return outerName;
}
return getComponentNameFromType(type.type) || 'Memo';
case REACT_LAZY_TYPE:
{
var lazyComponent = type;
var payload = lazyComponent._payload;
var init = lazyComponent._init;
try {
return getComponentNameFromType(init(payload));
} catch (x) {
return null;
}
}
// eslint-disable-next-line no-fallthrough
}
}
return null;
}
var assign = Object.assign;
// Helpers to patch console.logs to avoid logging during side-effect free
// replaying on render function. This currently only patches the object
// lazily which won't cover if the log function was extracted eagerly.
// We could also eagerly patch the method.
var disabledDepth = 0;
var prevLog;
var prevInfo;
var prevWarn;
var prevError;
var prevGroup;
var prevGroupCollapsed;
var prevGroupEnd;
function disabledLog() {}
disabledLog.__reactDisabledLog = true;
function disableLogs() {
{
if (disabledDepth === 0) {
/* eslint-disable react-internal/no-production-logging */
prevLog = console.log;
prevInfo = console.info;
prevWarn = console.warn;
prevError = console.error;
prevGroup = console.group;
prevGroupCollapsed = console.groupCollapsed;
prevGroupEnd = console.groupEnd; // https://github.com/facebook/react/issues/19099
var props = {
configurable: true,
enumerable: true,
value: disabledLog,
writable: true
}; // $FlowFixMe Flow thinks console is immutable.
Object.defineProperties(console, {
info: props,
log: props,
warn: props,
error: props,
group: props,
groupCollapsed: props,
groupEnd: props
});
/* eslint-enable react-internal/no-production-logging */
}
disabledDepth++;
}
}
function reenableLogs() {
{
disabledDepth--;
if (disabledDepth === 0) {
/* eslint-disable react-internal/no-production-logging */
var props = {
configurable: true,
enumerable: true,
writable: true
}; // $FlowFixMe Flow thinks console is immutable.
Object.defineProperties(console, {
log: assign({}, props, {
value: prevLog
}),
info: assign({}, props, {
value: prevInfo
}),
warn: assign({}, props, {
value: prevWarn
}),
error: assign({}, props, {
value: prevError
}),
group: assign({}, props, {
value: prevGroup
}),
groupCollapsed: assign({}, props, {
value: prevGroupCollapsed
}),
groupEnd: assign({}, props, {
value: prevGroupEnd
})
});
/* eslint-enable react-internal/no-production-logging */
}
if (disabledDepth < 0) {
error('disabledDepth fell below zero. ' + 'This is a bug in React. Please file an issue.');
}
}
}
var ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
var prefix;
function describeBuiltInComponentFrame(name, source, ownerFn) {
{
if (prefix === undefined) {
// Extract the VM specific prefix used by each line.
try {
throw Error();
} catch (x) {
var match = x.stack.trim().match(/\n( *(at )?)/);
prefix = match && match[1] || '';
}
} // We use the prefix to ensure our stacks line up with native stack frames.
return '\n' + prefix + name;
}
}
var reentry = false;
var componentFrameCache;
{
var PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
componentFrameCache = new PossiblyWeakMap();
}
function describeNativeComponentFrame(fn, construct) {
// If something asked for a stack inside a fake render, it should get ignored.
if ( !fn || reentry) {
return '';
}
{
var frame = componentFrameCache.get(fn);
if (frame !== undefined) {
return frame;
}
}
var control;
reentry = true;
var previousPrepareStackTrace = Error.prepareStackTrace; // $FlowFixMe It does accept undefined.
Error.prepareStackTrace = undefined;
var previousDispatcher;
{
previousDispatcher = ReactCurrentDispatcher.current; // Set the dispatcher in DEV because this might be call in the render function
// for warnings.
ReactCurrentDispatcher.current = null;
disableLogs();
}
try {
// This should throw.
if (construct) {
// Something should be setting the props in the constructor.
var Fake = function () {
throw Error();
}; // $FlowFixMe
Object.defineProperty(Fake.prototype, 'props', {
set: function () {
// We use a throwing setter instead of frozen or non-writable props
// because that won't throw in a non-strict mode function.
throw Error();
}
});
if (typeof Reflect === 'object' && Reflect.construct) {
// We construct a different control for this case to include any extra
// frames added by the construct call.
try {
Reflect.construct(Fake, []);
} catch (x) {
control = x;
}
Reflect.construct(fn, [], Fake);
} else {
try {
Fake.call();
} catch (x) {
control = x;
}
fn.call(Fake.prototype);
}
} else {
try {
throw Error();
} catch (x) {
control = x;
}
fn();
}
} catch (sample) {
// This is inlined manually because closure doesn't do it for us.
if (sample && control && typeof sample.stack === 'string') {
// This extracts the first frame from the sample that isn't also in the control.
// Skipping one frame that we assume is the frame that calls the two.
var sampleLines = sample.stack.split('\n');
var controlLines = control.stack.split('\n');
var s = sampleLines.length - 1;
var c = controlLines.length - 1;
while (s >= 1 && c >= 0 && sampleLines[s] !== controlLines[c]) {
// We expect at least one stack frame to be shared.
// Typically this will be the root most one. However, stack frames may be
// cut off due to maximum stack limits. In this case, one maybe cut off
// earlier than the other. We assume that the sample is longer or the same
// and there for cut off earlier. So we should find the root most frame in
// the sample somewhere in the control.
c--;
}
for (; s >= 1 && c >= 0; s--, c--) {
// Next we find the first one that isn't the same which should be the
// frame that called our sample function and the control.
if (sampleLines[s] !== controlLines[c]) {
// In V8, the first line is describing the message but other VMs don't.
// If we're about to return the first line, and the control is also on the same
// line, that's a pretty good indicator that our sample threw at same line as
// the control. I.e. before we entered the sample frame. So we ignore this result.
// This can happen if you passed a class to function component, or non-function.
if (s !== 1 || c !== 1) {
do {
s--;
c--; // We may still have similar intermediate frames from the construct call.
// The next one that isn't the same should be our match though.
if (c < 0 || sampleLines[s] !== controlLines[c]) {
// V8 adds a "new" prefix for native classes. Let's remove it to make it prettier.
var _frame = '\n' + sampleLines[s].replace(' at new ', ' at '); // If our component frame is labeled "<anonymous>"
// but we have a user-provided "displayName"
// splice it in to make the stack more readable.
if (fn.displayName && _frame.includes('<anonymous>')) {
_frame = _frame.replace('<anonymous>', fn.displayName);
}
{
if (typeof fn === 'function') {
componentFrameCache.set(fn, _frame);
}
} // Return the line we found.
return _frame;
}
} while (s >= 1 && c >= 0);
}
break;
}
}
}
} finally {
reentry = false;
{
ReactCurrentDispatcher.current = previousDispatcher;
reenableLogs();
}
Error.prepareStackTrace = previousPrepareStackTrace;
} // Fallback to just using the name if we couldn't make it throw.
var name = fn ? fn.displayName || fn.name : '';
var syntheticFrame = name ? describeBuiltInComponentFrame(name) : '';
{
if (typeof fn === 'function') {
componentFrameCache.set(fn, syntheticFrame);
}
}
return syntheticFrame;
}
function describeFunctionComponentFrame(fn, source, ownerFn) {
{
return describeNativeComponentFrame(fn, false);
}
}
function shouldConstruct(Component) {
var prototype = Component.prototype;
return !!(prototype && prototype.isReactComponent);
}
function describeUnknownElementTypeFrameInDEV(type, source, ownerFn) {
if (type == null) {
return '';
}
if (typeof type === 'function') {
{
return describeNativeComponentFrame(type, shouldConstruct(type));
}
}
if (typeof type === 'string') {
return describeBuiltInComponentFrame(type);
}
switch (type) {
case REACT_SUSPENSE_TYPE:
return describeBuiltInComponentFrame('Suspense');
case REACT_SUSPENSE_LIST_TYPE:
return describeBuiltInComponentFrame('SuspenseList');
}
if (typeof type === 'object') {
switch (type.$$typeof) {
case REACT_FORWARD_REF_TYPE:
return describeFunctionComponentFrame(type.render);
case REACT_MEMO_TYPE:
// Memo may contain any component type so we recursively resolve it.
return describeUnknownElementTypeFrameInDEV(type.type, source, ownerFn);
case REACT_LAZY_TYPE:
{
var lazyComponent = type;
var payload = lazyComponent._payload;
var init = lazyComponent._init;
try {
// Lazy may contain any component type so we recursively resolve it.
return describeUnknownElementTypeFrameInDEV(init(payload), source, ownerFn);
} catch (x) {}
}
}
}
return '';
}
var hasOwnProperty = Object.prototype.hasOwnProperty;
var loggedTypeFailures = {};
var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
function setCurrentlyValidatingElement(element) {
{
if (element) {
var owner = element._owner;
var stack = describeUnknownElementTypeFrameInDEV(element.type, element._source, owner ? owner.type : null);
ReactDebugCurrentFrame.setExtraStackFrame(stack);
} else {
ReactDebugCurrentFrame.setExtraStackFrame(null);
}
}
}
function checkPropTypes(typeSpecs, values, location, componentName, element) {
{
// $FlowFixMe This is okay but Flow doesn't know it.
var has = Function.call.bind(hasOwnProperty);
for (var typeSpecName in typeSpecs) {
if (has(typeSpecs, typeSpecName)) {
var error$1 = void 0; // Prop type validation may throw. In case they do, we don't want to
// fail the render phase where it didn't fail before. So we log it.
// After these have been cleaned up, we'll let them throw.
try {
// This is intentionally an invariant that gets caught. It's the same
// behavior as without this statement except with a better message.
if (typeof typeSpecs[typeSpecName] !== 'function') {
// eslint-disable-next-line react-internal/prod-error-codes
var err = Error((componentName || 'React class') + ': ' + location + ' type `' + typeSpecName + '` is invalid; ' + 'it must be a function, usually from the `prop-types` package, but received `' + typeof typeSpecs[typeSpecName] + '`.' + 'This often happens because of typos such as `PropTypes.function` instead of `PropTypes.func`.');
err.name = 'Invariant Violation';
throw err;
}
error$1 = typeSpecs[typeSpecName](values, typeSpecName, componentName, location, null, 'SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED');
} catch (ex) {
error$1 = ex;
}
if (error$1 && !(error$1 instanceof Error)) {
setCurrentlyValidatingElement(element);
error('%s: type specification of %s' + ' `%s` is invalid; the type checker ' + 'function must return `null` or an `Error` but returned a %s. ' + 'You may have forgotten to pass an argument to the type checker ' + 'creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and ' + 'shape all require an argument).', componentName || 'React class', location, typeSpecName, typeof error$1);
setCurrentlyValidatingElement(null);
}
if (error$1 instanceof Error && !(error$1.message in loggedTypeFailures)) {
// Only monitor this failure once because there tends to be a lot of the
// same error.
loggedTypeFailures[error$1.message] = true;
setCurrentlyValidatingElement(element);
error('Failed %s type: %s', location, error$1.message);
setCurrentlyValidatingElement(null);
}
}
}
}
}
var isArrayImpl = Array.isArray; // eslint-disable-next-line no-redeclare
function isArray(a) {
return isArrayImpl(a);
}
/*
* The `'' + value` pattern (used in in perf-sensitive code) throws for Symbol
* and Temporal.* types. See https://github.com/facebook/react/pull/22064.
*
* The functions in this module will throw an easier-to-understand,
* easier-to-debug exception with a clear errors message message explaining the
* problem. (Instead of a confusing exception thrown inside the implementation
* of the `value` object).
*/
// $FlowFixMe only called in DEV, so void return is not possible.
function typeName(value) {
{
// toStringTag is needed for namespaced types like Temporal.Instant
var hasToStringTag = typeof Symbol === 'function' && Symbol.toStringTag;
var type = hasToStringTag && value[Symbol.toStringTag] || value.constructor.name || 'Object';
return type;
}
} // $FlowFixMe only called in DEV, so void return is not possible.
function willCoercionThrow(value) {
{
try {
testStringCoercion(value);
return false;
} catch (e) {
return true;
}
}
}
function testStringCoercion(value) {
// If you ended up here by following an exception call stack, here's what's
// happened: you supplied an object or symbol value to React (as a prop, key,
// DOM attribute, CSS property, string ref, etc.) and when React tried to
// coerce it to a string using `'' + value`, an exception was thrown.
//
// The most common types that will cause this exception are `Symbol` instances
// and Temporal objects like `Temporal.Instant`. But any object that has a
// `valueOf` or `[Symbol.toPrimitive]` method that throws will also cause this
// exception. (Library authors do this to prevent users from using built-in
// numeric operators like `+` or comparison operators like `>=` because custom
// methods are needed to perform accurate arithmetic or comparison.)
//
// To fix the problem, coerce this object or symbol value to a string before
// passing it to React. The most reliable way is usually `String(value)`.
//
// To find which value is throwing, check the browser or debugger console.
// Before this exception was thrown, there should be `console.error` output
// that shows the type (Symbol, Temporal.PlainDate, etc.) that caused the
// problem and how that type was used: key, atrribute, input value prop, etc.
// In most cases, this console output also shows the component and its
// ancestor components where the exception happened.
//
// eslint-disable-next-line react-internal/safe-string-coercion
return '' + value;
}
function checkKeyStringCoercion(value) {
{
if (willCoercionThrow(value)) {
error('The provided key is an unsupported type %s.' + ' This value must be coerced to a string before before using it here.', typeName(value));
return testStringCoercion(value); // throw (to help callers find troubleshooting comments)
}
}
}
var ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
var RESERVED_PROPS = {
key: true,
ref: true,
__self: true,
__source: true
};
var specialPropKeyWarningShown;
var specialPropRefWarningShown;
function hasValidRef(config) {
{
if (hasOwnProperty.call(config, 'ref')) {
var getter = Object.getOwnPropertyDescriptor(config, 'ref').get;
if (getter && getter.isReactWarning) {
return false;
}
}
}
return config.ref !== undefined;
}
function hasValidKey(config) {
{
if (hasOwnProperty.call(config, 'key')) {
var getter = Object.getOwnPropertyDescriptor(config, 'key').get;
if (getter && getter.isReactWarning) {
return false;
}
}
}
return config.key !== undefined;
}
function warnIfStringRefCannotBeAutoConverted(config, self) {
{
if (typeof config.ref === 'string' && ReactCurrentOwner.current && self) ;
}
}
function defineKeyPropWarningGetter(props, displayName) {
{
var warnAboutAccessingKey = function () {
if (!specialPropKeyWarningShown) {
specialPropKeyWarningShown = true;
error('%s: `key` is not a prop. Trying to access it will result ' + 'in `undefined` being returned. If you need to access the same ' + 'value within the child component, you should pass it as a different ' + 'prop. (https://reactjs.org/link/special-props)', displayName);
}
};
warnAboutAccessingKey.isReactWarning = true;
Object.defineProperty(props, 'key', {
get: warnAboutAccessingKey,
configurable: true
});
}
}
function defineRefPropWarningGetter(props, displayName) {
{
var warnAboutAccessingRef = function () {
if (!specialPropRefWarningShown) {
specialPropRefWarningShown = true;
error('%s: `ref` is not a prop. Trying to access it will result ' + 'in `undefined` being returned. If you need to access the same ' + 'value within the child component, you should pass it as a different ' + 'prop. (https://reactjs.org/link/special-props)', displayName);
}
};
warnAboutAccessingRef.isReactWarning = true;
Object.defineProperty(props, 'ref', {
get: warnAboutAccessingRef,
configurable: true
});
}
}
/**
* Factory method to create a new React element. This no longer adheres to
* the class pattern, so do not use new to call it. Also, instanceof check
* will not work. Instead test $$typeof field against Symbol.for('react.element') to check
* if something is a React Element.
*
* @param {*} type
* @param {*} props
* @param {*} key
* @param {string|object} ref
* @param {*} owner
* @param {*} self A *temporary* helper to detect places where `this` is
* different from the `owner` when React.createElement is called, so that we
* can warn. We want to get rid of owner and replace string `ref`s with arrow
* functions, and as long as `this` and owner are the same, there will be no
* change in behavior.
* @param {*} source An annotation object (added by a transpiler or otherwise)
* indicating filename, line number, and/or other information.
* @internal
*/
var ReactElement = function (type, key, ref, self, source, owner, props) {
var element = {
// This tag allows us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
type: type,
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
_owner: owner
};
{
// The validation flag is currently mutative. We put it on
// an external backing store so that we can freeze the whole object.
// This can be replaced with a WeakMap once they are implemented in
// commonly used development environments.
element._store = {}; // To make comparing ReactElements easier for testing purposes, we make
// the validation flag non-enumerable (where possible, which should
// include every environment we run tests in), so the test framework
// ignores it.
Object.defineProperty(element._store, 'validated', {
configurable: false,
enumerable: false,
writable: true,
value: false
}); // self and source are DEV only properties.
Object.defineProperty(element, '_self', {
configurable: false,
enumerable: false,
writable: false,
value: self
}); // Two elements created in two different places should be considered
// equal for testing purposes and therefore we hide it from enumeration.
Object.defineProperty(element, '_source', {
configurable: false,
enumerable: false,
writable: false,
value: source
});
if (Object.freeze) {
Object.freeze(element.props);
Object.freeze(element);
}
}
return element;
};
/**
* https://github.com/reactjs/rfcs/pull/107
* @param {*} type
* @param {object} props
* @param {string} key
*/
function jsxDEV(type, config, maybeKey, source, self) {
{
var propName; // Reserved names are extracted
var props = {};
var key = null;
var ref = null; // Currently, key can be spread in as a prop. This causes a potential
// issue if key is also explicitly declared (ie. <div {...props} key="Hi" />
// or <div key="Hi" {...props} /> ). We want to deprecate key spread,
// but as an intermediary step, we will use jsxDEV for everything except
// <div {...props} key="Hi" />, because we aren't currently able to tell if
// key is explicitly declared to be undefined or not.
if (maybeKey !== undefined) {
{
checkKeyStringCoercion(maybeKey);
}
key = '' + maybeKey;
}
if (hasValidKey(config)) {
{
checkKeyStringCoercion(config.key);
}
key = '' + config.key;
}
if (hasValidRef(config)) {
ref = config.ref;
warnIfStringRefCannotBeAutoConverted(config, self);
} // Remaining properties are added to a new props object
for (propName in config) {
if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
props[propName] = config[propName];
}
} // Resolve default props
if (type && type.defaultProps) {
var defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
if (key || ref) {
var displayName = typeof type === 'function' ? type.displayName || type.name || 'Unknown' : type;
if (key) {
defineKeyPropWarningGetter(props, displayName);
}
if (ref) {
defineRefPropWarningGetter(props, displayName);
}
}
return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
}
}
var ReactCurrentOwner$1 = ReactSharedInternals.ReactCurrentOwner;
var ReactDebugCurrentFrame$1 = ReactSharedInternals.ReactDebugCurrentFrame;
function setCurrentlyValidatingElement$1(element) {
{
if (element) {
var owner = element._owner;
var stack = describeUnknownElementTypeFrameInDEV(element.type, element._source, owner ? owner.type : null);
ReactDebugCurrentFrame$1.setExtraStackFrame(stack);
} else {
ReactDebugCurrentFrame$1.setExtraStackFrame(null);
}
}
}
var propTypesMisspellWarningShown;
{
propTypesMisspellWarningShown = false;
}
/**
* Verifies the object is a ReactElement.
* See https://reactjs.org/docs/react-api.html#isvalidelement
* @param {?object} object
* @return {boolean} True if `object` is a ReactElement.
* @final
*/
function isValidElement(object) {
{
return typeof object === 'object' && object !== null && object.$$typeof === REACT_ELEMENT_TYPE;
}
}
function getDeclarationErrorAddendum() {
{
if (ReactCurrentOwner$1.current) {
var name = getComponentNameFromType(ReactCurrentOwner$1.current.type);
if (name) {
return '\n\nCheck the render method of `' + name + '`.';
}
}
return '';
}
}
function getSourceInfoErrorAddendum(source) {
{
return '';
}
}
/**
* Warn if there's no key explicitly set on dynamic arrays of children or
* object keys are not valid. This allows us to keep track of children between
* updates.
*/
var ownerHasKeyUseWarning = {};
function getCurrentComponentErrorInfo(parentType) {
{
var info = getDeclarationErrorAddendum();
if (!info) {
var parentName = typeof parentType === 'string' ? parentType : parentType.displayName || parentType.name;
if (parentName) {
info = "\n\nCheck the top-level render call using <" + parentName + ">.";
}
}
return info;
}
}
/**
* Warn if the element doesn't have an explicit key assigned to it.
* This element is in an array. The array could grow and shrink or be
* reordered. All children that haven't already been validated are required to
* have a "key" property assigned to it. Error statuses are cached so a warning
* will only be shown once.
*
* @internal
* @param {ReactElement} element Element that requires a key.
* @param {*} parentType element's parent's type.
*/
function validateExplicitKey(element, parentType) {
{
if (!element._store || element._store.validated || element.key != null) {
return;
}
element._store.validated = true;
var currentComponentErrorInfo = getCurrentComponentErrorInfo(parentType);
if (ownerHasKeyUseWarning[currentComponentErrorInfo]) {
return;
}
ownerHasKeyUseWarning[currentComponentErrorInfo] = true; // Usually the current owner is the offender, but if it accepts children as a
// property, it may be the creator of the child that's responsible for
// assigning it a key.
var childOwner = '';
if (element && element._owner && element._owner !== ReactCurrentOwner$1.current) {
// Give the component that originally created this child.
childOwner = " It was passed a child from " + getComponentNameFromType(element._owner.type) + ".";
}
setCurrentlyValidatingElement$1(element);
error('Each child in a list should have a unique "key" prop.' + '%s%s See https://reactjs.org/link/warning-keys for more information.', currentComponentErrorInfo, childOwner);
setCurrentlyValidatingElement$1(null);
}
}
/**
* Ensure that every element either is passed in a static location, in an
* array with an explicit keys property defined, or in an object literal
* with valid key property.
*
* @internal
* @param {ReactNode} node Statically passed child of any type.
* @param {*} parentType node's parent's type.
*/
function validateChildKeys(node, parentType) {
{
if (typeof node !== 'object') {
return;
}
if (isArray(node)) {
for (var i = 0; i < node.length; i++) {
var child = node[i];
if (isValidElement(child)) {
validateExplicitKey(child, parentType);
}
}
} else if (isValidElement(node)) {
// This element was passed in a valid location.
if (node._store) {
node._store.validated = true;
}
} else if (node) {
var iteratorFn = getIteratorFn(node);
if (typeof iteratorFn === 'function') {
// Entry iterators used to provide implicit keys,
// but now we print a separate warning for them later.
if (iteratorFn !== node.entries) {
var iterator = iteratorFn.call(node);
var step;
while (!(step = iterator.next()).done) {
if (isValidElement(step.value)) {
validateExplicitKey(step.value, parentType);
}
}
}
}
}
}
}
/**
* Given an element, validate that its props follow the propTypes definition,
* provided by the type.
*
* @param {ReactElement} element
*/
function validatePropTypes(element) {
{
var type = element.type;
if (type === null || type === undefined || typeof type === 'string') {
return;
}
var propTypes;
if (typeof type === 'function') {
propTypes = type.propTypes;
} else if (typeof type === 'object' && (type.$$typeof === REACT_FORWARD_REF_TYPE || // Note: Memo only checks outer props here.
// Inner props are checked in the reconciler.
type.$$typeof === REACT_MEMO_TYPE)) {
propTypes = type.propTypes;
} else {
return;
}
if (propTypes) {
// Intentionally inside to avoid triggering lazy initializers:
var name = getComponentNameFromType(type);
checkPropTypes(propTypes, element.props, 'prop', name, element);
} else if (type.PropTypes !== undefined && !propTypesMisspellWarningShown) {
propTypesMisspellWarningShown = true; // Intentionally inside to avoid triggering lazy initializers:
var _name = getComponentNameFromType(type);
error('Component %s declared `PropTypes` instead of `propTypes`. Did you misspell the property assignment?', _name || 'Unknown');
}
if (typeof type.getDefaultProps === 'function' && !type.getDefaultProps.isReactClassApproved) {
error('getDefaultProps is only used on classic React.createClass ' + 'definitions. Use a static property named `defaultProps` instead.');
}
}
}
/**
* Given a fragment, validate that it can only be provided with fragment props
* @param {ReactElement} fragment
*/
function validateFragmentProps(fragment) {
{
var keys = Object.keys(fragment.props);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (key !== 'children' && key !== 'key') {
setCurrentlyValidatingElement$1(fragment);
error('Invalid prop `%s` supplied to `React.Fragment`. ' + 'React.Fragment can only have `key` and `children` props.', key);
setCurrentlyValidatingElement$1(null);
break;
}
}
if (fragment.ref !== null) {
setCurrentlyValidatingElement$1(fragment);
error('Invalid attribute `ref` supplied to `React.Fragment`.');
setCurrentlyValidatingElement$1(null);
}
}
}
var didWarnAboutKeySpread = {};
function jsxWithValidation(type, props, key, isStaticChildren, source, self) {
{
var validType = isValidElementType(type); // We warn in this case but don't throw. We expect the element creation to
// succeed and there will likely be errors in render.
if (!validType) {
var info = '';
if (type === undefined || typeof type === 'object' && type !== null && Object.keys(type).length === 0) {
info += ' You likely forgot to export your component from the file ' + "it's defined in, or you might have mixed up default and named imports.";
}
var sourceInfo = getSourceInfoErrorAddendum();
if (sourceInfo) {
info += sourceInfo;
} else {
info += getDeclarationErrorAddendum();
}
var typeString;
if (type === null) {
typeString = 'null';
} else if (isArray(type)) {
typeString = 'array';
} else if (type !== undefined && type.$$typeof === REACT_ELEMENT_TYPE) {
typeString = "<" + (getComponentNameFromType(type.type) || 'Unknown') + " />";
info = ' Did you accidentally export a JSX literal instead of a component?';
} else {
typeString = typeof type;
}
error('React.jsx: type is invalid -- expected a string (for ' + 'built-in components) or a class/function (for composite ' + 'components) but got: %s.%s', typeString, info);
}
var element = jsxDEV(type, props, key, source, self); // The result can be nullish if a mock or a custom function is used.
// TODO: Drop this when these are no longer allowed as the type argument.
if (element == null) {
return element;
} // Skip key warning if the type isn't valid since our key validation logic
// doesn't expect a non-string/function type and can throw confusing errors.
// We don't want exception behavior to differ between dev and prod.
// (Rendering will throw with a helpful message and as soon as the type is
// fixed, the key warnings will appear.)
if (validType) {
var children = props.children;
if (children !== undefined) {
if (isStaticChildren) {
if (isArray(children)) {
for (var i = 0; i < children.length; i++) {
validateChildKeys(children[i], type);
}
if (Object.freeze) {
Object.freeze(children);
}
} else {
error('React.jsx: Static children should always be an array. ' + 'You are likely explicitly calling React.jsxs or React.jsxDEV. ' + 'Use the Babel transform instead.');
}
} else {
validateChildKeys(children, type);
}
}
}
{
if (hasOwnProperty.call(props, 'key')) {
var componentName = getComponentNameFromType(type);
var keys = Object.keys(props).filter(function (k) {
return k !== 'key';
});
var beforeExample = keys.length > 0 ? '{key: someKey, ' + keys.join(': ..., ') + ': ...}' : '{key: someKey}';
if (!didWarnAboutKeySpread[componentName + beforeExample]) {
var afterExample = keys.length > 0 ? '{' + keys.join(': ..., ') + ': ...}' : '{}';
error('A props object containing a "key" prop is being spread into JSX:\n' + ' let props = %s;\n' + ' <%s {...props} />\n' + 'React keys must be passed directly to JSX without using spread:\n' + ' let props = %s;\n' + ' <%s key={someKey} {...props} />', beforeExample, componentName, afterExample, componentName);
didWarnAboutKeySpread[componentName + beforeExample] = true;
}
}
}
if (type === REACT_FRAGMENT_TYPE) {
validateFragmentProps(element);
} else {
validatePropTypes(element);
}
return element;
}
} // These two functions exist to still get child warnings in dev
// even with the prod transform. This means that jsxDEV is purely
// opt-in behavior for better messages but that we won't stop
// giving you warnings if you use production apis.
function jsxWithValidationStatic(type, props, key) {
{
return jsxWithValidation(type, props, key, true);
}
}
function jsxWithValidationDynamic(type, props, key) {
{
return jsxWithValidation(type, props, key, false);
}
}
var jsx = jsxWithValidationDynamic ; // we may want to special case jsxs internally to take advantage of static children.
// for now we can ship identical prod functions
var jsxs = jsxWithValidationStatic ;
reactJsxRuntime_development.Fragment = REACT_FRAGMENT_TYPE;
reactJsxRuntime_development.jsx = jsx;
reactJsxRuntime_development.jsxs = jsxs;
})();
}
return reactJsxRuntime_development;
}
if (process.env.NODE_ENV === 'production') {
jsxRuntime.exports = requireReactJsxRuntime_production_min();
} else {
jsxRuntime.exports = requireReactJsxRuntime_development();
}
var jsxRuntimeExports = jsxRuntime.exports;
function ContentEditor({ client, schemaId, contentId, onSave, onPublish, onCancel, className = '' }) {
const { schema } = useSchema(client, { schemaId });
const { content, updateContent, createContent, publishContent, loading, error } = useContent(client, {
schemaId,
contentId
});
const [formData, setFormData] = useState({});
const [validationErrors, setValidationErrors] = useState({});
const [isSaving, setIsSaving] = useState(false);
const [isPublishing, setIsPublishing] = useState(false);
// Initialize form data when schema or content changes
useEffect(() => {
if (schema) {
const initialData = {};
schema.fields.forEach(field => {
if (content?.data && content.data[field.name] !== undefined) {
initialData[field.name] = content.data[field.name];
}
else if (field.defaultValue !== undefined) {
initialData[field.name] = field.defaultValue;
}
else {
initialData[field.name] = getDefaultValueForType(field.type);
}
});
setFormData(initialData);
setValidationErrors({});
}
}, [schema, content]);
const getDefaultValueForType = (type) => {
switch (type) {
case 'text':
case 'textarea':
case 'url':
case 'color':
return '';
case 'number':
return 0;
case 'boolean':
return false;
case 'array':
return [];
case 'object':
return {};
case 'date':
case 'datetime':
return new Date().toISOString().split('T')[0];
default:
return null;
}
};
const handleFieldChange = useCallback((fieldName, value) => {
setFormData(prev => ({ ...prev, [fieldName]: value }));
// Clear validation error for this field
if (validationErrors[fieldName]) {
setValidationErrors(prev => {
const newErrors = { ...prev };
delete newErrors[fieldName];
return newErrors;
});
}
}, [validationErrors]);
const validateForm = useCallback(() => {
if (!schema)
return false;
const errors = {};
schema.fields.forEach(field => {
const value = formData[field.name];
const validation = validateField(field, value);
if (!validation.isValid) {
errors[field.name] = validation.errors[0]?.message || `${field.label} is invalid`;
}
});
setValidationErrors(errors);
return Object.keys(errors).length === 0;
}, [schema, formData]);
const handleSave = useCallback(async () => {
if (!validateForm())
return;
setIsSaving(true);
try {
let savedContent;
if (contentId) {
savedContent = await updateContent(contentId, { data: formData });
}
else {
savedContent = await createContent({
schemaId,
projectId: client['config'].projectId || 1,
data: formData
});
}
onSave?.(savedContent);
}
catch (err) {
console.error('Failed to save content:', err);
}
finally {
setIsSaving(false);
}
}, [validateForm, contentId, updateContent, createContent, formData, onSave, client]);
const handlePublish = useCallback(async () => {
if (!contentId)
return;
setIsPublishing(true);
try {
const publishedContent = await publishContent(contentId);
onPublish?.(publishedContent);
}
catch (err) {
console.error('Failed to publish content:', err);
}
finally {
setIsPublishing(false);
}
}, [contentId, publishContent, onPublish]);
const renderField = useCallback((field) => {
const value = formData[field.name] || '';
const error = validationErrors[field.name];
const commonProps = {
id: field.name,
name: field.name,
value: formatFieldValue(value, field.type),
onChange: (e) => {
const newValue = parseFieldValue(e.target.value, field.type);
handleFieldChange(field.name, newValue);
},
className: `w-full p-3 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 ${error ? 'border-red-500' : 'border-gray-300'}`,
placeholder: field.placeholder,
required: field.required
};
switch (field.type) {
case 'text':
case 'url':
case 'color':
return (jsxRuntimeExports.jsx("input", { ...commonProps, type: field.type === 'color' ? 'color' : 'text' }));
case 'textarea':
return (jsxRuntimeExports.jsx("textarea", { ...commonProps, rows: 4, value: typeof value === 'string' ? value : '' }));
case 'number':
return (jsxRuntimeExports.jsx("input", { ...commonProps, type: "number", value: typeof value === 'number' ? value : '', onChange: (e) => {
const numValue = e.target.value ? parseFloat(e.target.value) : 0;
handleFieldChange(field.name, numValue);
} }));
case 'boolean':
return (jsxRuntimeExports.jsxs("div", { className: "flex items-center", children: [jsxRuntimeExports.jsx("input", { type: "checkbox", id: field.name, checked: Boolean(value), onChange: (e) => handleFieldChange(field.name, e.target.checked), className: "w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500" }), jsxRuntimeExports.jsx("label", { htmlFor: field.name, className: "ml-2 text-sm text-gray-700", children: field.label })] }));
case 'select':
return (jsxRuntimeExports.jsxs("select", { ...commonProps, children: [jsxRuntimeExports.jsxs("option", { value: "", children: ["Select ", field.label] }), field.options?.map(option => (jsxRuntimeExports.jsx("option", { value: option.value, children: option.label }, option.value)))] }));
case 'date':
case 'datetime':
return (jsxRuntimeExports.jsx("input", { ...commonProps, type: field.type === 'datetime' ? 'datetime-local' : 'date', value: value instanceof Date ? value.toISOString().split('T')[0] : value }));
case 'array':
return (jsxRuntimeExports.jsxs("div", { children: [jsxRuntimeExports.jsx("input", { ...commonProps, type: "text", value: Array.isArray(value) ? value.join(', ') : '', onChange: (e) => {
const arrayValue = e.target.value ? e.target.value.split(',').map(v => v.trim()) : [];
handleFieldChange(field.name, arrayValue);
} }), jsxRuntimeExports.jsx("p", { className: "text-xs text-gray-500 mt-1", children: "Separate values with commas" })] }));
case 'object':
return (jsxRuntimeExports.jsx("textarea", { ...commonProps, rows: 4, value: typeof value === 'object' ? JSON.stringify(value, null, 2) : '', onChange: (e) => {
try {
const objValue = e.target.value ? JSON.parse(e.target.value) : {};
handleFieldChange(field.name, objValue);
}
catch {
// Invalid JSON, keep as string
handleFieldChange(field.name, e.target.value);
}
} }));
default:
return (jsxRuntimeExports.jsx("input", { ...commonProps, type: "text" }));
}
}, [formData, validationErrors, handleFieldChange]);
if (loading) {
return (jsxRuntimeExports.jsxs("div", { className: `flex items-center justify-center p-8 ${className}`, children: [jsxRuntimeExports.jsx("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600" }), jsxRuntimeExports.jsx("span", { className: "ml-2", children: "Loading..." })] }));
}
if (error) {
return (jsxRuntimeExports.jsx("div", { className: `p-4 bg-red-50 border border-red-200 rounded-lg ${className}`, children: jsxRuntimeExports.jsxs("p", { className: "text-red-600", children: ["Error: ", error.message] }) }));
}
if (!schema) {
return (jsxRuntimeExports.jsx("div", { className: `p-4 bg-yellow-50 border border-yellow-200 rounded-lg ${className}`, children: jsxRuntimeExports.jsx("p", { className: "text-yellow-600", children: "Schema not found" }) }));
}
return (jsxRuntimeExports.jsxs("div", { className: `space-y-6 ${className}`, children: [jsxRuntimeExports.jsxs("div", { className: "border-b border-gray-200 pb-4", children: [jsxRuntimeExports.jsx("h2", { className: "text-2xl font-bold text-gray-900", children: contentId ? 'Edit Content' : 'Create Content' }), jsxRuntimeExports.jsx("p", { className: "text-gray-600 mt-1", children: schema.name }), schema.description && (jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-500 mt-2", children: schema.description }))] }), jsxRuntimeExports.jsxs("form", { className: "space-y-6", onSubmit: (e) => { e.preventDefault(); handleSave(); }, children: [schema.fields
.sort((a, b) => a.order - b.order)
.map(field => (jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [jsxRuntimeExports.jsxs("label", { htmlFor: field.name, className: "block text-sm font-medium text-gray-700", children: [field.label, field.required && jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" })] }), renderField(field), validationErrors[field.name] && (jsxRuntimeExports.jsx("p", { className: "text-sm text-red-600", children: validationErrors[field.name] })), field.helpText && (jsxRuntimeExports.jsx("p", { className: "text-xs text-gray-500", children: field.helpText }))] }, field.name))), jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-end space-x-3 pt-6 border-t border-gray-200", children: [onCancel && (jsxRuntimeExports.jsx("button", { type: "button", onClick: onCancel, className: "px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500", children: "Cancel" })), jsxRuntimeExports.jsx("button", { type: "submit", disabled: isSaving, className: "px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50", children: isSaving ? 'Saving...' : 'Save' }), contentId && content && !content.isPublished && onPublish && (jsxRuntimeExports.jsx("button", { type: "button", onClick: handlePublish, disabled: isPublishing, className: "px-4 py-2 text-sm font-medium text-white bg-green-600 border border-transparent rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 disabled:opacity-50", children: isPublishing ? 'Publishing...' : 'Publish' }))] })] })] }));
}
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
this.setState({ error, errorInfo });
if (this.props.onError) {
this.props.onError(error, errorInfo);
}
// Log error to console in development
if (process.env.NODE_ENV === 'development') {
console.error('ErrorBoundary caught an error:', error, errorInfo);
}
}
render() {
if (this.state.hasError) {
if (typeof this.props.fallback === 'function') {
return this.props.fallback(this.state.error, this.state.errorInfo);
}
if (this.props.fallback) {
return this.props.fallback;
}
// Default fallback UI
return (jsxRuntimeExports.jsxs("div", { style: {
padding: '20px',
border: '1px solid #ff6b6b',
borderRadius: '8px',
backgroundColor: '#fff5f5',
color: '#c53030'
}, children: [jsxRuntimeExports.jsx("h3", { children: "Something went wrong" }), jsxRuntimeExports.jsx("p", { children: "An error occurred while rendering this component." }), process.env.NODE_ENV === 'development' && this.state.error && (jsxRuntimeExports.jsxs("details", { style: { marginTop: '10px' }, children: [jsxRuntimeExports.jsx("summary", { children: "Error Details" }), jsxRuntimeExports.jsx("pre", { style: {
fontSize: '12px',
overflow: 'auto',
backgroundColor: '#f7fafc',
padding: '10px',
borderRadius: '4px',
marginTop: '10px'
}, children: this.state.error.toString() })] }))] }));
}
return this.props.children;
}
}
class PerformanceMonitor {
constructor() {
this.metrics = [];
this.maxMetrics = 1000; // Keep last 1000 metrics
}
recordMetric(metric) {
const fullMetric = {
...metric,
timestamp: Date.now()
};
this.metrics.push(fullMetric);
// Keep only the last maxMetrics
if (this.metrics.length > this.maxMetrics) {
this.metrics = this.metrics.slice(-this.maxMetrics);
}
}
getStats(timeWindow) {
const now = Date.now();
const relevantMetrics = timeWindow
? this.metrics.filter(m => now - m.timestamp < timeWindow)
: this.metrics;
if (relevantMetrics.length === 0) {
return {
totalCalls: 0,
averageDuration: 0,
successRate: 0,
slowestOperation: null,
fastestOperation: null
};
}
const successfulCalls = relevantMetrics.filter(m => m.success);
const durations = relevantMetrics.map(m => m.duration);
const slowest = relevantMetrics.reduce((max, current) => current.duration > max.duration ? current : max);
const fastest = relevantMetrics.reduce((min, current) => current.duration < min.duration ? current : min);
return {
totalCalls: relevantMetrics.length,
averageDuration: durations.reduce((sum, duration) => sum + duration, 0) / durations.length,
successRate: successfulCalls.length / relevantMetrics.length,
slowestOperation: slowest,
fastestOperation: fastest
};
}
getOperationStats(operation, timeWindow) {
const now = Date.now();
const operationMetrics = this.metrics.filter(m => m.operation === operation &&
(!timeWindow || now - m.timestamp < timeWindow));
if (operationMetrics.length === 0) {
return {
totalCalls: 0,
averageDuration: 0,
successRate: 0,
slowestOperation: null,
fastestOperation: null
};
}
const successfulCalls = operationMetrics.filter(m => m.success);
const durations = operationMetrics.map(m => m.duration);
const slowest = operationMetrics.reduce((max, current) => current.duration > max.duration ? current : max);
const fastest = operationMetrics.reduce((min, current) => current.duration < min.duration ? current : min);
return {
totalCalls: operationMetrics.length,
averageDuration: durations.reduce((sum, duration) => sum + duration, 0) / durations.length,
successRate: successfulCalls.length / operationMetrics.length,
slowestOperation: slowest,
fastestOperation: fastest
};
}
clear() {
this.metrics = [];
}
exportMetrics() {
return [...this.metrics];
}
}
// Global performance monitor instance
const performanceMonitor = new PerformanceMonitor();
// Utility function to measure async operations
async function measureAsync(operation, fn) {
const startTime = Date.now();
try {
const result = await fn();
performanceMonitor.recordMetric({
operation,
duration: Date.now() - startTime,
success: true
});
return result;
}
catch (error) {
performanceMonitor.recordMetric({
operation,
duration: Date.now() - startTime,
success: false,
error: error instanceof Error ? error.message : String(error)
});
throw error;
}
}
// Utility function to measure sync operations
function measureSync(operation, fn) {
const startTime = Date.now();
try {
const result = fn();
performanceMonitor.recordMetric({
operation,
duration: Date.now() - startTime,
success: true
});
return result;
}
catch (error) {
performanceMonitor.recordMetric({
operation,
duration: Date.now() - startTime,
success: false,
error: error instanceof Error ? error.message : String(error)
});
throw error;
}
}
export { API_ENDPOINTS, ContentCache, ContentEditor, DEFAULT_CONFIG, ERROR_CODES, ErrorBoundary, FIELD_TYPES, LRUCache, PerformanceMonitor, REALTIME_EVENTS, ResolvoCMSClient, SchemaCache, VALIDATION_TYPES, ValidationError, createZodSchema, ResolvoCMSClient as default, deserializeContent, formatFieldValue, generateFieldId, measureAsync, measureSync, parseFieldValue, performanceMonitor, sanitizeFieldName, serializeContent, useContent, useContentSubscription, useOptimisticContent, useOptimisticRealtime, useRealtime, useSchema, validateContent, validateField };
//# sourceMappingURL=index.esm.js.map