feat: kind schemas and better man pages

This commit is contained in:
Alejandro Gómez
2025-12-18 10:05:45 +01:00
parent 3b06e23686
commit a7dd4635dc
13 changed files with 2911 additions and 257 deletions

View File

@@ -5,15 +5,15 @@ import { useEventStore, useObservableMemo } from "applesauce-react/hooks";
import { isNostrEvent } from "@/lib/type-guards";
interface UseLiveTimelineOptions {
limit?: number;
stream?: boolean;
limit?: number;
stream?: boolean;
}
interface UseLiveTimelineReturn {
events: NostrEvent[];
loading: boolean;
error: Error | null;
eoseReceived: boolean;
events: NostrEvent[];
loading: boolean;
error: Error | null;
eoseReceived: boolean;
}
/**
@@ -27,103 +27,103 @@ interface UseLiveTimelineReturn {
* @returns Object containing events array (from store, sorted), loading state, and error
*/
export function useLiveTimeline(
id: string,
filters: Filter | Filter[],
relays: string[],
options: UseLiveTimelineOptions = { limit: 200 },
id: string,
filters: Filter | Filter[],
relays: string[],
options: UseLiveTimelineOptions = { limit: 1000 },
): UseLiveTimelineReturn {
const eventStore = useEventStore();
const { limit, stream = false } = options;
const [loading, setLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);
const [eoseReceived, setEoseReceived] = useState(false);
const eventStore = useEventStore();
const { limit, stream = false } = options;
const [loading, setLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);
const [eoseReceived, setEoseReceived] = useState(false);
// Stabilize filters and relays for dependency array
// Using JSON.stringify and .join() for deep comparison - this is intentional
// eslint-disable-next-line react-hooks/exhaustive-deps
const stableFilters = useMemo(() => filters, [JSON.stringify(filters)]);
// eslint-disable-next-line react-hooks/exhaustive-deps
const stableRelays = useMemo(() => relays, [relays.join(",")]);
// Stabilize filters and relays for dependency array
// Using JSON.stringify and .join() for deep comparison - this is intentional
// eslint-disable-next-line react-hooks/exhaustive-deps
const stableFilters = useMemo(() => filters, [JSON.stringify(filters)]);
// eslint-disable-next-line react-hooks/exhaustive-deps
const stableRelays = useMemo(() => relays, [relays.join(",")]);
// 1. Subscription Effect - Fetch data and feed EventStore
useEffect(() => {
if (relays.length === 0) {
// 1. Subscription Effect - Fetch data and feed EventStore
useEffect(() => {
if (relays.length === 0) {
setLoading(false);
return;
}
console.log("LiveTimeline: Starting query", {
id,
relays,
filters,
limit,
stream,
});
setLoading(true);
setError(null);
setEoseReceived(false);
// Normalize filters to array
const filterArray = Array.isArray(filters) ? filters : [filters];
// Add limit to filters if specified
const filtersWithLimit = filterArray.map((f) => ({
...f,
limit: limit || f.limit,
}));
const observable = pool.subscription(relays, filtersWithLimit, {
retries: 5,
reconnect: 5,
resubscribe: true,
eventStore, // Automatically add events to store
});
const subscription = observable.subscribe(
(response) => {
// Response can be an event or 'EOSE' string
if (typeof response === "string") {
console.log("LiveTimeline: EOSE received");
setEoseReceived(true);
if (!stream) {
setLoading(false);
return;
}
} else if (isNostrEvent(response)) {
// Event automatically added to store by pool.subscription (via options.eventStore)
} else {
console.warn("LiveTimeline: Unexpected response type:", response);
}
},
(err: Error) => {
console.error("LiveTimeline: Error", err);
setError(err);
setLoading(false);
},
() => {
// Only set loading to false if not streaming
if (!stream) {
setLoading(false);
}
},
);
console.log("LiveTimeline: Starting query", {
id,
relays,
filters,
limit,
stream,
});
setLoading(true);
setError(null);
setEoseReceived(false);
// Normalize filters to array
const filterArray = Array.isArray(filters) ? filters : [filters];
// Add limit to filters if specified
const filtersWithLimit = filterArray.map((f) => ({
...f,
limit: limit || f.limit,
}));
const observable = pool.subscription(relays, filtersWithLimit, {
retries: 5,
reconnect: 5,
resubscribe: true,
eventStore, // Automatically add events to store
});
const subscription = observable.subscribe(
(response) => {
// Response can be an event or 'EOSE' string
if (typeof response === "string") {
console.log("LiveTimeline: EOSE received");
setEoseReceived(true);
if (!stream) {
setLoading(false);
}
} else if (isNostrEvent(response)) {
// Event automatically added to store by pool.subscription (via options.eventStore)
} else {
console.warn("LiveTimeline: Unexpected response type:", response);
}
},
(err: Error) => {
console.error("LiveTimeline: Error", err);
setError(err);
setLoading(false);
},
() => {
// Only set loading to false if not streaming
if (!stream) {
setLoading(false);
}
},
);
return () => {
subscription.unsubscribe();
};
}, [id, stableFilters, stableRelays, limit, stream, eventStore]);
// 2. Observable Effect - Read from EventStore
const timelineEvents = useObservableMemo(() => {
// eventStore.timeline returns an Observable that emits sorted array of events matching filter
// It updates whenever relevant events are added/removed from store
return eventStore.timeline(filters);
}, [stableFilters]);
return {
events: timelineEvents || [],
loading,
error,
eoseReceived,
return () => {
subscription.unsubscribe();
};
}, [id, stableFilters, stableRelays, limit, stream, eventStore]);
// 2. Observable Effect - Read from EventStore
const timelineEvents = useObservableMemo(() => {
// eventStore.timeline returns an Observable that emits sorted array of events matching filter
// It updates whenever relevant events are added/removed from store
return eventStore.timeline(filters);
}, [stableFilters]);
return {
events: timelineEvents || [],
loading,
error,
eoseReceived,
};
}