@mdxui/do 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +412 -0
- package/dist/__test-utils__/index.d.ts +399 -0
- package/dist/__test-utils__/index.js +34641 -0
- package/dist/__test-utils__/index.js.map +1 -0
- package/dist/agents-xcIn2dUB.d.ts +832 -0
- package/dist/chunk-EEDMN7UF.js +1351 -0
- package/dist/chunk-EEDMN7UF.js.map +1 -0
- package/dist/chunk-G3PMV62Z.js +33 -0
- package/dist/chunk-G3PMV62Z.js.map +1 -0
- package/dist/chunk-GGO5GW72.js +695 -0
- package/dist/chunk-GGO5GW72.js.map +1 -0
- package/dist/chunk-GKSP5RIA.js +3 -0
- package/dist/chunk-GKSP5RIA.js.map +1 -0
- package/dist/chunk-NXPXL5NA.js +3789 -0
- package/dist/chunk-NXPXL5NA.js.map +1 -0
- package/dist/chunk-PC5FJY6M.js +20 -0
- package/dist/chunk-PC5FJY6M.js.map +1 -0
- package/dist/chunk-XF6LKY2M.js +445 -0
- package/dist/chunk-XF6LKY2M.js.map +1 -0
- package/dist/components/index.d.ts +813 -0
- package/dist/components/index.js +8 -0
- package/dist/components/index.js.map +1 -0
- package/dist/do-CaQVueZw.d.ts +195 -0
- package/dist/hooks/index.d.ts +801 -0
- package/dist/hooks/index.js +7 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/index.d.ts +1012 -0
- package/dist/index.js +843 -0
- package/dist/index.js.map +1 -0
- package/dist/magic-string.es-J7BYFTTJ.js +1307 -0
- package/dist/magic-string.es-J7BYFTTJ.js.map +1 -0
- package/dist/providers/index.d.ts +90 -0
- package/dist/providers/index.js +5 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/schemas/index.d.ts +206 -0
- package/dist/schemas/index.js +262 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/thing-DtI25yZh.d.ts +902 -0
- package/dist/types/index.d.ts +7681 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +94 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// src/types/index.ts
|
|
2
|
+
function isValidationErrorData(error) {
|
|
3
|
+
return error.code === "VALIDATION_ERROR";
|
|
4
|
+
}
|
|
5
|
+
function isNotFoundError(error) {
|
|
6
|
+
return error.code === "NOT_FOUND";
|
|
7
|
+
}
|
|
8
|
+
function isPermissionError(error) {
|
|
9
|
+
return error.code === "PERMISSION_DENIED";
|
|
10
|
+
}
|
|
11
|
+
function isRateLimitError(error) {
|
|
12
|
+
return error.code === "RATE_LIMITED";
|
|
13
|
+
}
|
|
14
|
+
function isAppError(error) {
|
|
15
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" && "message" in error && typeof error.message === "string";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { isAppError, isNotFoundError, isPermissionError, isRateLimitError, isValidationErrorData };
|
|
19
|
+
//# sourceMappingURL=chunk-PC5FJY6M.js.map
|
|
20
|
+
//# sourceMappingURL=chunk-PC5FJY6M.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types/index.ts"],"names":[],"mappings":";AAqPO,SAAS,sBAAsB,KAAA,EAA+C;AACnF,EAAA,OAAO,MAAM,IAAA,KAAS,kBAAA;AACxB;AAGO,SAAS,gBAAgB,KAAA,EAAyC;AACvE,EAAA,OAAO,MAAM,IAAA,KAAS,WAAA;AACxB;AAGO,SAAS,kBAAkB,KAAA,EAA2C;AAC3E,EAAA,OAAO,MAAM,IAAA,KAAS,mBAAA;AACxB;AAGO,SAAS,iBAAiB,KAAA,EAA0C;AACzE,EAAA,OAAO,MAAM,IAAA,KAAS,cAAA;AACxB;AAmBO,SAAS,WAAW,KAAA,EAAmC;AAC5D,EAAA,OACE,OAAO,KAAA,KAAU,QAAA,IACjB,KAAA,KAAU,QACV,MAAA,IAAU,KAAA,IACV,OAAQ,KAAA,CAAmB,SAAS,QAAA,IACpC,SAAA,IAAa,KAAA,IACb,OAAQ,MAAmB,OAAA,KAAY,QAAA;AAE3C","file":"chunk-PC5FJY6M.js","sourcesContent":["/**\n * @mdxui/do Type Definitions\n *\n * Comprehensive type system for the .do platform admin interface.\n * Covers all domain entities: DOs, Things, Relationships, Functions,\n * Workflows, Agents, Events, Integrations, and Auth.\n */\n\n// Common types (shared across domains)\nexport * from './common'\nimport type { SortDirection } from './common'\n\n// Durable Objects management\nexport * from './do'\n\n// Things/Nouns (semantic entities)\nexport * from './things'\n\n// Relationships (semantic graph)\n// Note: EntityReference is exported from ./common (re-exported by ./relationships for domain API)\nexport type {\n Relationship,\n SemanticPredicateCategory,\n SemanticPredicate,\n PredicateDefinition,\n RelationshipMetadata,\n SemanticTriple,\n GraphPattern,\n RelationshipCreateInput,\n RelationshipFilter,\n GraphTraversalResult,\n GraphPath,\n GraphStats,\n} from './relationships'\n\n// Functions (Code, Generative, Agentic, Human)\n// Note: TokenUsage and ExecutionError are exported from ./common (re-exported by ./functions for domain API)\nexport type {\n FunctionCategory,\n FunctionBase,\n CodeFunction,\n AIModel,\n GenerativeFunction,\n ToolDefinition,\n AgenticFunction,\n AgentClassification as FunctionAgentClassification,\n AgentGuardrail as FunctionAgentGuardrail,\n HandoffRule as FunctionHandoffRule,\n HumanFunction,\n EscalationRule,\n ReminderRule,\n Function,\n FunctionExecutionStatus,\n FunctionExecution,\n FunctionFilter,\n} from './functions'\n\n// Workflows (business process orchestration)\nexport * from './workflows'\n\n// Agents (autonomous AI workers) - these are the main agent types\nexport * from './agents'\n\n// Events (operation log & semantic events)\n// Note: EntityReference is exported from ./common (re-exported by ./events for domain API)\nexport type {\n OperationEventType,\n OperationEvent,\n EventMetadata,\n SemanticEvent,\n EventSource,\n EventDefinition,\n EventSubscription,\n EventFilter,\n EventQueryResult,\n EventStream,\n EventReplayRequest,\n ReplayTarget,\n EventStats,\n EventTimelineEntry,\n} from './events'\n\n// Integrations (external service connections)\nexport * from './integrations'\n\n// Auth (Users, Orgs, APIKeys)\nexport * from './auth'\n\n// Database Editor (Supabase-style spreadsheet UI)\nexport * from './database-editor'\n\n// Zod Validation Schemas\n// Note: types/schemas.ts contains Zod schemas for input validation and response parsing.\n// For the new schema-first approach where types are derived from schemas,\n// import from '@mdxui/do/schemas' instead.\nexport * from './schemas'\n\n/**\n * Common utility types\n */\n\n/** Pagination parameters */\nexport interface PaginationParams {\n page?: number\n perPage?: number\n cursor?: string\n}\n\n/** Paginated result */\nexport interface PaginatedResult<T> {\n data: T[]\n total: number\n page: number\n perPage: number\n totalPages: number\n hasMore: boolean\n cursor?: string\n}\n\n/** Sort parameters */\nexport interface SortParams<T = string> {\n field: T\n order: SortDirection\n}\n\n/** Date range filter */\nexport interface DateRangeFilter {\n after?: Date\n before?: Date\n}\n\n/** Common record metadata */\nexport interface RecordMetadata {\n createdAt: Date\n updatedAt: Date\n createdBy?: string\n updatedBy?: string\n}\n\n/** Soft-deletable record */\nexport interface SoftDeletable {\n deletedAt?: Date | null\n deletedBy?: string\n}\n\n/** Audit trail entry */\nexport interface AuditEntry {\n action: 'create' | 'update' | 'delete' | 'read'\n userId: string\n timestamp: Date\n resourceType: string\n resourceId: string\n changes?: Record<string, { old: unknown; new: unknown }>\n metadata?: Record<string, unknown>\n}\n\n/**\n * Semantic ID helpers\n */\n\n/** Parse a semantic ID (ns/type/id) into components */\nexport interface SemanticIdComponents {\n ns: string\n type: string\n id: string\n}\n\n/** Build a semantic ID from components */\nexport type SemanticId = `${string}/${string}/${string}`\n\n/**\n * Error types\n */\n\n/** Base error */\nexport interface BaseError {\n code: string\n message: string\n details?: Record<string, unknown>\n}\n\n/**\n * Validation error data structure (interface)\n *\n * This interface represents validation error data in API responses.\n * For throwing validation errors in code, use the `ValidationError` class\n * from the main package export.\n *\n * @see ValidationError class in lib/errors.ts for throwable errors\n */\nexport interface ValidationErrorData extends BaseError {\n code: 'VALIDATION_ERROR'\n field?: string\n errors: Array<{\n field: string\n message: string\n code: string\n }>\n}\n\n/** Not found error */\nexport interface NotFoundError extends BaseError {\n code: 'NOT_FOUND'\n resourceType: string\n resourceId: string\n}\n\n/** Permission error */\nexport interface PermissionError extends BaseError {\n code: 'PERMISSION_DENIED'\n requiredPermission: string\n resource?: string\n}\n\n/** Rate limit error */\nexport interface RateLimitError extends BaseError {\n code: 'RATE_LIMITED'\n retryAfter: number\n limit: number\n remaining: number\n}\n\n/** Union of all error types */\nexport type AppError = ValidationErrorData | NotFoundError | PermissionError | RateLimitError | BaseError\n\n/**\n * Type guard functions for AppError discriminated union\n *\n * These functions enable type-safe narrowing when handling errors:\n *\n * @example\n * ```typescript\n * function handleError(error: AppError) {\n * if (isValidationErrorData(error)) {\n * // TypeScript knows error.errors exists here\n * console.log(error.errors)\n * } else if (isNotFoundError(error)) {\n * // TypeScript knows error.resourceType and resourceId exist here\n * console.log(`${error.resourceType}/${error.resourceId} not found`)\n * }\n * }\n * ```\n */\n\n/** Type guard for ValidationErrorData */\nexport function isValidationErrorData(error: AppError): error is ValidationErrorData {\n return error.code === 'VALIDATION_ERROR'\n}\n\n/** Type guard for NotFoundError */\nexport function isNotFoundError(error: AppError): error is NotFoundError {\n return error.code === 'NOT_FOUND'\n}\n\n/** Type guard for PermissionError */\nexport function isPermissionError(error: AppError): error is PermissionError {\n return error.code === 'PERMISSION_DENIED'\n}\n\n/** Type guard for RateLimitError */\nexport function isRateLimitError(error: AppError): error is RateLimitError {\n return error.code === 'RATE_LIMITED'\n}\n\n/**\n * Generic type guard to check if an unknown value is an AppError\n *\n * Useful for catch blocks where the error type is unknown:\n *\n * @example\n * ```typescript\n * try {\n * await someOperation()\n * } catch (error) {\n * if (isAppError(error)) {\n * // TypeScript knows error is AppError here\n * console.log(error.code, error.message)\n * }\n * }\n * ```\n */\nexport function isAppError(error: unknown): error is AppError {\n return (\n typeof error === 'object' &&\n error !== null &&\n 'code' in error &&\n typeof (error as AppError).code === 'string' &&\n 'message' in error &&\n typeof (error as AppError).message === 'string'\n )\n}\n"]}
|
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
import { clsx } from 'clsx';
|
|
2
|
+
import { twMerge } from 'tailwind-merge';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
5
|
+
import { QueryClientProvider, QueryClient } from '@tanstack/react-query';
|
|
6
|
+
import { DO, useConnectionState, useDotdoContext } from '@dotdo/react';
|
|
7
|
+
import { DataProviderContext } from '@mdxui/app';
|
|
8
|
+
import { jsx } from 'react/jsx-runtime';
|
|
9
|
+
|
|
10
|
+
// src/lib/fetch-with-timeout.ts
|
|
11
|
+
var DEFAULT_REQUEST_TIMEOUT = 3e4;
|
|
12
|
+
async function fetchWithTimeout(url, options = {}) {
|
|
13
|
+
const { timeout = DEFAULT_REQUEST_TIMEOUT, signal: existingSignal, ...fetchOptions } = options;
|
|
14
|
+
const controller = new AbortController();
|
|
15
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
16
|
+
const existingAbortHandler = existingSignal ? () => controller.abort(existingSignal.reason) : null;
|
|
17
|
+
if (existingSignal) {
|
|
18
|
+
if (existingSignal.aborted) {
|
|
19
|
+
clearTimeout(timeoutId);
|
|
20
|
+
controller.abort(existingSignal.reason);
|
|
21
|
+
} else {
|
|
22
|
+
existingSignal.addEventListener("abort", existingAbortHandler);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const response = await fetch(url, {
|
|
27
|
+
...fetchOptions,
|
|
28
|
+
signal: controller.signal
|
|
29
|
+
});
|
|
30
|
+
return response;
|
|
31
|
+
} finally {
|
|
32
|
+
clearTimeout(timeoutId);
|
|
33
|
+
if (existingSignal && existingAbortHandler) {
|
|
34
|
+
existingSignal.removeEventListener("abort", existingAbortHandler);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function cn(...inputs) {
|
|
39
|
+
return twMerge(clsx(inputs));
|
|
40
|
+
}
|
|
41
|
+
function formatSemanticId(ns, type, id) {
|
|
42
|
+
return `${ns}/${type}/${id}`;
|
|
43
|
+
}
|
|
44
|
+
function parseSemanticId(semanticId) {
|
|
45
|
+
const parts = semanticId.split("/");
|
|
46
|
+
if (parts.length < 3) {
|
|
47
|
+
throw new Error(`Invalid semantic ID format: ${semanticId}`);
|
|
48
|
+
}
|
|
49
|
+
const type = parts[0];
|
|
50
|
+
const id = parts[parts.length - 1];
|
|
51
|
+
const namespace = parts.slice(1, -1).join("/");
|
|
52
|
+
return { type, namespace, id };
|
|
53
|
+
}
|
|
54
|
+
function formatDate(date, options) {
|
|
55
|
+
const d = typeof date === "string" ? new Date(date) : date;
|
|
56
|
+
return d.toLocaleDateString(void 0, options ?? {
|
|
57
|
+
year: "numeric",
|
|
58
|
+
month: "short",
|
|
59
|
+
day: "numeric"
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function formatDateTime(date, options) {
|
|
63
|
+
const d = typeof date === "string" ? new Date(date) : date;
|
|
64
|
+
return d.toLocaleString(void 0, options ?? {
|
|
65
|
+
year: "numeric",
|
|
66
|
+
month: "short",
|
|
67
|
+
day: "numeric",
|
|
68
|
+
hour: "2-digit",
|
|
69
|
+
minute: "2-digit"
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function formatRelativeTime(date) {
|
|
73
|
+
const d = typeof date === "string" ? new Date(date) : date;
|
|
74
|
+
const now = /* @__PURE__ */ new Date();
|
|
75
|
+
const diffMs = now.getTime() - d.getTime();
|
|
76
|
+
const diffSec = Math.floor(diffMs / 1e3);
|
|
77
|
+
const diffMin = Math.floor(diffSec / 60);
|
|
78
|
+
const diffHour = Math.floor(diffMin / 60);
|
|
79
|
+
const diffDay = Math.floor(diffHour / 24);
|
|
80
|
+
if (diffSec < 60) return `${diffSec} ${diffSec === 1 ? "second" : "seconds"} ago`;
|
|
81
|
+
if (diffMin < 60) return `${diffMin} ${diffMin === 1 ? "minute" : "minutes"} ago`;
|
|
82
|
+
if (diffHour < 24) return `${diffHour} ${diffHour === 1 ? "hour" : "hours"} ago`;
|
|
83
|
+
if (diffDay < 7) return `${diffDay} ${diffDay === 1 ? "day" : "days"} ago`;
|
|
84
|
+
return formatDate(d);
|
|
85
|
+
}
|
|
86
|
+
function formatDecimal(value, decimals = 1) {
|
|
87
|
+
const fixed = value.toFixed(decimals);
|
|
88
|
+
return fixed.endsWith(".0") ? fixed.slice(0, -2) : fixed;
|
|
89
|
+
}
|
|
90
|
+
function formatNumber(num) {
|
|
91
|
+
if (num < 0) return `-${formatNumber(-num)}`;
|
|
92
|
+
if (num >= 1e6) return `${formatDecimal(num / 1e6)}M`;
|
|
93
|
+
if (num >= 1e3) return `${formatDecimal(num / 1e3)}K`;
|
|
94
|
+
return num.toString();
|
|
95
|
+
}
|
|
96
|
+
function formatBytes(bytes) {
|
|
97
|
+
if (bytes === 0) return "0 B";
|
|
98
|
+
const k = 1024;
|
|
99
|
+
const sizes = ["B", "KB", "MB", "GB", "TB"];
|
|
100
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
101
|
+
return `${formatDecimal(bytes / Math.pow(k, i))} ${sizes[i]}`;
|
|
102
|
+
}
|
|
103
|
+
function formatDuration(ms) {
|
|
104
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
105
|
+
if (ms < 6e4) return `${formatDecimal(ms / 1e3)}s`;
|
|
106
|
+
if (ms < 36e5) return `${Math.floor(ms / 6e4)}m ${Math.floor(ms % 6e4 / 1e3)}s`;
|
|
107
|
+
return `${Math.floor(ms / 36e5)}h ${Math.floor(ms % 36e5 / 6e4)}m`;
|
|
108
|
+
}
|
|
109
|
+
function truncate(text, maxLength, suffix = "...") {
|
|
110
|
+
if (text.length <= maxLength) return text;
|
|
111
|
+
return text.slice(0, maxLength) + suffix;
|
|
112
|
+
}
|
|
113
|
+
function capitalize(text) {
|
|
114
|
+
return text.charAt(0).toUpperCase() + text.slice(1);
|
|
115
|
+
}
|
|
116
|
+
function camelToTitle(text) {
|
|
117
|
+
return text.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase()).trim();
|
|
118
|
+
}
|
|
119
|
+
function generateId(prefix) {
|
|
120
|
+
const id = Math.random().toString(36).substring(2, 15);
|
|
121
|
+
return prefix ? `${prefix}_${id}` : id;
|
|
122
|
+
}
|
|
123
|
+
function debounce(fn, delay) {
|
|
124
|
+
let timeoutId;
|
|
125
|
+
return (...args) => {
|
|
126
|
+
clearTimeout(timeoutId);
|
|
127
|
+
timeoutId = setTimeout(() => fn(...args), delay);
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function groupBy(array, keyFn) {
|
|
131
|
+
return array.reduce((acc, item) => {
|
|
132
|
+
const key = keyFn(item);
|
|
133
|
+
if (!acc[key]) acc[key] = [];
|
|
134
|
+
acc[key].push(item);
|
|
135
|
+
return acc;
|
|
136
|
+
}, {});
|
|
137
|
+
}
|
|
138
|
+
async function retryWithBackoff(fn, config = {}) {
|
|
139
|
+
const { maxRetries = 3, backoffMultiplier = 1e3, signal, onRetry } = config;
|
|
140
|
+
let lastError;
|
|
141
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
142
|
+
if (signal?.aborted) {
|
|
143
|
+
throw lastError ?? new Error("Retry cancelled");
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
return await fn();
|
|
147
|
+
} catch (error) {
|
|
148
|
+
lastError = error instanceof Error ? error : new Error("Retry failed");
|
|
149
|
+
if (attempt < maxRetries - 1) {
|
|
150
|
+
onRetry?.(lastError, attempt + 1);
|
|
151
|
+
const delay = Math.pow(2, attempt) * backoffMultiplier;
|
|
152
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
153
|
+
if (signal?.aborted) {
|
|
154
|
+
throw lastError ?? new Error("Retry cancelled");
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
throw lastError;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// src/lib/url-utils.ts
|
|
163
|
+
function deriveRpcUrl(doUrl) {
|
|
164
|
+
let rpcUrl = doUrl.replace(/^wss:/, "https:").replace(/^ws:/, "http:");
|
|
165
|
+
rpcUrl = rpcUrl.replace(/\/sync$/, "");
|
|
166
|
+
return `${rpcUrl}/rpc`;
|
|
167
|
+
}
|
|
168
|
+
function deriveSyncUrl(doUrl) {
|
|
169
|
+
if (doUrl.endsWith("/sync")) {
|
|
170
|
+
return doUrl;
|
|
171
|
+
}
|
|
172
|
+
return `${doUrl}/sync`;
|
|
173
|
+
}
|
|
174
|
+
function useHealthCheck(config) {
|
|
175
|
+
const [isConnected, setIsConnected] = useState(false);
|
|
176
|
+
const [connectionError, setConnectionError] = useState();
|
|
177
|
+
const [isChecking, setIsChecking] = useState(true);
|
|
178
|
+
const { apiEndpoint, authToken, requestTimeout, healthCheckRetries = 3 } = config;
|
|
179
|
+
const abortControllerRef = useRef(null);
|
|
180
|
+
const performHealthCheck = useCallback(async () => {
|
|
181
|
+
const response = await fetchWithTimeout(`${apiEndpoint}/health`, {
|
|
182
|
+
method: "GET",
|
|
183
|
+
headers: {
|
|
184
|
+
"Content-Type": "application/json",
|
|
185
|
+
...authToken ? { Authorization: `Bearer ${authToken}` } : {}
|
|
186
|
+
},
|
|
187
|
+
timeout: requestTimeout ?? DEFAULT_REQUEST_TIMEOUT
|
|
188
|
+
});
|
|
189
|
+
if (!response.ok) {
|
|
190
|
+
throw new Error(`Health check failed: ${response.status}`);
|
|
191
|
+
}
|
|
192
|
+
}, [apiEndpoint, authToken, requestTimeout]);
|
|
193
|
+
const checkConnection = useCallback(async () => {
|
|
194
|
+
setIsChecking(true);
|
|
195
|
+
try {
|
|
196
|
+
await retryWithBackoff(performHealthCheck, {
|
|
197
|
+
maxRetries: healthCheckRetries,
|
|
198
|
+
backoffMultiplier: 1e3
|
|
199
|
+
});
|
|
200
|
+
setIsConnected(true);
|
|
201
|
+
setConnectionError(void 0);
|
|
202
|
+
} catch (error) {
|
|
203
|
+
setIsConnected(false);
|
|
204
|
+
setConnectionError(error instanceof Error ? error : new Error("Connection failed"));
|
|
205
|
+
} finally {
|
|
206
|
+
setIsChecking(false);
|
|
207
|
+
}
|
|
208
|
+
}, [performHealthCheck, healthCheckRetries]);
|
|
209
|
+
useEffect(() => {
|
|
210
|
+
abortControllerRef.current = new AbortController();
|
|
211
|
+
const { signal } = abortControllerRef.current;
|
|
212
|
+
async function runCheck() {
|
|
213
|
+
try {
|
|
214
|
+
await retryWithBackoff(performHealthCheck, {
|
|
215
|
+
maxRetries: healthCheckRetries,
|
|
216
|
+
backoffMultiplier: 1e3,
|
|
217
|
+
signal
|
|
218
|
+
});
|
|
219
|
+
if (!signal.aborted) {
|
|
220
|
+
setIsConnected(true);
|
|
221
|
+
setConnectionError(void 0);
|
|
222
|
+
setIsChecking(false);
|
|
223
|
+
}
|
|
224
|
+
} catch (error) {
|
|
225
|
+
if (!signal.aborted) {
|
|
226
|
+
setIsConnected(false);
|
|
227
|
+
setConnectionError(error instanceof Error ? error : new Error("Connection failed"));
|
|
228
|
+
setIsChecking(false);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
runCheck();
|
|
233
|
+
return () => {
|
|
234
|
+
abortControllerRef.current?.abort();
|
|
235
|
+
};
|
|
236
|
+
}, [performHealthCheck, healthCheckRetries]);
|
|
237
|
+
return {
|
|
238
|
+
isConnected,
|
|
239
|
+
connectionError,
|
|
240
|
+
checkConnection,
|
|
241
|
+
isChecking
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
var DEFAULT_COLLECTIONS = ["Thing", "Agent", "Workflow"];
|
|
245
|
+
function createQueryClient() {
|
|
246
|
+
return new QueryClient({
|
|
247
|
+
defaultOptions: {
|
|
248
|
+
queries: {
|
|
249
|
+
// Stale time: 30 seconds (data considered fresh)
|
|
250
|
+
staleTime: 30 * 1e3,
|
|
251
|
+
// Cache time: 5 minutes (keep in cache after unmount)
|
|
252
|
+
gcTime: 5 * 60 * 1e3,
|
|
253
|
+
// Retry failed requests 1 time
|
|
254
|
+
retry: 1,
|
|
255
|
+
// Refetch on window focus for fresh data
|
|
256
|
+
refetchOnWindowFocus: true
|
|
257
|
+
},
|
|
258
|
+
mutations: {
|
|
259
|
+
// Retry mutations once on failure
|
|
260
|
+
retry: 1
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
function mapConnectionStateToSyncStatus(state) {
|
|
266
|
+
switch (state) {
|
|
267
|
+
case "connecting":
|
|
268
|
+
return "connecting";
|
|
269
|
+
case "connected":
|
|
270
|
+
return "synced";
|
|
271
|
+
case "reconnecting":
|
|
272
|
+
return "syncing";
|
|
273
|
+
case "disconnected":
|
|
274
|
+
return "disconnected";
|
|
275
|
+
default:
|
|
276
|
+
return "disconnected";
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
var DOContext = React.createContext(null);
|
|
280
|
+
function useDO() {
|
|
281
|
+
const context = React.useContext(DOContext);
|
|
282
|
+
if (!context) {
|
|
283
|
+
throw new Error("useDO must be used within a DOProvider");
|
|
284
|
+
}
|
|
285
|
+
return context;
|
|
286
|
+
}
|
|
287
|
+
function useDOUrls() {
|
|
288
|
+
const { rpcUrl, syncUrl, config } = useDO();
|
|
289
|
+
return {
|
|
290
|
+
rpcUrl,
|
|
291
|
+
syncUrl,
|
|
292
|
+
apiEndpoint: config.apiEndpoint,
|
|
293
|
+
doUrl: config.doUrl ?? null
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
function useSyncStatus() {
|
|
297
|
+
const { syncStatus } = useDO();
|
|
298
|
+
return syncStatus;
|
|
299
|
+
}
|
|
300
|
+
function DOProviderInner({
|
|
301
|
+
config,
|
|
302
|
+
namespace,
|
|
303
|
+
setNamespace,
|
|
304
|
+
userId,
|
|
305
|
+
isConnected,
|
|
306
|
+
connectionError,
|
|
307
|
+
rpcUrl,
|
|
308
|
+
syncUrl,
|
|
309
|
+
isTanStackDBMode,
|
|
310
|
+
collections,
|
|
311
|
+
children
|
|
312
|
+
}) {
|
|
313
|
+
const connectionState = useConnectionState();
|
|
314
|
+
const dotdoContext = useDotdoContext();
|
|
315
|
+
const syncStatus = isTanStackDBMode ? mapConnectionStateToSyncStatus(connectionState) : "disconnected";
|
|
316
|
+
const db = isTanStackDBMode ? dotdoContext : null;
|
|
317
|
+
const value = React.useMemo(
|
|
318
|
+
() => ({
|
|
319
|
+
config,
|
|
320
|
+
namespace,
|
|
321
|
+
setNamespace,
|
|
322
|
+
userId,
|
|
323
|
+
isConnected,
|
|
324
|
+
connectionError,
|
|
325
|
+
syncStatus,
|
|
326
|
+
rpcUrl,
|
|
327
|
+
syncUrl,
|
|
328
|
+
isTanStackDBMode,
|
|
329
|
+
db,
|
|
330
|
+
collections
|
|
331
|
+
}),
|
|
332
|
+
[
|
|
333
|
+
config,
|
|
334
|
+
namespace,
|
|
335
|
+
setNamespace,
|
|
336
|
+
userId,
|
|
337
|
+
isConnected,
|
|
338
|
+
connectionError,
|
|
339
|
+
syncStatus,
|
|
340
|
+
rpcUrl,
|
|
341
|
+
syncUrl,
|
|
342
|
+
isTanStackDBMode,
|
|
343
|
+
db,
|
|
344
|
+
collections
|
|
345
|
+
]
|
|
346
|
+
);
|
|
347
|
+
return /* @__PURE__ */ jsx(DOContext.Provider, { value, children });
|
|
348
|
+
}
|
|
349
|
+
function DOProviderREST({
|
|
350
|
+
config,
|
|
351
|
+
namespace,
|
|
352
|
+
setNamespace,
|
|
353
|
+
userId,
|
|
354
|
+
isConnected,
|
|
355
|
+
connectionError,
|
|
356
|
+
children
|
|
357
|
+
}) {
|
|
358
|
+
const value = React.useMemo(
|
|
359
|
+
() => ({
|
|
360
|
+
config,
|
|
361
|
+
namespace,
|
|
362
|
+
setNamespace,
|
|
363
|
+
userId,
|
|
364
|
+
isConnected,
|
|
365
|
+
connectionError,
|
|
366
|
+
syncStatus: "disconnected",
|
|
367
|
+
rpcUrl: null,
|
|
368
|
+
syncUrl: null,
|
|
369
|
+
isTanStackDBMode: false,
|
|
370
|
+
db: null,
|
|
371
|
+
collections: DEFAULT_COLLECTIONS
|
|
372
|
+
}),
|
|
373
|
+
[config, namespace, setNamespace, userId, isConnected, connectionError]
|
|
374
|
+
);
|
|
375
|
+
return /* @__PURE__ */ jsx(DOContext.Provider, { value, children });
|
|
376
|
+
}
|
|
377
|
+
function DOProvider({
|
|
378
|
+
config,
|
|
379
|
+
initialNamespace = "default",
|
|
380
|
+
userId,
|
|
381
|
+
children,
|
|
382
|
+
queryClient: providedQueryClient
|
|
383
|
+
}) {
|
|
384
|
+
const [namespace, setNamespace] = React.useState(initialNamespace);
|
|
385
|
+
const isTanStackDBMode = Boolean(config.doUrl);
|
|
386
|
+
const rpcUrl = config.doUrl ? deriveRpcUrl(config.doUrl) : null;
|
|
387
|
+
const syncUrl = config.doUrl ? deriveSyncUrl(config.doUrl) : null;
|
|
388
|
+
const collections = config.collections ?? DEFAULT_COLLECTIONS;
|
|
389
|
+
const { isConnected, connectionError } = useHealthCheck({
|
|
390
|
+
apiEndpoint: config.apiEndpoint,
|
|
391
|
+
authToken: config.authToken,
|
|
392
|
+
requestTimeout: config.requestTimeout,
|
|
393
|
+
healthCheckRetries: config.healthCheckRetries
|
|
394
|
+
});
|
|
395
|
+
const [queryClient] = React.useState(() => providedQueryClient ?? createQueryClient());
|
|
396
|
+
const dotdoConfig = React.useMemo(() => {
|
|
397
|
+
if (!config.authToken) return void 0;
|
|
398
|
+
return {
|
|
399
|
+
auth: {
|
|
400
|
+
token: config.authToken
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
}, [config.authToken]);
|
|
404
|
+
const content = isTanStackDBMode ? (
|
|
405
|
+
// TanStack DB mode: wrap with @dotdo/react provider
|
|
406
|
+
/* @__PURE__ */ jsx(DO, { ns: config.doUrl, config: dotdoConfig, children: /* @__PURE__ */ jsx(
|
|
407
|
+
DOProviderInner,
|
|
408
|
+
{
|
|
409
|
+
config,
|
|
410
|
+
namespace,
|
|
411
|
+
setNamespace,
|
|
412
|
+
userId,
|
|
413
|
+
isConnected,
|
|
414
|
+
connectionError,
|
|
415
|
+
rpcUrl,
|
|
416
|
+
syncUrl,
|
|
417
|
+
isTanStackDBMode: true,
|
|
418
|
+
collections,
|
|
419
|
+
children
|
|
420
|
+
}
|
|
421
|
+
) })
|
|
422
|
+
) : (
|
|
423
|
+
// REST mode: no @dotdo/react wrapper
|
|
424
|
+
/* @__PURE__ */ jsx(
|
|
425
|
+
DOProviderREST,
|
|
426
|
+
{
|
|
427
|
+
config,
|
|
428
|
+
namespace,
|
|
429
|
+
setNamespace,
|
|
430
|
+
userId,
|
|
431
|
+
isConnected,
|
|
432
|
+
connectionError,
|
|
433
|
+
children
|
|
434
|
+
}
|
|
435
|
+
)
|
|
436
|
+
);
|
|
437
|
+
return /* @__PURE__ */ jsx(QueryClientProvider, { client: queryClient, children: content });
|
|
438
|
+
}
|
|
439
|
+
function useDataProviderSafe() {
|
|
440
|
+
return React.useContext(DataProviderContext);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
export { DEFAULT_REQUEST_TIMEOUT, DOProvider, camelToTitle, capitalize, cn, debounce, fetchWithTimeout, formatBytes, formatDate, formatDateTime, formatDuration, formatNumber, formatRelativeTime, formatSemanticId, generateId, groupBy, parseSemanticId, truncate, useDO, useDOUrls, useDataProviderSafe, useSyncStatus };
|
|
444
|
+
//# sourceMappingURL=chunk-XF6LKY2M.js.map
|
|
445
|
+
//# sourceMappingURL=chunk-XF6LKY2M.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/fetch-with-timeout.ts","../src/lib/utils.ts","../src/lib/url-utils.ts","../src/lib/use-health-check.ts","../src/providers/do-provider.tsx"],"names":["DotdoProvider"],"mappings":";;;;;;;;;;AAYO,IAAM,uBAAA,GAA0B;AAyCvC,eAAsB,gBAAA,CACpB,GAAA,EACA,OAAA,GAAmC,EAAC,EACjB;AACnB,EAAA,MAAM,EAAE,OAAA,GAAU,uBAAA,EAAyB,QAAQ,cAAA,EAAgB,GAAG,cAAa,GAAI,OAAA;AAEvF,EAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,EAAA,MAAM,YAAY,UAAA,CAAW,MAAM,UAAA,CAAW,KAAA,IAAS,OAAO,CAAA;AAG9D,EAAA,MAAM,uBAAuB,cAAA,GACzB,MAAM,WAAW,KAAA,CAAM,cAAA,CAAe,MAAM,CAAA,GAC5C,IAAA;AAEJ,EAAA,IAAI,cAAA,EAAgB;AAClB,IAAA,IAAI,eAAe,OAAA,EAAS;AAC1B,MAAA,YAAA,CAAa,SAAS,CAAA;AACtB,MAAA,UAAA,CAAW,KAAA,CAAM,eAAe,MAAM,CAAA;AAAA,IACxC,CAAA,MAAO;AACL,MAAA,cAAA,CAAe,gBAAA,CAAiB,SAAS,oBAAqB,CAAA;AAAA,IAChE;AAAA,EACF;AAEA,EAAA,IAAI;AACF,IAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,MAChC,GAAG,YAAA;AAAA,MACH,QAAQ,UAAA,CAAW;AAAA,KACpB,CAAA;AACD,IAAA,OAAO,QAAA;AAAA,EACT,CAAA,SAAE;AACA,IAAA,YAAA,CAAa,SAAS,CAAA;AACtB,IAAA,IAAI,kBAAkB,oBAAA,EAAsB;AAC1C,MAAA,cAAA,CAAe,mBAAA,CAAoB,SAAS,oBAAoB,CAAA;AAAA,IAClE;AAAA,EACF;AACF;AClFO,SAAS,MAAM,MAAA,EAAsB;AAC1C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;AAKO,SAAS,gBAAA,CAAiB,EAAA,EAAY,IAAA,EAAc,EAAA,EAAoB;AAC7E,EAAA,OAAO,CAAA,EAAG,EAAE,CAAA,CAAA,EAAI,IAAI,IAAI,EAAE,CAAA,CAAA;AAC5B;AAOO,SAAS,gBAAgB,UAAA,EAAqE;AACnG,EAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,KAAA,CAAM,GAAG,CAAA;AAClC,EAAA,IAAI,KAAA,CAAM,SAAS,CAAA,EAAG;AACpB,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,4BAAA,EAA+B,UAAU,CAAA,CAAE,CAAA;AAAA,EAC7D;AAIA,EAAA,MAAM,IAAA,GAAO,MAAM,CAAC,CAAA;AACpB,EAAA,MAAM,EAAA,GAAK,KAAA,CAAM,KAAA,CAAM,MAAA,GAAS,CAAC,CAAA;AACjC,EAAA,MAAM,YAAY,KAAA,CAAM,KAAA,CAAM,GAAG,EAAE,CAAA,CAAE,KAAK,GAAG,CAAA;AAE7C,EAAA,OAAO,EAAE,IAAA,EAAM,SAAA,EAAW,EAAA,EAAG;AAC/B;AAKO,SAAS,UAAA,CAAW,MAAqB,OAAA,EAA8C;AAC5F,EAAA,MAAM,IAAI,OAAO,IAAA,KAAS,WAAW,IAAI,IAAA,CAAK,IAAI,CAAA,GAAI,IAAA;AACtD,EAAA,OAAO,CAAA,CAAE,kBAAA,CAAmB,MAAA,EAAW,OAAA,IAAW;AAAA,IAChD,IAAA,EAAM,SAAA;AAAA,IACN,KAAA,EAAO,OAAA;AAAA,IACP,GAAA,EAAK;AAAA,GACN,CAAA;AACH;AAKO,SAAS,cAAA,CAAe,MAAqB,OAAA,EAA8C;AAChG,EAAA,MAAM,IAAI,OAAO,IAAA,KAAS,WAAW,IAAI,IAAA,CAAK,IAAI,CAAA,GAAI,IAAA;AACtD,EAAA,OAAO,CAAA,CAAE,cAAA,CAAe,MAAA,EAAW,OAAA,IAAW;AAAA,IAC5C,IAAA,EAAM,SAAA;AAAA,IACN,KAAA,EAAO,OAAA;AAAA,IACP,GAAA,EAAK,SAAA;AAAA,IACL,IAAA,EAAM,SAAA;AAAA,IACN,MAAA,EAAQ;AAAA,GACT,CAAA;AACH;AAKO,SAAS,mBAAmB,IAAA,EAA6B;AAC9D,EAAA,MAAM,IAAI,OAAO,IAAA,KAAS,WAAW,IAAI,IAAA,CAAK,IAAI,CAAA,GAAI,IAAA;AACtD,EAAA,MAAM,GAAA,uBAAU,IAAA,EAAK;AACrB,EAAA,MAAM,MAAA,GAAS,GAAA,CAAI,OAAA,EAAQ,GAAI,EAAE,OAAA,EAAQ;AACzC,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,KAAA,CAAM,MAAA,GAAS,GAAI,CAAA;AACxC,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,KAAA,CAAM,OAAA,GAAU,EAAE,CAAA;AACvC,EAAA,MAAM,QAAA,GAAW,IAAA,CAAK,KAAA,CAAM,OAAA,GAAU,EAAE,CAAA;AACxC,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,KAAA,CAAM,QAAA,GAAW,EAAE,CAAA;AAExC,EAAA,IAAI,OAAA,GAAU,IAAI,OAAO,CAAA,EAAG,OAAO,CAAA,CAAA,EAAI,OAAA,KAAY,CAAA,GAAI,QAAA,GAAW,SAAS,CAAA,IAAA,CAAA;AAC3E,EAAA,IAAI,OAAA,GAAU,IAAI,OAAO,CAAA,EAAG,OAAO,CAAA,CAAA,EAAI,OAAA,KAAY,CAAA,GAAI,QAAA,GAAW,SAAS,CAAA,IAAA,CAAA;AAC3E,EAAA,IAAI,QAAA,GAAW,IAAI,OAAO,CAAA,EAAG,QAAQ,CAAA,CAAA,EAAI,QAAA,KAAa,CAAA,GAAI,MAAA,GAAS,OAAO,CAAA,IAAA,CAAA;AAC1E,EAAA,IAAI,OAAA,GAAU,GAAG,OAAO,CAAA,EAAG,OAAO,CAAA,CAAA,EAAI,OAAA,KAAY,CAAA,GAAI,KAAA,GAAQ,MAAM,CAAA,IAAA,CAAA;AACpE,EAAA,OAAO,WAAW,CAAC,CAAA;AACrB;AAKA,SAAS,aAAA,CAAc,KAAA,EAAe,QAAA,GAAmB,CAAA,EAAW;AAClE,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,OAAA,CAAQ,QAAQ,CAAA;AAEpC,EAAA,OAAO,KAAA,CAAM,SAAS,IAAI,CAAA,GAAI,MAAM,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA,GAAI,KAAA;AACrD;AAKO,SAAS,aAAa,GAAA,EAAqB;AAChD,EAAA,IAAI,MAAM,CAAA,EAAG,OAAO,IAAI,YAAA,CAAa,CAAC,GAAG,CAAC,CAAA,CAAA;AAC1C,EAAA,IAAI,OAAO,GAAA,EAAW,OAAO,GAAG,aAAA,CAAc,GAAA,GAAM,GAAS,CAAC,CAAA,CAAA,CAAA;AAC9D,EAAA,IAAI,OAAO,GAAA,EAAO,OAAO,GAAG,aAAA,CAAc,GAAA,GAAM,GAAK,CAAC,CAAA,CAAA,CAAA;AACtD,EAAA,OAAO,IAAI,QAAA,EAAS;AACtB;AAKO,SAAS,YAAY,KAAA,EAAuB;AACjD,EAAA,IAAI,KAAA,KAAU,GAAG,OAAO,KAAA;AACxB,EAAA,MAAM,CAAA,GAAI,IAAA;AACV,EAAA,MAAM,QAAQ,CAAC,GAAA,EAAK,IAAA,EAAM,IAAA,EAAM,MAAM,IAAI,CAAA;AAC1C,EAAA,MAAM,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,GAAA,CAAI,KAAK,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,CAAC,CAAC,CAAA;AAClD,EAAA,OAAO,CAAA,EAAG,aAAA,CAAc,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,CAAC,CAAC,CAAC,CAAA,CAAA,EAAI,KAAA,CAAM,CAAC,CAAC,CAAA,CAAA;AAC7D;AAKO,SAAS,eAAe,EAAA,EAAoB;AACjD,EAAA,IAAI,EAAA,GAAK,GAAA,EAAM,OAAO,CAAA,EAAG,EAAE,CAAA,EAAA,CAAA;AAC3B,EAAA,IAAI,KAAK,GAAA,EAAQ,OAAO,GAAG,aAAA,CAAc,EAAA,GAAK,GAAI,CAAC,CAAA,CAAA,CAAA;AACnD,EAAA,IAAI,EAAA,GAAK,IAAA,EAAW,OAAO,CAAA,EAAG,KAAK,KAAA,CAAM,EAAA,GAAK,GAAM,CAAC,KAAK,IAAA,CAAK,KAAA,CAAO,EAAA,GAAK,GAAA,GAAU,GAAI,CAAC,CAAA,CAAA,CAAA;AAC1F,EAAA,OAAO,CAAA,EAAG,IAAA,CAAK,KAAA,CAAM,EAAA,GAAK,IAAS,CAAC,CAAA,EAAA,EAAK,IAAA,CAAK,KAAA,CAAO,EAAA,GAAK,IAAA,GAAa,GAAM,CAAC,CAAA,CAAA,CAAA;AAChF;AAKO,SAAS,QAAA,CAAS,IAAA,EAAc,SAAA,EAAmB,MAAA,GAAiB,KAAA,EAAe;AACxF,EAAA,IAAI,IAAA,CAAK,MAAA,IAAU,SAAA,EAAW,OAAO,IAAA;AACrC,EAAA,OAAO,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,SAAS,CAAA,GAAI,MAAA;AACpC;AAKO,SAAS,WAAW,IAAA,EAAsB;AAC/C,EAAA,OAAO,IAAA,CAAK,OAAO,CAAC,CAAA,CAAE,aAAY,GAAI,IAAA,CAAK,MAAM,CAAC,CAAA;AACpD;AAKO,SAAS,aAAa,IAAA,EAAsB;AACjD,EAAA,OAAO,IAAA,CACJ,OAAA,CAAQ,UAAA,EAAY,KAAK,CAAA,CACzB,OAAA,CAAQ,IAAA,EAAM,CAAC,GAAA,KAAQ,GAAA,CAAI,WAAA,EAAa,EACxC,IAAA,EAAK;AACV;AAKO,SAAS,WAAW,MAAA,EAAyB;AAClD,EAAA,MAAM,EAAA,GAAK,KAAK,MAAA,EAAO,CAAE,SAAS,EAAE,CAAA,CAAE,SAAA,CAAU,CAAA,EAAG,EAAE,CAAA;AACrD,EAAA,OAAO,MAAA,GAAS,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,EAAE,CAAA,CAAA,GAAK,EAAA;AACtC;AAoBO,SAAS,QAAA,CACd,IACA,KAAA,EACyB;AACzB,EAAA,IAAI,SAAA;AACJ,EAAA,OAAO,IAAI,IAAA,KAAe;AACxB,IAAA,YAAA,CAAa,SAAS,CAAA;AACtB,IAAA,SAAA,GAAY,WAAW,MAAM,EAAA,CAAG,GAAG,IAAI,GAAG,KAAK,CAAA;AAAA,EACjD,CAAA;AACF;AAKO,SAAS,OAAA,CACd,OACA,KAAA,EACgB;AAChB,EAAA,OAAO,KAAA,CAAM,MAAA,CAAO,CAAC,GAAA,EAAK,IAAA,KAAS;AACjC,IAAA,MAAM,GAAA,GAAM,MAAM,IAAI,CAAA;AACtB,IAAA,IAAI,CAAC,GAAA,CAAI,GAAG,GAAG,GAAA,CAAI,GAAG,IAAI,EAAC;AAC3B,IAAA,GAAA,CAAI,GAAG,CAAA,CAAE,IAAA,CAAK,IAAI,CAAA;AAClB,IAAA,OAAO,GAAA;AAAA,EACT,CAAA,EAAG,EAAoB,CAAA;AACzB;AAqDA,eAAsB,gBAAA,CACpB,EAAA,EACA,MAAA,GAAsB,EAAC,EACX;AACZ,EAAA,MAAM,EAAE,UAAA,GAAa,CAAA,EAAG,oBAAoB,GAAA,EAAM,MAAA,EAAQ,SAAQ,GAAI,MAAA;AACtE,EAAA,IAAI,SAAA;AAEJ,EAAA,KAAA,IAAS,OAAA,GAAU,CAAA,EAAG,OAAA,GAAU,UAAA,EAAY,OAAA,EAAA,EAAW;AAErD,IAAA,IAAI,QAAQ,OAAA,EAAS;AACnB,MAAA,MAAM,SAAA,IAAa,IAAI,KAAA,CAAM,iBAAiB,CAAA;AAAA,IAChD;AAEA,IAAA,IAAI;AACF,MAAA,OAAO,MAAM,EAAA,EAAG;AAAA,IAClB,SAAS,KAAA,EAAO;AACd,MAAA,SAAA,GAAY,KAAA,YAAiB,KAAA,GAAQ,KAAA,GAAQ,IAAI,MAAM,cAAc,CAAA;AAGrE,MAAA,IAAI,OAAA,GAAU,aAAa,CAAA,EAAG;AAE5B,QAAA,OAAA,GAAU,SAAA,EAAW,UAAU,CAAC,CAAA;AAGhC,QAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,OAAO,CAAA,GAAI,iBAAA;AAGrC,QAAA,MAAM,IAAI,OAAA,CAAc,CAAC,YAAY,UAAA,CAAW,OAAA,EAAS,KAAK,CAAC,CAAA;AAG/D,QAAA,IAAI,QAAQ,OAAA,EAAS;AACnB,UAAA,MAAM,SAAA,IAAa,IAAI,KAAA,CAAM,iBAAiB,CAAA;AAAA,QAChD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,EAAA,MAAM,SAAA;AACR;;;ACnQO,SAAS,aAAa,KAAA,EAAuB;AAClD,EAAA,IAAI,MAAA,GAAS,MAAM,OAAA,CAAQ,OAAA,EAAS,QAAQ,CAAA,CAAE,OAAA,CAAQ,QAAQ,OAAO,CAAA;AACrE,EAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,SAAA,EAAW,EAAE,CAAA;AACrC,EAAA,OAAO,GAAG,MAAM,CAAA,IAAA,CAAA;AAClB;AAmBO,SAAS,cAAc,KAAA,EAAuB;AACnD,EAAA,IAAI,KAAA,CAAM,QAAA,CAAS,OAAO,CAAA,EAAG;AAC3B,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,OAAO,GAAG,KAAK,CAAA,KAAA,CAAA;AACjB;ACgBO,SAAS,eAAe,MAAA,EAA8C;AAC3E,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAI,SAAS,KAAK,CAAA;AACpD,EAAA,MAAM,CAAC,eAAA,EAAiB,kBAAkB,CAAA,GAAI,QAAA,EAAgB;AAC9D,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAI,SAAS,IAAI,CAAA;AAEjD,EAAA,MAAM,EAAE,WAAA,EAAa,SAAA,EAAW,cAAA,EAAgB,kBAAA,GAAqB,GAAE,GAAI,MAAA;AAG3E,EAAA,MAAM,kBAAA,GAAqB,OAA+B,IAAI,CAAA;AAM9D,EAAA,MAAM,kBAAA,GAAqB,YAAY,YAA2B;AAChE,IAAA,MAAM,QAAA,GAAW,MAAM,gBAAA,CAAiB,CAAA,EAAG,WAAW,CAAA,OAAA,CAAA,EAAW;AAAA,MAC/D,MAAA,EAAQ,KAAA;AAAA,MACR,OAAA,EAAS;AAAA,QACP,cAAA,EAAgB,kBAAA;AAAA,QAChB,GAAI,YAAY,EAAE,aAAA,EAAe,UAAU,SAAS,CAAA,CAAA,KAAO;AAAC,OAC9D;AAAA,MACA,SAAS,cAAA,IAAkB;AAAA,KAC5B,CAAA;AAED,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,qBAAA,EAAwB,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAAA,IAC3D;AAAA,EACF,CAAA,EAAG,CAAC,WAAA,EAAa,SAAA,EAAW,cAAc,CAAC,CAAA;AAE3C,EAAA,MAAM,eAAA,GAAkB,YAAY,YAAY;AAC9C,IAAA,aAAA,CAAc,IAAI,CAAA;AAElB,IAAA,IAAI;AACF,MAAA,MAAM,iBAAiB,kBAAA,EAAoB;AAAA,QACzC,UAAA,EAAY,kBAAA;AAAA,QACZ,iBAAA,EAAmB;AAAA,OACpB,CAAA;AAED,MAAA,cAAA,CAAe,IAAI,CAAA;AACnB,MAAA,kBAAA,CAAmB,KAAA,CAAS,CAAA;AAAA,IAC9B,SAAS,KAAA,EAAO;AACd,MAAA,cAAA,CAAe,KAAK,CAAA;AACpB,MAAA,kBAAA,CAAmB,iBAAiB,KAAA,GAAQ,KAAA,GAAQ,IAAI,KAAA,CAAM,mBAAmB,CAAC,CAAA;AAAA,IACpF,CAAA,SAAE;AACA,MAAA,aAAA,CAAc,KAAK,CAAA;AAAA,IACrB;AAAA,EACF,CAAA,EAAG,CAAC,kBAAA,EAAoB,kBAAkB,CAAC,CAAA;AAG3C,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,kBAAA,CAAmB,OAAA,GAAU,IAAI,eAAA,EAAgB;AACjD,IAAA,MAAM,EAAE,MAAA,EAAO,GAAI,kBAAA,CAAmB,OAAA;AAEtC,IAAA,eAAe,QAAA,GAAW;AACxB,MAAA,IAAI;AACF,QAAA,MAAM,iBAAiB,kBAAA,EAAoB;AAAA,UACzC,UAAA,EAAY,kBAAA;AAAA,UACZ,iBAAA,EAAmB,GAAA;AAAA,UACnB;AAAA,SACD,CAAA;AAED,QAAA,IAAI,CAAC,OAAO,OAAA,EAAS;AACnB,UAAA,cAAA,CAAe,IAAI,CAAA;AACnB,UAAA,kBAAA,CAAmB,KAAA,CAAS,CAAA;AAC5B,UAAA,aAAA,CAAc,KAAK,CAAA;AAAA,QACrB;AAAA,MACF,SAAS,KAAA,EAAO;AACd,QAAA,IAAI,CAAC,OAAO,OAAA,EAAS;AACnB,UAAA,cAAA,CAAe,KAAK,CAAA;AACpB,UAAA,kBAAA,CAAmB,iBAAiB,KAAA,GAAQ,KAAA,GAAQ,IAAI,KAAA,CAAM,mBAAmB,CAAC,CAAA;AAClF,UAAA,aAAA,CAAc,KAAK,CAAA;AAAA,QACrB;AAAA,MACF;AAAA,IACF;AAEA,IAAA,QAAA,EAAS;AAET,IAAA,OAAO,MAAM;AACX,MAAA,kBAAA,CAAmB,SAAS,KAAA,EAAM;AAAA,IACpC,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,kBAAA,EAAoB,kBAAkB,CAAC,CAAA;AAE3C,EAAA,OAAO;AAAA,IACL,WAAA;AAAA,IACA,eAAA;AAAA,IACA,eAAA;AAAA,IACA;AAAA,GACF;AACF;AClJA,IAAM,mBAAA,GAAsB,CAAC,OAAA,EAAS,OAAA,EAAS,UAAU,CAAA;AAKzD,SAAS,iBAAA,GAAoB;AAC3B,EAAA,OAAO,IAAI,WAAA,CAAY;AAAA,IACrB,cAAA,EAAgB;AAAA,MACd,OAAA,EAAS;AAAA;AAAA,QAEP,WAAW,EAAA,GAAK,GAAA;AAAA;AAAA,QAEhB,MAAA,EAAQ,IAAI,EAAA,GAAK,GAAA;AAAA;AAAA,QAEjB,KAAA,EAAO,CAAA;AAAA;AAAA,QAEP,oBAAA,EAAsB;AAAA,OACxB;AAAA,MACA,SAAA,EAAW;AAAA;AAAA,QAET,KAAA,EAAO;AAAA;AACT;AACF,GACD,CAAA;AACH;AAKA,SAAS,+BAA+B,KAAA,EAAoC;AAC1E,EAAA,QAAQ,KAAA;AAAO,IACb,KAAK,YAAA;AACH,MAAA,OAAO,YAAA;AAAA,IACT,KAAK,WAAA;AACH,MAAA,OAAO,QAAA;AAAA,IACT,KAAK,cAAA;AACH,MAAA,OAAO,SAAA;AAAA,IACT,KAAK,cAAA;AACH,MAAA,OAAO,cAAA;AAAA,IACT;AACE,MAAA,OAAO,cAAA;AAAA;AAEb;AAmCA,IAAM,SAAA,GAAkB,oBAAqC,IAAI,CAAA;AAK1D,SAAS,KAAA,GAAwB;AACtC,EAAA,MAAM,OAAA,GAAgB,iBAAW,SAAS,CAAA;AAC1C,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,MAAM,IAAI,MAAM,wCAAwC,CAAA;AAAA,EAC1D;AACA,EAAA,OAAO,OAAA;AACT;AAKO,SAAS,SAAA,GAAY;AAC1B,EAAA,MAAM,EAAE,MAAA,EAAQ,OAAA,EAAS,MAAA,KAAW,KAAA,EAAM;AAC1C,EAAA,OAAO;AAAA,IACL,MAAA;AAAA,IACA,OAAA;AAAA,IACA,aAAa,MAAA,CAAO,WAAA;AAAA,IACpB,KAAA,EAAO,OAAO,KAAA,IAAS;AAAA,GACzB;AACF;AAKO,SAAS,aAAA,GAA4B;AAC1C,EAAA,MAAM,EAAE,UAAA,EAAW,GAAI,KAAA,EAAM;AAC7B,EAAA,OAAO,UAAA;AACT;AA4EA,SAAS,eAAA,CAAgB;AAAA,EACvB,MAAA;AAAA,EACA,SAAA;AAAA,EACA,YAAA;AAAA,EACA,MAAA;AAAA,EACA,WAAA;AAAA,EACA,eAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,gBAAA;AAAA,EACA,WAAA;AAAA,EACA;AACF,CAAA,EAAyB;AAEvB,EAAA,MAAM,kBAAkB,kBAAA,EAAmB;AAC3C,EAAA,MAAM,eAAe,eAAA,EAAgB;AAGrC,EAAA,MAAM,UAAA,GAAa,gBAAA,GACf,8BAAA,CAA+B,eAAe,CAAA,GAC9C,cAAA;AAGJ,EAAA,MAAM,EAAA,GAAK,mBAAmB,YAAA,GAAe,IAAA;AAG7C,EAAA,MAAM,KAAA,GAAc,KAAA,CAAA,OAAA;AAAA,IAClB,OAAO;AAAA,MACL,MAAA;AAAA,MACA,SAAA;AAAA,MACA,YAAA;AAAA,MACA,MAAA;AAAA,MACA,WAAA;AAAA,MACA,eAAA;AAAA,MACA,UAAA;AAAA,MACA,MAAA;AAAA,MACA,OAAA;AAAA,MACA,gBAAA;AAAA,MACA,EAAA;AAAA,MACA;AAAA,KACF,CAAA;AAAA,IACA;AAAA,MACE,MAAA;AAAA,MACA,SAAA;AAAA,MACA,YAAA;AAAA,MACA,MAAA;AAAA,MACA,WAAA;AAAA,MACA,eAAA;AAAA,MACA,UAAA;AAAA,MACA,MAAA;AAAA,MACA,OAAA;AAAA,MACA,gBAAA;AAAA,MACA,EAAA;AAAA,MACA;AAAA;AACF,GACF;AAEA,EAAA,uBAAO,GAAA,CAAC,SAAA,CAAU,QAAA,EAAV,EAAmB,OAAe,QAAA,EAAS,CAAA;AACrD;AAMA,SAAS,cAAA,CAAe;AAAA,EACtB,MAAA;AAAA,EACA,SAAA;AAAA,EACA,YAAA;AAAA,EACA,MAAA;AAAA,EACA,WAAA;AAAA,EACA,eAAA;AAAA,EACA;AACF,CAAA,EAA0F;AAExF,EAAA,MAAM,KAAA,GAAc,KAAA,CAAA,OAAA;AAAA,IAClB,OAAO;AAAA,MACL,MAAA;AAAA,MACA,SAAA;AAAA,MACA,YAAA;AAAA,MACA,MAAA;AAAA,MACA,WAAA;AAAA,MACA,eAAA;AAAA,MACA,UAAA,EAAY,cAAA;AAAA,MACZ,MAAA,EAAQ,IAAA;AAAA,MACR,OAAA,EAAS,IAAA;AAAA,MACT,gBAAA,EAAkB,KAAA;AAAA,MAClB,EAAA,EAAI,IAAA;AAAA,MACJ,WAAA,EAAa;AAAA,KACf,CAAA;AAAA,IACA,CAAC,MAAA,EAAQ,SAAA,EAAW,YAAA,EAAc,MAAA,EAAQ,aAAa,eAAe;AAAA,GACxE;AAEA,EAAA,uBAAO,GAAA,CAAC,SAAA,CAAU,QAAA,EAAV,EAAmB,OAAe,QAAA,EAAS,CAAA;AACrD;AAEO,SAAS,UAAA,CAAW;AAAA,EACzB,MAAA;AAAA,EACA,gBAAA,GAAmB,SAAA;AAAA,EACnB,MAAA;AAAA,EACA,QAAA;AAAA,EACA,WAAA,EAAa;AACf,CAAA,EAAoB;AAElB,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAU,eAAS,gBAAgB,CAAA;AAGjE,EAAA,MAAM,gBAAA,GAAmB,OAAA,CAAQ,MAAA,CAAO,KAAK,CAAA;AAC7C,EAAA,MAAM,SAAS,MAAA,CAAO,KAAA,GAAQ,YAAA,CAAa,MAAA,CAAO,KAAK,CAAA,GAAI,IAAA;AAC3D,EAAA,MAAM,UAAU,MAAA,CAAO,KAAA,GAAQ,aAAA,CAAc,MAAA,CAAO,KAAK,CAAA,GAAI,IAAA;AAC7D,EAAA,MAAM,WAAA,GAAc,OAAO,WAAA,IAAe,mBAAA;AAG1C,EAAA,MAAM,EAAE,WAAA,EAAa,eAAA,EAAgB,GAAI,cAAA,CAAe;AAAA,IACtD,aAAa,MAAA,CAAO,WAAA;AAAA,IACpB,WAAW,MAAA,CAAO,SAAA;AAAA,IAClB,gBAAgB,MAAA,CAAO,cAAA;AAAA,IACvB,oBAAoB,MAAA,CAAO;AAAA,GAC5B,CAAA;AAGD,EAAA,MAAM,CAAC,WAAW,CAAA,GAAU,eAAS,MAAM,mBAAA,IAAuB,mBAAmB,CAAA;AAGrF,EAAA,MAAM,WAAA,GAAoB,cAAQ,MAAM;AACtC,IAAA,IAAI,CAAC,MAAA,CAAO,SAAA,EAAW,OAAO,MAAA;AAC9B,IAAA,OAAO;AAAA,MACL,IAAA,EAAM;AAAA,QACJ,OAAO,MAAA,CAAO;AAAA;AAChB,KACF;AAAA,EACF,CAAA,EAAG,CAAC,MAAA,CAAO,SAAS,CAAC,CAAA;AAGrB,EAAA,MAAM,OAAA,GAAU,gBAAA;AAAA;AAAA,wBAEbA,EAAA,EAAA,EAAc,EAAA,EAAI,MAAA,CAAO,KAAA,EAAQ,QAAQ,WAAA,EACxC,QAAA,kBAAA,GAAA;AAAA,MAAC,eAAA;AAAA,MAAA;AAAA,QACC,MAAA;AAAA,QACA,SAAA;AAAA,QACA,YAAA;AAAA,QACA,MAAA;AAAA,QACA,WAAA;AAAA,QACA,eAAA;AAAA,QACA,MAAA;AAAA,QACA,OAAA;AAAA,QACA,gBAAA,EAAkB,IAAA;AAAA,QAClB,WAAA;AAAA,QAEC;AAAA;AAAA,KACH,EACF;AAAA;AAAA;AAAA,oBAGA,GAAA;AAAA,MAAC,cAAA;AAAA,MAAA;AAAA,QACC,MAAA;AAAA,QACA,SAAA;AAAA,QACA,YAAA;AAAA,QACA,MAAA;AAAA,QACA,WAAA;AAAA,QACA,eAAA;AAAA,QAEC;AAAA;AAAA;AACH,GAAA;AAGF,EAAA,uBAAO,GAAA,CAAC,mBAAA,EAAA,EAAoB,MAAA,EAAQ,WAAA,EAAc,QAAA,EAAA,OAAA,EAAQ,CAAA;AAC5D;AAuBO,SAAS,mBAAA,GAA2C;AACzD,EAAA,OAAa,iBAAW,mBAAmB,CAAA;AAC7C","file":"chunk-XF6LKY2M.js","sourcesContent":["/**\n * Fetch wrapper with configurable timeout\n *\n * Prevents indefinite hangs on slow networks or unresponsive servers\n * by wrapping fetch with AbortController-based timeout.\n *\n * @module\n */\n\n/**\n * Default timeout in milliseconds (30 seconds)\n */\nexport const DEFAULT_REQUEST_TIMEOUT = 30000\n\n/**\n * Extended RequestInit that includes optional timeout\n */\nexport interface FetchWithTimeoutOptions extends RequestInit {\n /** Timeout in milliseconds. Defaults to 30000 (30 seconds) */\n timeout?: number\n}\n\n/**\n * Fetch wrapper with configurable timeout\n *\n * Uses AbortController to cancel requests that exceed the timeout.\n * If the request already has a signal, a composite abort will be used\n * that respects both the existing signal and the timeout.\n *\n * @param url - The URL to fetch\n * @param options - Fetch options including optional timeout\n * @returns Promise resolving to the Response\n * @throws {DOMException} AbortError if the request times out\n *\n * @example\n * ```typescript\n * // Basic usage with default 30s timeout\n * const response = await fetchWithTimeout('https://api.example.com/data')\n *\n * // Custom timeout\n * const response = await fetchWithTimeout('https://api.example.com/data', {\n * timeout: 5000, // 5 seconds\n * })\n *\n * // With other fetch options\n * const response = await fetchWithTimeout('https://api.example.com/data', {\n * method: 'POST',\n * headers: { 'Content-Type': 'application/json' },\n * body: JSON.stringify({ key: 'value' }),\n * timeout: 10000,\n * })\n * ```\n */\nexport async function fetchWithTimeout(\n url: string,\n options: FetchWithTimeoutOptions = {}\n): Promise<Response> {\n const { timeout = DEFAULT_REQUEST_TIMEOUT, signal: existingSignal, ...fetchOptions } = options\n\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n\n // If there's an existing signal, listen for its abort\n const existingAbortHandler = existingSignal\n ? () => controller.abort(existingSignal.reason)\n : null\n\n if (existingSignal) {\n if (existingSignal.aborted) {\n clearTimeout(timeoutId)\n controller.abort(existingSignal.reason)\n } else {\n existingSignal.addEventListener('abort', existingAbortHandler!)\n }\n }\n\n try {\n const response = await fetch(url, {\n ...fetchOptions,\n signal: controller.signal,\n })\n return response\n } finally {\n clearTimeout(timeoutId)\n if (existingSignal && existingAbortHandler) {\n existingSignal.removeEventListener('abort', existingAbortHandler)\n }\n }\n}\n\n/**\n * Create a fetch wrapper with a default timeout from config\n *\n * Useful for creating a configured fetch function that can be shared\n * across multiple hooks/components with consistent timeout behavior.\n *\n * @param defaultTimeout - Default timeout in milliseconds\n * @returns A fetch function with the default timeout applied\n *\n * @example\n * ```typescript\n * // Create a configured fetch\n * const configuredFetch = createFetchWithTimeout(5000)\n *\n * // Use it (timeout defaults to 5000ms)\n * const response = await configuredFetch('https://api.example.com/data')\n *\n * // Override the default timeout\n * const response = await configuredFetch('https://api.example.com/data', {\n * timeout: 10000,\n * })\n * ```\n */\nexport function createFetchWithTimeout(defaultTimeout: number) {\n return (url: string, options: FetchWithTimeoutOptions = {}): Promise<Response> =>\n fetchWithTimeout(url, { timeout: defaultTimeout, ...options })\n}\n","import { clsx, type ClassValue } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\n\n/**\n * Merge class names with Tailwind CSS support\n */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\n/**\n * Format a semantic ID (ns/type/id)\n */\nexport function formatSemanticId(ns: string, type: string, id: string): string {\n return `${ns}/${type}/${id}`\n}\n\n/**\n * Parse a semantic ID into components\n * Format: type/namespace/id where namespace can contain slashes\n * @throws Error if the semantic ID format is invalid\n */\nexport function parseSemanticId(semanticId: string): { type: string; namespace: string; id: string } {\n const parts = semanticId.split('/')\n if (parts.length < 3) {\n throw new Error(`Invalid semantic ID format: ${semanticId}`)\n }\n\n // Format is type/namespace.../id\n // Type is first, id is last, namespace is everything in between\n const type = parts[0]\n const id = parts[parts.length - 1]\n const namespace = parts.slice(1, -1).join('/')\n\n return { type, namespace, id }\n}\n\n/**\n * Format a date for display\n */\nexport function formatDate(date: Date | string, options?: Intl.DateTimeFormatOptions): string {\n const d = typeof date === 'string' ? new Date(date) : date\n return d.toLocaleDateString(undefined, options ?? {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n })\n}\n\n/**\n * Format a date and time for display\n */\nexport function formatDateTime(date: Date | string, options?: Intl.DateTimeFormatOptions): string {\n const d = typeof date === 'string' ? new Date(date) : date\n return d.toLocaleString(undefined, options ?? {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n })\n}\n\n/**\n * Format a relative time (e.g., \"2 hours ago\")\n */\nexport function formatRelativeTime(date: Date | string): string {\n const d = typeof date === 'string' ? new Date(date) : date\n const now = new Date()\n const diffMs = now.getTime() - d.getTime()\n const diffSec = Math.floor(diffMs / 1000)\n const diffMin = Math.floor(diffSec / 60)\n const diffHour = Math.floor(diffMin / 60)\n const diffDay = Math.floor(diffHour / 24)\n\n if (diffSec < 60) return `${diffSec} ${diffSec === 1 ? 'second' : 'seconds'} ago`\n if (diffMin < 60) return `${diffMin} ${diffMin === 1 ? 'minute' : 'minutes'} ago`\n if (diffHour < 24) return `${diffHour} ${diffHour === 1 ? 'hour' : 'hours'} ago`\n if (diffDay < 7) return `${diffDay} ${diffDay === 1 ? 'day' : 'days'} ago`\n return formatDate(d)\n}\n\n/**\n * Format a decimal, removing trailing .0 for whole numbers\n */\nfunction formatDecimal(value: number, decimals: number = 1): string {\n const fixed = value.toFixed(decimals)\n // Remove trailing .0 for whole numbers\n return fixed.endsWith('.0') ? fixed.slice(0, -2) : fixed\n}\n\n/**\n * Format a number with abbreviation (1K, 1M, etc.)\n */\nexport function formatNumber(num: number): string {\n if (num < 0) return `-${formatNumber(-num)}`\n if (num >= 1_000_000) return `${formatDecimal(num / 1_000_000)}M`\n if (num >= 1_000) return `${formatDecimal(num / 1_000)}K`\n return num.toString()\n}\n\n/**\n * Format bytes to human-readable string\n */\nexport function formatBytes(bytes: number): string {\n if (bytes === 0) return '0 B'\n const k = 1024\n const sizes = ['B', 'KB', 'MB', 'GB', 'TB']\n const i = Math.floor(Math.log(bytes) / Math.log(k))\n return `${formatDecimal(bytes / Math.pow(k, i))} ${sizes[i]}`\n}\n\n/**\n * Format duration in milliseconds to human-readable string\n */\nexport function formatDuration(ms: number): string {\n if (ms < 1000) return `${ms}ms`\n if (ms < 60_000) return `${formatDecimal(ms / 1000)}s`\n if (ms < 3_600_000) return `${Math.floor(ms / 60_000)}m ${Math.floor((ms % 60_000) / 1000)}s`\n return `${Math.floor(ms / 3_600_000)}h ${Math.floor((ms % 3_600_000) / 60_000)}m`\n}\n\n/**\n * Truncate text with ellipsis\n */\nexport function truncate(text: string, maxLength: number, suffix: string = '...'): string {\n if (text.length <= maxLength) return text\n return text.slice(0, maxLength) + suffix\n}\n\n/**\n * Capitalize first letter\n */\nexport function capitalize(text: string): string {\n return text.charAt(0).toUpperCase() + text.slice(1)\n}\n\n/**\n * Convert camelCase to Title Case\n */\nexport function camelToTitle(text: string): string {\n return text\n .replace(/([A-Z])/g, ' $1')\n .replace(/^./, (str) => str.toUpperCase())\n .trim()\n}\n\n/**\n * Generate a random ID\n */\nexport function generateId(prefix?: string): string {\n const id = Math.random().toString(36).substring(2, 15)\n return prefix ? `${prefix}_${id}` : id\n}\n\n/**\n * Debounce function\n *\n * Creates a debounced version of the function that delays execution\n * until after the specified delay has passed since the last call.\n *\n * @example\n * ```ts\n * const debouncedSearch = debounce((query: string) => {\n * fetch(`/search?q=${query}`)\n * }, 300)\n *\n * // Only the last call within 300ms will execute\n * debouncedSearch('h')\n * debouncedSearch('he')\n * debouncedSearch('hello') // Only this one runs\n * ```\n */\nexport function debounce<Args extends unknown[]>(\n fn: (...args: Args) => unknown,\n delay: number\n): (...args: Args) => void {\n let timeoutId: ReturnType<typeof setTimeout>\n return (...args: Args) => {\n clearTimeout(timeoutId)\n timeoutId = setTimeout(() => fn(...args), delay)\n }\n}\n\n/**\n * Group array by key\n */\nexport function groupBy<T, K extends string | number>(\n array: T[],\n keyFn: (item: T) => K\n): Record<K, T[]> {\n return array.reduce((acc, item) => {\n const key = keyFn(item)\n if (!acc[key]) acc[key] = []\n acc[key].push(item)\n return acc\n }, {} as Record<K, T[]>)\n}\n\n/**\n * Configuration for retry with exponential backoff\n */\nexport interface RetryConfig {\n /** Maximum number of retry attempts (default: 3) */\n maxRetries?: number\n /** Base multiplier for backoff in ms (default: 1000). Backoff = 2^attempt * multiplier */\n backoffMultiplier?: number\n /** Optional AbortSignal for cancellation */\n signal?: AbortSignal\n /** Callback invoked before each retry with the error and attempt number */\n onRetry?: (error: Error, attempt: number) => void\n}\n\n/**\n * Execute an async function with retry and exponential backoff\n *\n * Retries the provided function on failure with exponential backoff delays.\n * Backoff formula: 2^attempt * backoffMultiplier (default: 1s, 2s, 4s, etc.)\n *\n * @param fn - Async function to execute\n * @param config - Retry configuration\n * @returns Promise resolving to the function result\n * @throws Last error if all retries exhausted\n *\n * @example\n * ```ts\n * // Basic usage\n * const result = await retryWithBackoff(\n * () => fetch('/api/health'),\n * { maxRetries: 3 }\n * )\n *\n * // With cancellation\n * const controller = new AbortController()\n * const result = await retryWithBackoff(\n * () => fetch('/api/health'),\n * { maxRetries: 3, signal: controller.signal }\n * )\n * // Later: controller.abort()\n *\n * // With retry callback\n * const result = await retryWithBackoff(\n * () => fetch('/api/health'),\n * {\n * maxRetries: 3,\n * onRetry: (error, attempt) => console.log(`Retry ${attempt}: ${error.message}`)\n * }\n * )\n * ```\n */\nexport async function retryWithBackoff<T>(\n fn: () => Promise<T>,\n config: RetryConfig = {}\n): Promise<T> {\n const { maxRetries = 3, backoffMultiplier = 1000, signal, onRetry } = config\n let lastError: Error | undefined\n\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n // Check for cancellation before each attempt\n if (signal?.aborted) {\n throw lastError ?? new Error('Retry cancelled')\n }\n\n try {\n return await fn()\n } catch (error) {\n lastError = error instanceof Error ? error : new Error('Retry failed')\n\n // Don't wait after last attempt\n if (attempt < maxRetries - 1) {\n // Notify before retry\n onRetry?.(lastError, attempt + 1)\n\n // Exponential backoff: 2^0 * mult, 2^1 * mult, 2^2 * mult, etc.\n const delay = Math.pow(2, attempt) * backoffMultiplier\n\n // Wait with timeout\n await new Promise<void>((resolve) => setTimeout(resolve, delay))\n\n // Check for cancellation after waiting\n if (signal?.aborted) {\n throw lastError ?? new Error('Retry cancelled')\n }\n }\n }\n }\n\n throw lastError\n}\n","/**\n * URL utilities for @mdxui/do\n *\n * Provides functions to derive RPC and sync URLs from the base doUrl configuration.\n * These utilities ensure consistent URL formatting across the package.\n *\n * @module\n */\n\n/**\n * Convert WebSocket URL to HTTP URL for RPC\n *\n * Transforms a doUrl into an RPC endpoint URL:\n * - Converts wss:// to https:// and ws:// to http://\n * - Strips trailing /sync if present\n * - Appends /rpc\n *\n * @param doUrl - The base DO URL (e.g., 'wss://api.do/do/workspace' or 'wss://api.do/do/workspace/sync')\n * @returns The derived RPC URL (e.g., 'https://api.do/do/workspace/rpc')\n *\n * @example\n * ```typescript\n * deriveRpcUrl('wss://api.do/do/workspace')\n * // => 'https://api.do/do/workspace/rpc'\n *\n * deriveRpcUrl('wss://api.do/do/workspace/sync')\n * // => 'https://api.do/do/workspace/rpc'\n * ```\n */\nexport function deriveRpcUrl(doUrl: string): string {\n let rpcUrl = doUrl.replace(/^wss:/, 'https:').replace(/^ws:/, 'http:')\n rpcUrl = rpcUrl.replace(/\\/sync$/, '')\n return `${rpcUrl}/rpc`\n}\n\n/**\n * Ensure sync URL ends with /sync\n *\n * Normalizes a doUrl to ensure it ends with /sync for WebSocket connections.\n *\n * @param doUrl - The base DO URL\n * @returns The normalized sync URL with /sync suffix\n *\n * @example\n * ```typescript\n * deriveSyncUrl('wss://api.do/do/workspace')\n * // => 'wss://api.do/do/workspace/sync'\n *\n * deriveSyncUrl('wss://api.do/do/workspace/sync')\n * // => 'wss://api.do/do/workspace/sync' (unchanged)\n * ```\n */\nexport function deriveSyncUrl(doUrl: string): string {\n if (doUrl.endsWith('/sync')) {\n return doUrl\n }\n return `${doUrl}/sync`\n}\n","'use client'\n\n/**\n * Health check hook for @mdxui/do\n *\n * Provides a reusable hook for checking API endpoint connectivity\n * with retry logic and exponential backoff.\n *\n * @module\n */\n\nimport { useState, useEffect, useCallback, useRef } from 'react'\nimport { fetchWithTimeout, DEFAULT_REQUEST_TIMEOUT } from './fetch-with-timeout'\nimport { retryWithBackoff } from './utils'\n\n/**\n * Configuration for health check behavior\n */\nexport interface HealthCheckConfig {\n /** API endpoint base URL (e.g., 'https://api.do/admin') */\n apiEndpoint: string\n /** Optional auth token for Bearer authentication */\n authToken?: string\n /** Request timeout in milliseconds (defaults to DEFAULT_REQUEST_TIMEOUT) */\n requestTimeout?: number\n /** Number of retry attempts (defaults to 3) */\n healthCheckRetries?: number\n}\n\n/**\n * Result of the health check hook\n */\nexport interface HealthCheckResult {\n /** Whether the API is reachable */\n isConnected: boolean\n /** Error from the last failed attempt, if any */\n connectionError?: Error\n /** Manually trigger a health check */\n checkConnection: () => Promise<void>\n /** Whether a check is currently in progress */\n isChecking: boolean\n}\n\n/**\n * Hook to perform health checks against an API endpoint\n *\n * Automatically checks connection on mount with retry and exponential backoff.\n * Can also be manually triggered via the returned `checkConnection` function.\n *\n * @param config - Health check configuration\n * @returns Health check state and manual trigger function\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const { isConnected, connectionError, checkConnection } = useHealthCheck({\n * apiEndpoint: 'https://api.do/admin',\n * authToken: 'my-token',\n * healthCheckRetries: 3,\n * })\n *\n * if (connectionError) {\n * return <div>Connection failed: {connectionError.message}</div>\n * }\n *\n * if (!isConnected) {\n * return <div>Connecting...</div>\n * }\n *\n * return <div>Connected!</div>\n * }\n * ```\n */\nexport function useHealthCheck(config: HealthCheckConfig): HealthCheckResult {\n const [isConnected, setIsConnected] = useState(false)\n const [connectionError, setConnectionError] = useState<Error>()\n const [isChecking, setIsChecking] = useState(true)\n\n const { apiEndpoint, authToken, requestTimeout, healthCheckRetries = 3 } = config\n\n // AbortController for cancellation support in useEffect\n const abortControllerRef = useRef<AbortController | null>(null)\n\n /**\n * Performs a single health check request\n * Shared between manual checkConnection and useEffect\n */\n const performHealthCheck = useCallback(async (): Promise<void> => {\n const response = await fetchWithTimeout(`${apiEndpoint}/health`, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n ...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),\n },\n timeout: requestTimeout ?? DEFAULT_REQUEST_TIMEOUT,\n })\n\n if (!response.ok) {\n throw new Error(`Health check failed: ${response.status}`)\n }\n }, [apiEndpoint, authToken, requestTimeout])\n\n const checkConnection = useCallback(async () => {\n setIsChecking(true)\n\n try {\n await retryWithBackoff(performHealthCheck, {\n maxRetries: healthCheckRetries,\n backoffMultiplier: 1000,\n })\n\n setIsConnected(true)\n setConnectionError(undefined)\n } catch (error) {\n setIsConnected(false)\n setConnectionError(error instanceof Error ? error : new Error('Connection failed'))\n } finally {\n setIsChecking(false)\n }\n }, [performHealthCheck, healthCheckRetries])\n\n // Check connection on mount\n useEffect(() => {\n abortControllerRef.current = new AbortController()\n const { signal } = abortControllerRef.current\n\n async function runCheck() {\n try {\n await retryWithBackoff(performHealthCheck, {\n maxRetries: healthCheckRetries,\n backoffMultiplier: 1000,\n signal,\n })\n\n if (!signal.aborted) {\n setIsConnected(true)\n setConnectionError(undefined)\n setIsChecking(false)\n }\n } catch (error) {\n if (!signal.aborted) {\n setIsConnected(false)\n setConnectionError(error instanceof Error ? error : new Error('Connection failed'))\n setIsChecking(false)\n }\n }\n }\n\n runCheck()\n\n return () => {\n abortControllerRef.current?.abort()\n }\n }, [performHealthCheck, healthCheckRetries])\n\n return {\n isConnected,\n connectionError,\n checkConnection,\n isChecking,\n }\n}\n","'use client'\n\nimport * as React from 'react'\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query'\nimport { DO as DotdoProvider, useConnectionState, useDotdoContext, type DotdoContextValue } from '@dotdo/react'\nimport type { ConnectionState } from '@dotdo/client'\nimport { DataProviderContext } from '@mdxui/app'\nimport type { DataProvider } from '@mdxui/app'\nimport type { DOAdminConfig, SyncStatus } from '../types'\nimport { deriveRpcUrl, deriveSyncUrl } from '../lib/url-utils'\nimport { useHealthCheck } from '../lib/use-health-check'\n\n/**\n * Default collections to sync when not specified\n */\nconst DEFAULT_COLLECTIONS = ['Thing', 'Agent', 'Workflow'] as const\n\n/**\n * Create a QueryClient with sensible defaults for @mdxui/do\n */\nfunction createQueryClient() {\n return new QueryClient({\n defaultOptions: {\n queries: {\n // Stale time: 30 seconds (data considered fresh)\n staleTime: 30 * 1000,\n // Cache time: 5 minutes (keep in cache after unmount)\n gcTime: 5 * 60 * 1000,\n // Retry failed requests 1 time\n retry: 1,\n // Refetch on window focus for fresh data\n refetchOnWindowFocus: true,\n },\n mutations: {\n // Retry mutations once on failure\n retry: 1,\n },\n },\n })\n}\n\n/**\n * Map @dotdo/client ConnectionState to our SyncStatus\n */\nfunction mapConnectionStateToSyncStatus(state: ConnectionState): SyncStatus {\n switch (state) {\n case 'connecting':\n return 'connecting'\n case 'connected':\n return 'synced'\n case 'reconnecting':\n return 'syncing'\n case 'disconnected':\n return 'disconnected'\n default:\n return 'disconnected'\n }\n}\n\n/**\n * DO Context value\n */\nexport interface DOContextValue {\n /** Admin configuration */\n config: DOAdminConfig\n /** Current namespace */\n namespace: string\n /** Set current namespace */\n setNamespace: (ns: string) => void\n /** Current user ID */\n userId?: string\n /** Is connected to backend? */\n isConnected: boolean\n /** Connection error */\n connectionError?: Error\n /** Sync status (TanStack DB mode) */\n syncStatus: SyncStatus\n /** RPC URL derived from doUrl */\n rpcUrl: string | null\n /** Sync URL derived from doUrl */\n syncUrl: string | null\n /** Whether using TanStack DB mode */\n isTanStackDBMode: boolean\n /** TanStack DB context from @dotdo/react (null in REST mode) */\n db: DotdoContextValue | null\n /** Collections being synced */\n collections: readonly string[]\n}\n\n/**\n * DO Context\n */\nconst DOContext = React.createContext<DOContextValue | null>(null)\n\n/**\n * Hook to access DO context\n */\nexport function useDO(): DOContextValue {\n const context = React.useContext(DOContext)\n if (!context) {\n throw new Error('useDO must be used within a DOProvider')\n }\n return context\n}\n\n/**\n * Hook to access derived URLs for sync operations\n */\nexport function useDOUrls() {\n const { rpcUrl, syncUrl, config } = useDO()\n return {\n rpcUrl,\n syncUrl,\n apiEndpoint: config.apiEndpoint,\n doUrl: config.doUrl ?? null,\n }\n}\n\n/**\n * Hook to check sync status\n */\nexport function useSyncStatus(): SyncStatus {\n const { syncStatus } = useDO()\n return syncStatus\n}\n\n/**\n * Props for DOProvider\n */\nexport interface DOProviderProps {\n /** Admin configuration */\n config: DOAdminConfig\n /** Initial namespace */\n initialNamespace?: string\n /** Current user ID */\n userId?: string\n /** Children */\n children: React.ReactNode\n /** Custom QueryClient (for testing) */\n queryClient?: QueryClient\n}\n\n/**\n * DOProvider - Main provider for @mdxui/do\n *\n * Supports two modes:\n * - **REST API mode**: Traditional fetch-based API calls (apiEndpoint only)\n * - **TanStack DB mode**: Reactive local-first with WebSocket sync (doUrl provided)\n *\n * @example\n * ```tsx\n * // REST API mode\n * <DOProvider\n * config={{\n * apiEndpoint: 'https://api.do/admin',\n * authMethod: 'jwt',\n * bindings: [...],\n * realTimeUpdates: true,\n * }}\n * initialNamespace=\"my-app\"\n * userId=\"user_123\"\n * >\n * <App />\n * </DOProvider>\n *\n * // TanStack DB mode (recommended)\n * <DOProvider\n * config={{\n * apiEndpoint: 'https://api.do/admin',\n * doUrl: 'wss://api.do/do/workspace',\n * authMethod: 'jwt',\n * authToken: 'xxx',\n * bindings: [...],\n * realTimeUpdates: true,\n * collections: ['Task', 'User', 'Project'],\n * offline: { enabled: true },\n * }}\n * >\n * <App />\n * </DOProvider>\n * ```\n */\n/**\n * Inner provider component that bridges @dotdo/react context to our DOContext.\n * This component must be rendered inside the DotdoProvider to access useConnectionState.\n */\ninterface DOProviderInnerProps {\n config: DOAdminConfig\n namespace: string\n setNamespace: (ns: string) => void\n userId?: string\n isConnected: boolean\n connectionError?: Error\n rpcUrl: string | null\n syncUrl: string | null\n isTanStackDBMode: boolean\n collections: readonly string[]\n children: React.ReactNode\n}\n\nfunction DOProviderInner({\n config,\n namespace,\n setNamespace,\n userId,\n isConnected,\n connectionError,\n rpcUrl,\n syncUrl,\n isTanStackDBMode,\n collections,\n children,\n}: DOProviderInnerProps) {\n // Get connection state from @dotdo/react when in TanStack DB mode\n const connectionState = useConnectionState()\n const dotdoContext = useDotdoContext()\n\n // Map @dotdo/react connection state to our sync status\n const syncStatus = isTanStackDBMode\n ? mapConnectionStateToSyncStatus(connectionState)\n : 'disconnected'\n\n // Get db reference from @dotdo/react context\n const db = isTanStackDBMode ? dotdoContext : null\n\n // Context value\n const value = React.useMemo<DOContextValue>(\n () => ({\n config,\n namespace,\n setNamespace,\n userId,\n isConnected,\n connectionError,\n syncStatus,\n rpcUrl,\n syncUrl,\n isTanStackDBMode,\n db,\n collections,\n }),\n [\n config,\n namespace,\n setNamespace,\n userId,\n isConnected,\n connectionError,\n syncStatus,\n rpcUrl,\n syncUrl,\n isTanStackDBMode,\n db,\n collections,\n ]\n )\n\n return <DOContext.Provider value={value}>{children}</DOContext.Provider>\n}\n\n/**\n * REST mode provider - used when doUrl is not configured.\n * Does not wrap with @dotdo/react provider.\n */\nfunction DOProviderREST({\n config,\n namespace,\n setNamespace,\n userId,\n isConnected,\n connectionError,\n children,\n}: Omit<DOProviderInnerProps, 'rpcUrl' | 'syncUrl' | 'isTanStackDBMode' | 'collections'>) {\n // Context value for REST mode\n const value = React.useMemo<DOContextValue>(\n () => ({\n config,\n namespace,\n setNamespace,\n userId,\n isConnected,\n connectionError,\n syncStatus: 'disconnected',\n rpcUrl: null,\n syncUrl: null,\n isTanStackDBMode: false,\n db: null,\n collections: DEFAULT_COLLECTIONS,\n }),\n [config, namespace, setNamespace, userId, isConnected, connectionError]\n )\n\n return <DOContext.Provider value={value}>{children}</DOContext.Provider>\n}\n\nexport function DOProvider({\n config,\n initialNamespace = 'default',\n userId,\n children,\n queryClient: providedQueryClient,\n}: DOProviderProps) {\n // Namespace state\n const [namespace, setNamespace] = React.useState(initialNamespace)\n\n // Derive TanStack DB mode\n const isTanStackDBMode = Boolean(config.doUrl)\n const rpcUrl = config.doUrl ? deriveRpcUrl(config.doUrl) : null\n const syncUrl = config.doUrl ? deriveSyncUrl(config.doUrl) : null\n const collections = config.collections ?? DEFAULT_COLLECTIONS\n\n // Health check for API connectivity (with retry and exponential backoff)\n const { isConnected, connectionError } = useHealthCheck({\n apiEndpoint: config.apiEndpoint,\n authToken: config.authToken,\n requestTimeout: config.requestTimeout,\n healthCheckRetries: config.healthCheckRetries,\n })\n\n // Use provided QueryClient or create one\n const [queryClient] = React.useState(() => providedQueryClient ?? createQueryClient())\n\n // Build @dotdo/react client config\n const dotdoConfig = React.useMemo(() => {\n if (!config.authToken) return undefined\n return {\n auth: {\n token: config.authToken,\n },\n }\n }, [config.authToken])\n\n // Render tree with appropriate providers\n const content = isTanStackDBMode ? (\n // TanStack DB mode: wrap with @dotdo/react provider\n <DotdoProvider ns={config.doUrl!} config={dotdoConfig}>\n <DOProviderInner\n config={config}\n namespace={namespace}\n setNamespace={setNamespace}\n userId={userId}\n isConnected={isConnected}\n connectionError={connectionError}\n rpcUrl={rpcUrl}\n syncUrl={syncUrl}\n isTanStackDBMode={true}\n collections={collections}\n >\n {children}\n </DOProviderInner>\n </DotdoProvider>\n ) : (\n // REST mode: no @dotdo/react wrapper\n <DOProviderREST\n config={config}\n namespace={namespace}\n setNamespace={setNamespace}\n userId={userId}\n isConnected={isConnected}\n connectionError={connectionError}\n >\n {children}\n </DOProviderREST>\n )\n\n return <QueryClientProvider client={queryClient}>{content}</QueryClientProvider>\n}\n\n/**\n * Hook to access the QueryClient for manual cache operations\n */\nexport { useQueryClient } from '@tanstack/react-query'\n\n/**\n * Hook to safely access the DataProvider, returning null if not available.\n *\n * This is useful for hooks that can optionally use a DataProvider when one\n * is configured, but should fall back to RPC when no DataProvider is present.\n *\n * @example\n * ```tsx\n * const dataProvider = useDataProviderSafe()\n * if (dataProvider) {\n * // Use DataProvider\n * } else {\n * // Fall back to RPC\n * }\n * ```\n */\nexport function useDataProviderSafe(): DataProvider | null {\n return React.useContext(DataProviderContext)\n}\n"]}
|