Split router out into its own library

This commit is contained in:
Jon Staab
2025-04-23 13:34:04 -07:00
parent 489a307a47
commit 2996e25359
42 changed files with 604 additions and 295 deletions
+3 -3
View File
@@ -1,5 +1,5 @@
import {Repository} from "@welshman/relay"
import {verifyEvent, TrustedEvent, SignedEvent} from "@welshman/util"
import {verifyEvent, TrustedEvent} from "@welshman/util"
import {AbstractAdapter} from "./adapter.js"
import {Pool} from "./pool.js"
@@ -12,8 +12,8 @@ export type NetContext = {
}
export const netContext: NetContext = {
pool: Pool.getSingleton(),
repository: Repository.getSingleton(),
pool: Pool.get(),
repository: Repository.get(),
isEventValid: (event, url) => verifyEvent(event),
isEventDeleted: (event, url) => netContext.repository.isDeleted(event),
}
+12 -11
View File
@@ -200,17 +200,18 @@ export const pull = async ({context, ...options}: PullOptions) => {
await Promise.all(
Array.from(idsByRelay.entries()).map(([relay, allIds]) => {
return Promise.all(
chunk(500, allIds).map(ids =>
new Promise<void>(resolve =>
requestOne({
relay,
context,
filters: [{ids}],
autoClose: true,
onClose: resolve,
onEvent: event => result.push(event as SignedEvent),
})
)
chunk(500, allIds).map(
ids =>
new Promise<void>(resolve =>
requestOne({
relay,
context,
filters: [{ids}],
autoClose: true,
onClose: resolve,
onEvent: event => result.push(event as SignedEvent),
}),
),
),
)
}),
+1 -1
View File
@@ -25,7 +25,7 @@ export class Pool {
_data = new Map<string, Socket>()
_subs: PoolSubscription[] = []
static getSingleton() {
static get() {
if (!poolSingleton) {
poolSingleton = new Pool()
}
+18 -23
View File
@@ -1,8 +1,6 @@
import {EventEmitter} from "events"
import {on, fromPairs, sleep, yieldThread} from "@welshman/lib"
import {SignedEvent} from "@welshman/util"
import {RelayMessage, ClientMessageType, isRelayOk} from "./message.js"
import {AbstractAdapter, AdapterEvent, AdapterContext, getAdapter} from "./adapter.js"
import {AdapterEvent, AdapterContext, getAdapter} from "./adapter.js"
export enum PublishStatus {
Sending = "publish:status:sending",
@@ -46,28 +44,25 @@ export const publishOne = (options: PublishOneOptions) =>
resolve(status)
}
adapter.on(
AdapterEvent.Receive,
(message: RelayMessage, url: string) => {
if (isRelayOk(message)) {
const [_, id, ok, detail] = message
adapter.on(AdapterEvent.Receive, (message: RelayMessage, url: string) => {
if (isRelayOk(message)) {
const [_, id, ok, detail] = message
if (id !== options.event.id) return
if (id !== options.event.id) return
if (ok) {
status = PublishStatus.Success
options.onSuccess?.(detail)
} else {
status = PublishStatus.Failure
options.onFailure?.(detail)
}
cleanup()
if (ok) {
status = PublishStatus.Success
options.onSuccess?.(detail)
} else {
status = PublishStatus.Failure
options.onFailure?.(detail)
}
},
)
options.signal?.addEventListener('abort', () => {
cleanup()
}
})
options.signal?.addEventListener("abort", () => {
if (status === PublishStatus.Pending) {
status = PublishStatus.Aborted
options.onAborted?.()
@@ -149,8 +144,8 @@ export const publish = async (options: PublishOptions) => {
options.onComplete?.()
}
},
})
)
}),
),
)
return status
+52 -45
View File
@@ -1,5 +1,16 @@
import {EventEmitter} from "events"
import {on, uniq, lt, flatten, addToMapKey, defer, Deferred, call, randomId, yieldThread, pushToMapKey, batcher} from "@welshman/lib"
import {
on,
uniq,
lt,
flatten,
addToMapKey,
defer,
Deferred,
call,
randomId,
pushToMapKey,
batcher,
} from "@welshman/lib"
import {
Filter,
getAddress,
@@ -9,9 +20,8 @@ import {
getFilterResultCardinality,
} from "@welshman/util"
import {RelayMessage, ClientMessageType, isRelayEvent, isRelayEose} from "./message.js"
import {getAdapter, AdapterContext, AbstractAdapter, AdapterEvent} from "./adapter.js"
import {getAdapter, AdapterContext, AdapterEvent} from "./adapter.js"
import {SocketEvent, SocketStatus} from "./socket.js"
import {Unsubscriber} from "./util.js"
import {netContext} from "./context.js"
import {Tracker} from "./tracker.js"
@@ -160,7 +170,6 @@ export const request = async (options: RequestOptions) => {
const ctrl = new AbortController()
const signal = options.signal ? AbortSignal.any([options.signal, ctrl.signal]) : ctrl.signal
const threshold = options.threshold || 1
const promises: Promise<TrustedEvent[]>[] = []
if (relays.size !== options.relays.length) {
console.warn("Non-unique relays passed to request")
@@ -181,14 +190,13 @@ export const request = async (options: RequestOptions) => {
options.onClose?.()
ctrl.abort()
}
}
})
)
)
},
}),
),
),
)
}
export type LoaderOptions = {
delay: number
timeout?: number
@@ -251,7 +259,7 @@ export const makeLoader = (options: LoaderOptions) =>
resultsByRequest.set(request, defer())
// Propagate abort when all requests have been closed for a given relay
request.signal?.addEventListener('abort', () => close(relay, request))
request.signal?.addEventListener("abort", () => close(relay, request))
}
}
@@ -268,47 +276,46 @@ export const makeLoader = (options: LoaderOptions) =>
signalsByRelay.set(relay, AbortSignal.any(signals))
}
Array.from(requestsByRelay).forEach(
async ([relay, requests]) => {
// Union all filters for a given request and send them together
const filters = unionFilters(requests.flatMap(r => r.filters))
Array.from(requestsByRelay).forEach(async ([relay, requests]) => {
// Union all filters for a given request and send them together
const filters = unionFilters(requests.flatMap(r => r.filters))
// Propagate events to caller, but only for requests that have not been aborted
const getOpenRequests = () =>
requests.filter(request => !closedRequestsByRelay.get(relay)?.has(request))
// Propagate events to caller, but only for requests that have not been aborted
const getOpenRequests = () =>
requests.filter(request => !closedRequestsByRelay.get(relay)?.has(request))
requestOne({
relay,
filters,
tracker,
autoClose: true,
signal: signalsByRelay.get(relay),
context: options.context,
isEventValid: options.isEventValid,
isEventDeleted: options.isEventDeleted,
onEvent: (event: TrustedEvent, url: string) => {
for (const request of getOpenRequests()) {
if (matchFilters(request.filters, event)) {
pushToMapKey(eventsByRequest, request, event)
request.onEvent?.(event, url)
requestOne({
relay,
filters,
tracker,
autoClose: true,
signal: signalsByRelay.get(relay),
context: options.context,
isEventValid: options.isEventValid,
isEventDeleted: options.isEventDeleted,
onEvent: (event: TrustedEvent, url: string) => {
for (const request of getOpenRequests()) {
if (matchFilters(request.filters, event)) {
pushToMapKey(eventsByRequest, request, event)
request.onEvent?.(event, url)
// Calculate cardinality for unioned filters so that we can return early
if (request.filters.length === 1) {
const cardinality = getFilterResultCardinality(request.filters[0])
// Calculate cardinality for unioned filters so that we can return early
if (request.filters.length === 1) {
const cardinality = getFilterResultCardinality(request.filters[0])
if (eventsByRequest.get(request)?.length === cardinality) {
close(relay, request)
}
if (eventsByRequest.get(request)?.length === cardinality) {
close(relay, request)
}
}
}
},
onDisconnect: (url: string) => getOpenRequests().forEach(request => request.onDisconnect?.(url)),
onEose: (url: string) => getOpenRequests().forEach(request => request.onEose?.(url)),
onClose: () => requests.forEach(request => close(relay, request)),
})
}
)
}
},
onDisconnect: (url: string) =>
getOpenRequests().forEach(request => request.onDisconnect?.(url)),
onEose: (url: string) => getOpenRequests().forEach(request => request.onEose?.(url)),
onClose: () => requests.forEach(request => close(relay, request)),
})
})
return allRequests.map(r => resultsByRequest.get(r)!)
})