@kehto/services 0.14.1 → 0.16.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/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ServiceHandler, Signer } from '@kehto/runtime';
1
+ import { ServiceHandler, Signer, RelayEventResult } from '@kehto/runtime';
2
2
  import { IdentityGetProfileMessage, IdentityGetProfileResultMessage, IdentityGetFollowsMessage, IdentityGetFollowsResultMessage, IdentityGetListMessage, IdentityGetListResultMessage, IdentityGetZapsMessage, IdentityGetZapsResultMessage, IdentityGetMutesMessage, IdentityGetMutesResultMessage, IdentityGetBlockedMessage, IdentityGetBlockedResultMessage, IdentityGetBadgesMessage, IdentityGetBadgesResultMessage } from '@napplet/nap/identity/types';
3
3
  import { NostrFilter, NostrEvent, EventTemplate, LinkOpenOptions, LinkOpenResult, ListSupport, ListRef, ListItem, ListOptions, ListMutationResult, SerialOpenRequest, SerialOpenResult, BleEvent, BleOpenRequest, BleOpenResult, BleService, BleAttribute, BleWriteOptions, WebrtcEvent, WebrtcOpenRequest, WebrtcOpenResult, CommonProfileTarget, CommonProfileResult, CommonFollowsResult, CommonActionResult, CommonReaction, CommonReportTarget, CommonReportReason } from '@napplet/core';
4
4
  import { MediaMetadata, MediaAction } from '@napplet/nap/media/types';
@@ -1635,7 +1635,7 @@ declare function createResourceService(options: ResourceServiceOptions): Resourc
1635
1635
  * Inbound: outbox.getEvent, outbox.query, outbox.subscribe, outbox.close,
1636
1636
  * outbox.publish, outbox.resolveRelays
1637
1637
  * Outbound: outbox.getEvent.result, outbox.query.result, outbox.event,
1638
- * outbox.eose, outbox.closed, outbox.publish.result,
1638
+ * outbox.closed, outbox.publish.result,
1639
1639
  * outbox.resolveRelays.result
1640
1640
  *
1641
1641
  * The shell owns relay discovery, routing, fallback, deduplication, signature
@@ -1653,21 +1653,12 @@ declare function createResourceService(options: ResourceServiceOptions): Resourc
1653
1653
  * @packageDocumentation
1654
1654
  */
1655
1655
 
1656
- /**
1657
- * Relay-selection strategy:
1658
- * - `outbox` — query/publish via author write relays (the outbox model)
1659
- * - `inbox` — query/publish via recipient read relays (the inbox model)
1660
- * - `auto` — let the shell choose per its policy and relay intelligence
1661
- */
1662
- type OutboxStrategy = 'outbox' | 'inbox' | 'auto';
1663
1656
  /** Options for a single-event outbox lookup. */
1664
1657
  interface OutboxEventOptions {
1665
1658
  /** Author hint for routing through the author's outbox relays. */
1666
1659
  author?: string;
1667
1660
  /** Relay hints; treated as a hint subject to shell validation, not a bypass. */
1668
1661
  relays?: string[];
1669
- /** Relay-selection strategy. */
1670
- strategy?: OutboxStrategy;
1671
1662
  /** Wall-clock budget for the lookup, in milliseconds. */
1672
1663
  timeoutMs?: number;
1673
1664
  }
@@ -1677,8 +1668,6 @@ interface OutboxQueryOptions {
1677
1668
  authors?: string[];
1678
1669
  /** Relay hints; treated as a hint subject to shell validation, not a bypass. */
1679
1670
  relays?: string[];
1680
- /** Relay-selection strategy. */
1681
- strategy?: OutboxStrategy;
1682
1671
  /** Maximum events to collect. */
1683
1672
  limit?: number;
1684
1673
  /** Wall-clock budget for the query, in milliseconds. */
@@ -1686,8 +1675,6 @@ interface OutboxQueryOptions {
1686
1675
  }
1687
1676
  /** Options for a live outbox subscription. */
1688
1677
  interface OutboxSubscribeOptions extends OutboxQueryOptions {
1689
- /** Keep the subscription open for real-time events after EOSE. */
1690
- live?: boolean;
1691
1678
  }
1692
1679
  /** Options for an outbox publish. */
1693
1680
  interface OutboxPublishOptions {
@@ -1695,8 +1682,6 @@ interface OutboxPublishOptions {
1695
1682
  relays?: string[];
1696
1683
  /** Recipient authors whose inbox relays should be included for directed events. */
1697
1684
  targetAuthors?: string[];
1698
- /** Relay-selection strategy. */
1699
- strategy?: OutboxStrategy;
1700
1685
  }
1701
1686
  /** A read/write target for relay-plan resolution. */
1702
1687
  interface OutboxTarget {
@@ -1706,8 +1691,6 @@ interface OutboxTarget {
1706
1691
  pubkey?: string;
1707
1692
  /** Whether the plan is for reading (their write relays) or writing (their read relays). */
1708
1693
  direction?: 'read' | 'write';
1709
- /** Relay-selection strategy. */
1710
- strategy?: OutboxStrategy;
1711
1694
  }
1712
1695
  /** The relay plan the shell would use for a target. */
1713
1696
  interface OutboxRelayPlan {
@@ -1720,10 +1703,8 @@ interface OutboxRelayPlan {
1720
1703
  }
1721
1704
  /** Outcome of a single-event outbox lookup. */
1722
1705
  interface OutboxEventResult {
1723
- /** The signature-validated event matching the requested event id, when found. */
1724
- event?: NostrEvent;
1725
- /** Relay URLs where the shell observed the event. */
1726
- relays: string[];
1706
+ /** The signature-validated event result matching the requested event id, when found. */
1707
+ result?: RelayEventResult;
1727
1708
  /** True when some relay lists or connections failed and the lookup was partial. */
1728
1709
  incomplete?: boolean;
1729
1710
  /** Error reason when the lookup could not complete or did not find the event. */
@@ -1731,10 +1712,8 @@ interface OutboxEventResult {
1731
1712
  }
1732
1713
  /** Outcome of an outbox query, as returned by the {@link OutboxRouter}. */
1733
1714
  interface OutboxResult {
1734
- /** Deduplicated, signature-validated events. */
1735
- events: NostrEvent[];
1736
- /** Map of event id -> relay URLs where the shell observed the event. */
1737
- relays: Record<string, string[]>;
1715
+ /** Deduplicated, signature-validated event results. */
1716
+ events: RelayEventResult[];
1738
1717
  /** True when some relay lists or connections failed and results are partial. */
1739
1718
  incomplete?: boolean;
1740
1719
  /** Error reason when the query could not complete. */
@@ -1755,10 +1734,8 @@ interface OutboxPublishResult {
1755
1734
  }
1756
1735
  /** Sink an {@link OutboxRouter} streams subscription lifecycle through. */
1757
1736
  interface OutboxSubscriptionSink {
1758
- /** Deliver a matching event; `relay` is the relay it was observed on, when known. */
1759
- event(event: NostrEvent, relay?: string): void;
1760
- /** Signal end-of-stored-events. */
1761
- eose(): void;
1737
+ /** Deliver a matching event result. */
1738
+ event(result: RelayEventResult): void;
1762
1739
  /** Signal that the subscription was closed upstream; `reason` is optional. */
1763
1740
  closed(reason?: string): void;
1764
1741
  }
@@ -1818,9 +1795,6 @@ declare function createOutboxService(options: OutboxServiceOptions): ServiceHand
1818
1795
  * - reading an author's events → their **write** relays (where they publish)
1819
1796
  * - writing to reach an author → their **read** relays (their inbox)
1820
1797
  *
1821
- * `strategy` overrides the direction default: `outbox` forces write relays,
1822
- * `inbox` forces read relays, `auto` (default) follows the read/write direction.
1823
- *
1824
1798
  * @example
1825
1799
  * ```ts
1826
1800
  * import { createOutboxService, createRelayPoolOutboxRouter } from '@kehto/services';
@@ -3027,4 +3001,4 @@ declare function createCordnRelayCoordinatorClient(options: CordnRelayCoordinato
3027
3001
  /** Create a Cordn/ContextVM-backed NAP-DM adapter. */
3028
3002
  declare function createCordnDmAdapter(options: CordnDmAdapterOptions): DmAdapter;
3029
3003
 
3030
- export { type AudioServiceOptions, type AudioSource, type BleServiceContext, type BleServiceOptions, type CacheServiceOptions, type CatalogIntentResolver, type CatalogIntentResolverOptions, type CommonServiceContext, type CommonServiceOptions, type ConfigSchemaValidation, type ConfigService, type ConfigServiceOptions, type CoordinatedRelayOptions, type CordnCodecResult, type CordnCoordinatorClient, type CordnCoordinatorSubscription, type CordnDmAdapterOptions, type CordnDmClient, type CordnGroupMessage, type CordnRelayCoordinatorOptions, type CountRequest, type CountResult, type CountServiceOptions, type DmAdapter, type DmConversation, type DmConversationPage, type DmConversationQuery, type DmHexPubkey, DmMemoryStore, type DmMessage, type DmMessagePage, type DmMessageQuery, type DmMessageStatus, type DmOk, type DmPeer, type DmRelayPool, type DmSendRequest, type DmSendResult, type DmService, type DmServiceOptions, type DmStatus, type DmSubscribeRequest, type DmSubscription, type DmTimestamp, type HostCacheBridge, type HostKeyEvent, type HostKeysBridge, type HostMediaBridge, type HttpUploaderOptions, type HttpUploaderRails, type IdentityServiceOptions, type IntentArchetypeSupport, type IntentAvailability, type IntentBehavior, type IntentCandidate, type IntentCatalogEntry, type IntentHandlerPreference, type IntentOpenParams, type IntentRequest, type IntentResolver, type IntentResolverContext, type IntentResult, type IntentServiceOptions, type IntentWindowController, type KeysServiceOptions, type LinkOpenContext, type LinkServiceOptions, type ListsServiceContext, type ListsServiceOptions, type ManifestArchetypeInput, type MaybePromise, type MediaMetadataLike, type MediaPlaybackOwner, type MediaServiceOptions, type MediaSessionCreateOptions, type MediaSessionTarget, type MediaSourceRef, type NdrDmAdapterOptions, type NdrRelayTransport, type NdrRelayTransportOptions, type NdrRumorLike, type NdrRuntimeLike, type Nip17DmAdapterOptions, type NostrTag, type Notification, type NotificationServiceOptions, type NotifyServiceOptions, type OutboxEventOptions, type OutboxEventResult, type OutboxPublishOptions, type OutboxPublishResult, type OutboxQueryOptions, type OutboxRelayPlan, type OutboxRelayPool, type OutboxResult, type OutboxRouter, type OutboxRouterSubscription, type OutboxServiceOptions, type OutboxStrategy, type OutboxSubscribeOptions, type OutboxSubscriptionSink, type OutboxTarget, type RailServerConfig, type RelayListEntry, type RelayPoolOutboxRouterOptions, type RelayPoolServiceOptions, type ResourceInfo, type ResourceInfoContext, type ResourceInfoProvider, type ResourceSchemeInfo, type ResourceService, type ResourceServiceOptions, type SerialServiceContext, type SerialServiceOptions, type SignEvent, type ThemeService, type ThemeServiceOptions, type UploadDimensions, type UploadInfo, type UploadInfoContext, type UploadInfoProvider, type UploadRail, type UploadRailInfo, type UploadRequest, type UploadResult, type UploadServiceOptions, type UploadState, type UploadStatus, type Uploader, type UploaderContext, type WebrtcServiceContext, type WebrtcServiceOptions, createAudioService, createBleService, createBrowserMediaBridge, createCacheService, createCatalogIntentResolver, createCommonService, createConfigService, createCoordinatedRelay, createCordnDmAdapter, createCordnRelayCoordinatorClient, createCountService, createDmService, createHttpUploader, createIdentityService, createIntentService, createKeysService, createLinkService, createListsService, createMediaService, createNdrDmAdapter, createNdrRelayTransport, createNip17DmAdapter, createNotificationService, createNotifyService, createOutboxService, createRelayPoolOutboxRouter, createRelayPoolService, createResourceService, createSerialService, createThemeService, createUploadService, createWebrtcService, manifestToIntentCatalogEntry };
3004
+ export { type AudioServiceOptions, type AudioSource, type BleServiceContext, type BleServiceOptions, type CacheServiceOptions, type CatalogIntentResolver, type CatalogIntentResolverOptions, type CommonServiceContext, type CommonServiceOptions, type ConfigSchemaValidation, type ConfigService, type ConfigServiceOptions, type CoordinatedRelayOptions, type CordnCodecResult, type CordnCoordinatorClient, type CordnCoordinatorSubscription, type CordnDmAdapterOptions, type CordnDmClient, type CordnGroupMessage, type CordnRelayCoordinatorOptions, type CountRequest, type CountResult, type CountServiceOptions, type DmAdapter, type DmConversation, type DmConversationPage, type DmConversationQuery, type DmHexPubkey, DmMemoryStore, type DmMessage, type DmMessagePage, type DmMessageQuery, type DmMessageStatus, type DmOk, type DmPeer, type DmRelayPool, type DmSendRequest, type DmSendResult, type DmService, type DmServiceOptions, type DmStatus, type DmSubscribeRequest, type DmSubscription, type DmTimestamp, type HostCacheBridge, type HostKeyEvent, type HostKeysBridge, type HostMediaBridge, type HttpUploaderOptions, type HttpUploaderRails, type IdentityServiceOptions, type IntentArchetypeSupport, type IntentAvailability, type IntentBehavior, type IntentCandidate, type IntentCatalogEntry, type IntentHandlerPreference, type IntentOpenParams, type IntentRequest, type IntentResolver, type IntentResolverContext, type IntentResult, type IntentServiceOptions, type IntentWindowController, type KeysServiceOptions, type LinkOpenContext, type LinkServiceOptions, type ListsServiceContext, type ListsServiceOptions, type ManifestArchetypeInput, type MaybePromise, type MediaMetadataLike, type MediaPlaybackOwner, type MediaServiceOptions, type MediaSessionCreateOptions, type MediaSessionTarget, type MediaSourceRef, type NdrDmAdapterOptions, type NdrRelayTransport, type NdrRelayTransportOptions, type NdrRumorLike, type NdrRuntimeLike, type Nip17DmAdapterOptions, type NostrTag, type Notification, type NotificationServiceOptions, type NotifyServiceOptions, type OutboxEventOptions, type OutboxEventResult, type OutboxPublishOptions, type OutboxPublishResult, type OutboxQueryOptions, type OutboxRelayPlan, type OutboxRelayPool, type OutboxResult, type OutboxRouter, type OutboxRouterSubscription, type OutboxServiceOptions, type OutboxSubscribeOptions, type OutboxSubscriptionSink, type OutboxTarget, type RailServerConfig, type RelayListEntry, type RelayPoolOutboxRouterOptions, type RelayPoolServiceOptions, type ResourceInfo, type ResourceInfoContext, type ResourceInfoProvider, type ResourceSchemeInfo, type ResourceService, type ResourceServiceOptions, type SerialServiceContext, type SerialServiceOptions, type SignEvent, type ThemeService, type ThemeServiceOptions, type UploadDimensions, type UploadInfo, type UploadInfoContext, type UploadInfoProvider, type UploadRail, type UploadRailInfo, type UploadRequest, type UploadResult, type UploadServiceOptions, type UploadState, type UploadStatus, type Uploader, type UploaderContext, type WebrtcServiceContext, type WebrtcServiceOptions, createAudioService, createBleService, createBrowserMediaBridge, createCacheService, createCatalogIntentResolver, createCommonService, createConfigService, createCoordinatedRelay, createCordnDmAdapter, createCordnRelayCoordinatorClient, createCountService, createDmService, createHttpUploader, createIdentityService, createIntentService, createKeysService, createLinkService, createListsService, createMediaService, createNdrDmAdapter, createNdrRelayTransport, createNip17DmAdapter, createNotificationService, createNotifyService, createOutboxService, createRelayPoolOutboxRouter, createRelayPoolService, createResourceService, createSerialService, createThemeService, createUploadService, createWebrtcService, manifestToIntentCatalogEntry };
package/dist/index.js CHANGED
@@ -426,6 +426,7 @@ function createIdentityService(options) {
426
426
  }
427
427
 
428
428
  // src/relay-pool-service.ts
429
+ import { createRelayEventResultWithHints } from "@kehto/runtime";
429
430
  var EOSE_FALLBACK_MS = 15e3;
430
431
  function createRelayPoolService(options) {
431
432
  const tracked = /* @__PURE__ */ new Map();
@@ -470,7 +471,7 @@ function createRelayPoolService(options) {
470
471
  }
471
472
  return;
472
473
  }
473
- send({ type: "relay.event", subId, event: item });
474
+ send({ type: "relay.event", subId, result: createRelayEventResultWithHints(item, relayUrls2) });
474
475
  }, relayUrls2);
475
476
  tracked.set(subKey, { handle, eoseTimer });
476
477
  return;
@@ -516,6 +517,7 @@ function createRelayPoolService(options) {
516
517
  }
517
518
 
518
519
  // src/cache-service.ts
520
+ import { createRelayEventResult } from "@kehto/runtime";
519
521
  function createCacheService(options) {
520
522
  return {
521
523
  descriptor: {
@@ -535,7 +537,7 @@ function createCacheService(options) {
535
537
  }
536
538
  options.query(filters).then((events) => {
537
539
  for (const event of events) {
538
- send({ type: "relay.event", subId, event });
540
+ send({ type: "relay.event", subId, result: createRelayEventResult(event) });
539
541
  }
540
542
  send({ type: "relay.eose", subId });
541
543
  }).catch(() => {
@@ -647,6 +649,7 @@ function isPlainObject(value) {
647
649
  }
648
650
 
649
651
  // src/coordinated-relay.ts
652
+ import { createRelayEventResult as createRelayEventResult2, createRelayEventResultWithHints as createRelayEventResultWithHints2 } from "@kehto/runtime";
650
653
  var DEFAULT_EOSE_TIMEOUT_MS = 15e3;
651
654
  function createCoordinatedRelay(options) {
652
655
  const timeoutMs = options.eoseTimeoutMs ?? DEFAULT_EOSE_TIMEOUT_MS;
@@ -669,10 +672,13 @@ function createCoordinatedRelay(options) {
669
672
  handleMessage(windowId, message, send) {
670
673
  const relayMessage = message;
671
674
  if (message.type === "relay.subscribe") {
672
- let deliver2 = function(event) {
675
+ let deliver2 = function(event, relayHints) {
673
676
  if (tracked.seenIds.has(event.id)) return;
674
677
  tracked.seenIds.add(event.id);
675
- if (subs.has(subKey)) send({ type: "relay.event", subId, event });
678
+ if (subs.has(subKey)) {
679
+ const result2 = relayHints ? createRelayEventResultWithHints2(event, relayHints) : createRelayEventResult2(event);
680
+ send({ type: "relay.event", subId, result: result2 });
681
+ }
676
682
  };
677
683
  var deliver = deliver2;
678
684
  const subId = relayMessage.subId;
@@ -728,7 +734,7 @@ function createCoordinatedRelay(options) {
728
734
  maybeSendEose(subKey, subId, send);
729
735
  return;
730
736
  }
731
- deliver2(item);
737
+ deliver2(item, relayUrls2);
732
738
  if (cacheAvailable) {
733
739
  try {
734
740
  options.cache.store(item);
@@ -2024,6 +2030,59 @@ var OUTBOX_DESCRIPTOR = {
2024
2030
  version: OUTBOX_SERVICE_VERSION,
2025
2031
  description: "NAP-OUTBOX outbox-aware relay routing \u2014 getEvent/query/subscribe/publish/resolveRelays"
2026
2032
  };
2033
+ function isRecord(value) {
2034
+ return typeof value === "object" && value !== null;
2035
+ }
2036
+ function stringArray(value) {
2037
+ if (!Array.isArray(value)) return void 0;
2038
+ const strings = value.filter((item) => typeof item === "string");
2039
+ return strings.length > 0 ? strings : void 0;
2040
+ }
2041
+ function uint(value) {
2042
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : void 0;
2043
+ }
2044
+ function sanitizeEventOptions(raw) {
2045
+ if (!isRecord(raw)) return void 0;
2046
+ const out = {};
2047
+ if (typeof raw.author === "string") out.author = raw.author;
2048
+ const relays = stringArray(raw.relays);
2049
+ if (relays) out.relays = relays;
2050
+ const timeoutMs = uint(raw.timeoutMs);
2051
+ if (timeoutMs !== void 0) out.timeoutMs = timeoutMs;
2052
+ return Object.keys(out).length > 0 ? out : void 0;
2053
+ }
2054
+ function sanitizeQueryOptions(raw) {
2055
+ if (!isRecord(raw)) return void 0;
2056
+ const out = {};
2057
+ const authors = stringArray(raw.authors);
2058
+ if (authors) out.authors = authors;
2059
+ const relays = stringArray(raw.relays);
2060
+ if (relays) out.relays = relays;
2061
+ const limit = uint(raw.limit);
2062
+ if (limit !== void 0) out.limit = limit;
2063
+ const timeoutMs = uint(raw.timeoutMs);
2064
+ if (timeoutMs !== void 0) out.timeoutMs = timeoutMs;
2065
+ return Object.keys(out).length > 0 ? out : void 0;
2066
+ }
2067
+ var sanitizeSubscribeOptions = sanitizeQueryOptions;
2068
+ function sanitizePublishOptions(raw) {
2069
+ if (!isRecord(raw)) return void 0;
2070
+ const out = {};
2071
+ const relays = stringArray(raw.relays);
2072
+ if (relays) out.relays = relays;
2073
+ const targetAuthors = stringArray(raw.targetAuthors);
2074
+ if (targetAuthors) out.targetAuthors = targetAuthors;
2075
+ return Object.keys(out).length > 0 ? out : void 0;
2076
+ }
2077
+ function sanitizeTarget(raw) {
2078
+ if (!isRecord(raw)) return void 0;
2079
+ const out = {};
2080
+ const authors = stringArray(raw.authors);
2081
+ if (authors) out.authors = authors;
2082
+ if (typeof raw.pubkey === "string") out.pubkey = raw.pubkey;
2083
+ if (raw.direction === "read" || raw.direction === "write") out.direction = raw.direction;
2084
+ return out;
2085
+ }
2027
2086
  function normalizeFilters(raw) {
2028
2087
  if (Array.isArray(raw)) {
2029
2088
  const filters = raw.filter((f) => typeof f === "object" && f !== null);
@@ -2040,17 +2099,14 @@ function buildEventQuery(eventId, eventOptions) {
2040
2099
  queryOptions.authors = [eventOptions.author];
2041
2100
  }
2042
2101
  if (Array.isArray(eventOptions?.relays)) queryOptions.relays = eventOptions.relays;
2043
- if (eventOptions?.strategy !== void 0) queryOptions.strategy = eventOptions.strategy;
2044
2102
  if (eventOptions?.timeoutMs !== void 0) queryOptions.timeoutMs = eventOptions.timeoutMs;
2045
2103
  return { filter, queryOptions };
2046
2104
  }
2047
2105
  function eventResultFromQuery(eventId, result2) {
2048
- const event = result2.events.find((candidate) => candidate.id === eventId);
2049
- const eventResult = {
2050
- relays: event ? result2.relays[eventId] ?? [] : []
2051
- };
2052
- if (event) eventResult.event = event;
2053
- if (!event) eventResult.error = result2.error ?? "not found";
2106
+ const relayResult = result2.events.find((candidate) => candidate.event.id === eventId);
2107
+ const eventResult = {};
2108
+ if (relayResult) eventResult.result = relayResult;
2109
+ if (!relayResult) eventResult.error = result2.error ?? "not found";
2054
2110
  else if (result2.error !== void 0) eventResult.error = result2.error;
2055
2111
  if (result2.incomplete !== void 0) eventResult.incomplete = result2.incomplete;
2056
2112
  return eventResult;
@@ -2060,15 +2116,14 @@ function fallbackGetEvent(router, eventId, eventOptions) {
2060
2116
  return router.query([filter], queryOptions).then((result2) => eventResultFromQuery(eventId, result2));
2061
2117
  }
2062
2118
  function sendEventResult(id, eventId, result2, send) {
2063
- if (result2.event && result2.event.id !== eventId) {
2064
- send({ type: "outbox.getEvent.result", id, relays: [], error: "not found" });
2119
+ if (result2.result && result2.result.event.id !== eventId) {
2120
+ send({ type: "outbox.getEvent.result", id, error: "not found" });
2065
2121
  return;
2066
2122
  }
2067
2123
  send({
2068
2124
  type: "outbox.getEvent.result",
2069
2125
  id,
2070
- ...result2.event === void 0 ? {} : { event: result2.event },
2071
- relays: result2.relays,
2126
+ ...result2.result === void 0 ? {} : { result: result2.result },
2072
2127
  ...result2.incomplete === void 0 ? {} : { incomplete: result2.incomplete },
2073
2128
  ...result2.error === void 0 ? {} : { error: result2.error }
2074
2129
  });
@@ -2077,12 +2132,12 @@ function handleGetEvent(router, msg, send) {
2077
2132
  const m = msg;
2078
2133
  const id = m.id ?? "";
2079
2134
  if (typeof m.eventId !== "string" || m.eventId.length === 0) {
2080
- send({ type: "outbox.getEvent.result", id, relays: [], error: "invalid filter" });
2135
+ send({ type: "outbox.getEvent.result", id, error: "invalid filter" });
2081
2136
  return;
2082
2137
  }
2083
2138
  const getEvent = router.getEvent ?? ((eventId, eventOptions) => fallbackGetEvent(router, eventId, eventOptions));
2084
- void getEvent(m.eventId, m.options).then((result2) => sendEventResult(id, m.eventId, result2, send)).catch(
2085
- (err2) => send({ type: "outbox.getEvent.result", id, relays: [], error: toErrorMessage(err2) })
2139
+ void getEvent(m.eventId, sanitizeEventOptions(m.options)).then((result2) => sendEventResult(id, m.eventId, result2, send)).catch(
2140
+ (err2) => send({ type: "outbox.getEvent.result", id, error: toErrorMessage(err2) })
2086
2141
  );
2087
2142
  }
2088
2143
  function createOutboxService(options) {
@@ -2096,20 +2151,19 @@ function createOutboxService(options) {
2096
2151
  const id = m.id ?? "";
2097
2152
  const filters = normalizeFilters(m.filters);
2098
2153
  if (!filters) {
2099
- send({ type: "outbox.query.result", id, events: [], relays: {}, error: "invalid filter" });
2154
+ send({ type: "outbox.query.result", id, events: [], error: "invalid filter" });
2100
2155
  return;
2101
2156
  }
2102
- void router.query(filters, m.options).then(
2157
+ void router.query(filters, sanitizeQueryOptions(m.options)).then(
2103
2158
  (result2) => send({
2104
2159
  type: "outbox.query.result",
2105
2160
  id,
2106
2161
  events: result2.events,
2107
- relays: result2.relays,
2108
2162
  ...result2.incomplete === void 0 ? {} : { incomplete: result2.incomplete },
2109
2163
  ...result2.error === void 0 ? {} : { error: result2.error }
2110
2164
  })
2111
2165
  ).catch(
2112
- (err2) => send({ type: "outbox.query.result", id, events: [], relays: {}, error: toErrorMessage(err2) })
2166
+ (err2) => send({ type: "outbox.query.result", id, events: [], error: toErrorMessage(err2) })
2113
2167
  );
2114
2168
  }
2115
2169
  function handleSubscribe(windowId, msg, send) {
@@ -2125,14 +2179,13 @@ function createOutboxService(options) {
2125
2179
  return;
2126
2180
  }
2127
2181
  const sink = {
2128
- event: (event, relay) => send({ type: "outbox.event", subId, event, ...relay === void 0 ? {} : { relay } }),
2129
- eose: () => send({ type: "outbox.eose", subId }),
2182
+ event: (result2) => send({ type: "outbox.event", subId, result: result2 }),
2130
2183
  closed: (reason) => {
2131
2184
  subscriptions.delete(subKey);
2132
2185
  send({ type: "outbox.closed", subId, ...reason === void 0 ? {} : { reason } });
2133
2186
  }
2134
2187
  };
2135
- subscriptions.set(subKey, router.subscribe(filters, m.options, sink));
2188
+ subscriptions.set(subKey, router.subscribe(filters, sanitizeSubscribeOptions(m.options), sink));
2136
2189
  }
2137
2190
  function handleClose(windowId, msg, send) {
2138
2191
  const m = msg;
@@ -2150,7 +2203,7 @@ function createOutboxService(options) {
2150
2203
  send({ type: "outbox.publish.result", id, ok: false, error: "invalid filter" });
2151
2204
  return;
2152
2205
  }
2153
- void router.publish(m.event, m.options).then(
2206
+ void router.publish(m.event, sanitizePublishOptions(m.options)).then(
2154
2207
  (result2) => send({
2155
2208
  type: "outbox.publish.result",
2156
2209
  id,
@@ -2167,11 +2220,12 @@ function createOutboxService(options) {
2167
2220
  function handleResolveRelays(msg, send) {
2168
2221
  const m = msg;
2169
2222
  const id = m.id ?? "";
2170
- if (!m.target || typeof m.target !== "object") {
2223
+ const target = sanitizeTarget(m.target);
2224
+ if (!target) {
2171
2225
  send({ type: "outbox.resolveRelays.result", id, error: "invalid filter" });
2172
2226
  return;
2173
2227
  }
2174
- void router.resolveRelays(m.target).then((plan) => send({ type: "outbox.resolveRelays.result", id, plan })).catch(
2228
+ void router.resolveRelays(target).then((plan) => send({ type: "outbox.resolveRelays.result", id, plan })).catch(
2175
2229
  (err2) => send({ type: "outbox.resolveRelays.result", id, error: toErrorMessage(err2) })
2176
2230
  );
2177
2231
  }
@@ -2219,6 +2273,7 @@ function toErrorMessage(err2) {
2219
2273
  }
2220
2274
 
2221
2275
  // src/relay-pool-outbox-router.ts
2276
+ import { createRelayEventResultWithHints as createRelayEventResultWithHints3 } from "@kehto/runtime";
2222
2277
  var DEFAULT_QUERY_TIMEOUT_MS = 4e3;
2223
2278
  function defaultRelayAllowed(url) {
2224
2279
  return typeof url === "string" && (url.startsWith("wss://") || url.startsWith("ws://"));
@@ -2231,11 +2286,6 @@ function deriveAuthors(filters, optionAuthors) {
2231
2286
  for (const author of optionAuthors ?? []) authors.add(author);
2232
2287
  return [...authors];
2233
2288
  }
2234
- function wantsWriteRelays(direction, strategy) {
2235
- if (strategy === "outbox") return true;
2236
- if (strategy === "inbox") return false;
2237
- return direction === "read";
2238
- }
2239
2289
  function allowed(ctx, urls) {
2240
2290
  const out = /* @__PURE__ */ new Set();
2241
2291
  for (const url of urls) {
@@ -2243,8 +2293,8 @@ function allowed(ctx, urls) {
2243
2293
  }
2244
2294
  return [...out];
2245
2295
  }
2246
- async function resolvePlan(ctx, pubkeys, direction, strategy, relayHints) {
2247
- const useWrite = wantsWriteRelays(direction, strategy);
2296
+ async function resolvePlan(ctx, pubkeys, direction, relayHints) {
2297
+ const useWrite = direction === "read";
2248
2298
  const collected = /* @__PURE__ */ new Set();
2249
2299
  const missingAuthors = [];
2250
2300
  let sawNip65 = false;
@@ -2292,10 +2342,10 @@ function admitEvent(ctx, collector, event) {
2292
2342
  );
2293
2343
  }
2294
2344
  function buildCollectResult(collector, timedOut) {
2295
- const events = [...collector.seen.values()];
2296
- const relayObj = {};
2297
- for (const event of events) relayObj[event.id] = [...collector.relayMap.get(event.id) ?? []];
2298
- return { events, relayMap: relayObj, incomplete: timedOut };
2345
+ const events = [...collector.seen.values()].map(
2346
+ (event) => createRelayEventResultWithHints3(event, [...collector.relayMap.get(event.id) ?? []])
2347
+ );
2348
+ return { events, incomplete: timedOut };
2299
2349
  }
2300
2350
  function collectFromRelays(ctx, filters, relayUrls2, timeoutMs) {
2301
2351
  return new Promise((resolve) => {
@@ -2340,28 +2390,27 @@ function relayPoolSubscribe(ctx, filters, relayUrl, cb) {
2340
2390
  }
2341
2391
  async function queryImpl(ctx, filters, options) {
2342
2392
  if (!ctx.relayPool.isAvailable()) {
2343
- return { events: [], relays: {}, incomplete: true, error: "relay list unavailable" };
2393
+ return { events: [], incomplete: true, error: "relay list unavailable" };
2344
2394
  }
2345
- const strategy = options?.strategy ?? "auto";
2346
2395
  const authors = deriveAuthors(filters, options?.authors);
2347
- const plan = await resolvePlan(ctx, authors, "read", strategy, options?.relays);
2396
+ const plan = await resolvePlan(ctx, authors, "read", options?.relays);
2348
2397
  if (plan.relays.length === 0) {
2349
- return { events: [], relays: {}, incomplete: true, error: "relay list unavailable" };
2398
+ return { events: [], incomplete: true, error: "relay list unavailable" };
2350
2399
  }
2351
2400
  const timeoutMs = options?.timeoutMs ?? ctx.defaultTimeoutMs;
2352
2401
  const collected = await collectFromRelays(ctx, filters, plan.relays, timeoutMs);
2353
2402
  const incomplete = collected.incomplete || (plan.missingAuthors?.length ?? 0) > 0;
2354
2403
  let events = collected.events;
2355
2404
  if (options?.limit !== void 0 && events.length > options.limit) {
2356
- events = [...events].sort((a, b) => b.created_at - a.created_at).slice(0, options.limit);
2405
+ events = [...events].sort((a, b) => b.event.created_at - a.event.created_at).slice(0, options.limit);
2357
2406
  }
2358
- const result2 = { events, relays: collected.relayMap };
2407
+ const result2 = { events };
2359
2408
  if (incomplete) result2.incomplete = true;
2360
2409
  return result2;
2361
2410
  }
2362
2411
  async function getEventImpl(ctx, eventId, options) {
2363
2412
  if (typeof eventId !== "string" || eventId.length === 0) {
2364
- return { relays: [], error: "invalid filter" };
2413
+ return { error: "invalid filter" };
2365
2414
  }
2366
2415
  const filter = { ids: [eventId] };
2367
2416
  const queryOptions = { limit: 1 };
@@ -2370,15 +2419,12 @@ async function getEventImpl(ctx, eventId, options) {
2370
2419
  queryOptions.authors = [options.author];
2371
2420
  }
2372
2421
  if (Array.isArray(options?.relays)) queryOptions.relays = options.relays;
2373
- if (options?.strategy !== void 0) queryOptions.strategy = options.strategy;
2374
2422
  if (options?.timeoutMs !== void 0) queryOptions.timeoutMs = options.timeoutMs;
2375
2423
  const queryResult = await queryImpl(ctx, [filter], queryOptions);
2376
- const event = queryResult.events.find((candidate) => candidate.id === eventId);
2377
- const result2 = {
2378
- relays: event ? queryResult.relays[eventId] ?? [] : []
2379
- };
2380
- if (event) result2.event = event;
2381
- if (!event) result2.error = queryResult.error ?? "not found";
2424
+ const eventResult = queryResult.events.find((candidate) => candidate.event.id === eventId);
2425
+ const result2 = {};
2426
+ if (eventResult) result2.result = eventResult;
2427
+ if (!eventResult) result2.error = queryResult.error ?? "not found";
2382
2428
  else if (queryResult.error !== void 0) result2.error = queryResult.error;
2383
2429
  if (queryResult.incomplete !== void 0) result2.incomplete = queryResult.incomplete;
2384
2430
  return result2;
@@ -2392,43 +2438,28 @@ function closeLiveSub(sub) {
2392
2438
  }
2393
2439
  sub.handles.length = 0;
2394
2440
  }
2395
- function attachLiveRelay(ctx, sub, filters, relayUrl, live, sink) {
2441
+ function attachLiveRelay(ctx, sub, filters, relayUrl, sink) {
2396
2442
  sub.handles.push(relayPoolSubscribe(ctx, filters, relayUrl, (item) => {
2397
2443
  if (sub.closed) return;
2398
- if (item === "EOSE") {
2399
- sub.eoseCount += 1;
2400
- if (!sub.eoseSent && sub.eoseCount >= sub.relayCount) {
2401
- sub.eoseSent = true;
2402
- sink.eose();
2403
- if (!live) {
2404
- sub.closed = true;
2405
- closeLiveSub(sub);
2406
- sink.closed();
2407
- }
2408
- }
2409
- return;
2410
- }
2444
+ if (item === "EOSE") return;
2411
2445
  if (sub.seen.has(item.id)) return;
2412
2446
  void ctx.verify(item).then((ok) => {
2413
2447
  if (!ok || sub.closed || sub.seen.has(item.id)) return;
2414
2448
  sub.seen.add(item.id);
2415
- sink.event(item, relayUrl);
2449
+ sink.event(createRelayEventResultWithHints3(item, [relayUrl]));
2416
2450
  });
2417
2451
  }));
2418
2452
  }
2419
2453
  function startSubscription(ctx, filters, options, sink) {
2420
- const live = options?.live ?? true;
2421
- const strategy = options?.strategy ?? "auto";
2422
2454
  const authors = deriveAuthors(filters, options?.authors);
2423
- const sub = { handles: [], seen: /* @__PURE__ */ new Set(), closed: false, eoseCount: 0, relayCount: 0, eoseSent: false };
2424
- void resolvePlan(ctx, authors, "read", strategy, options?.relays).then((plan) => {
2455
+ const sub = { handles: [], seen: /* @__PURE__ */ new Set(), closed: false };
2456
+ void resolvePlan(ctx, authors, "read", options?.relays).then((plan) => {
2425
2457
  if (sub.closed) return;
2426
2458
  if (plan.relays.length === 0) {
2427
2459
  sink.closed("relay list unavailable");
2428
2460
  return;
2429
2461
  }
2430
- sub.relayCount = plan.relays.length;
2431
- for (const relayUrl of plan.relays) attachLiveRelay(ctx, sub, filters, relayUrl, live, sink);
2462
+ for (const relayUrl of plan.relays) attachLiveRelay(ctx, sub, filters, relayUrl, sink);
2432
2463
  }).catch((err2) => {
2433
2464
  if (!sub.closed) sink.closed(err2 instanceof Error ? err2.message : "subscribe failed");
2434
2465
  });
@@ -2441,12 +2472,11 @@ function startSubscription(ctx, filters, options, sink) {
2441
2472
  };
2442
2473
  }
2443
2474
  async function resolvePublishTargets(ctx, signed, options) {
2444
- const strategy = options?.strategy ?? "auto";
2445
2475
  const targets = /* @__PURE__ */ new Set();
2446
- const authorPlan = await resolvePlan(ctx, [signed.pubkey], "read", strategy === "inbox" ? "auto" : "outbox");
2476
+ const authorPlan = await resolvePlan(ctx, [signed.pubkey], "read");
2447
2477
  for (const url of authorPlan.relays) targets.add(url);
2448
2478
  if (options?.targetAuthors && options.targetAuthors.length > 0) {
2449
- const inboxPlan = await resolvePlan(ctx, options.targetAuthors, "write", "inbox");
2479
+ const inboxPlan = await resolvePlan(ctx, options.targetAuthors, "write");
2450
2480
  for (const url of inboxPlan.relays) targets.add(url);
2451
2481
  }
2452
2482
  for (const url of options?.relays ?? []) targets.add(url);
@@ -2508,7 +2538,7 @@ function createRelayPoolOutboxRouter(options) {
2508
2538
  publish: (template, publishOptions) => publishImpl(ctx, template, publishOptions),
2509
2539
  resolveRelays: (target) => {
2510
2540
  const pubkeys = target.authors ?? (target.pubkey ? [target.pubkey] : []);
2511
- return resolvePlan(ctx, pubkeys, target.direction ?? "read", target.strategy ?? "auto");
2541
+ return resolvePlan(ctx, pubkeys, target.direction ?? "read");
2512
2542
  }
2513
2543
  };
2514
2544
  }