@qntx/nostr 0.0.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.
Files changed (146) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +25 -0
  3. package/dist/builder-BphJa-Y-.d.mts +67 -0
  4. package/dist/builder-q0VsvnYE.mjs +158 -0
  5. package/dist/builder-q0VsvnYE.mjs.map +1 -0
  6. package/dist/client-Bd8B1mYm.mjs +835 -0
  7. package/dist/client-Bd8B1mYm.mjs.map +1 -0
  8. package/dist/client.d.mts +2 -0
  9. package/dist/client.mjs +2 -0
  10. package/dist/core.d.mts +8 -0
  11. package/dist/core.mjs +9 -0
  12. package/dist/error-B49rbDzT.d.mts +7 -0
  13. package/dist/error-CwBxle6N.mjs +13 -0
  14. package/dist/error-CwBxle6N.mjs.map +1 -0
  15. package/dist/error-DZEs5OuZ.mjs +24 -0
  16. package/dist/error-DZEs5OuZ.mjs.map +1 -0
  17. package/dist/error-WEFS-R-4.d.mts +20 -0
  18. package/dist/event-B5D2jxt_.d.mts +108 -0
  19. package/dist/event-CzoQEFMF.mjs +188 -0
  20. package/dist/event-CzoQEFMF.mjs.map +1 -0
  21. package/dist/filter-BxwjEkZp.mjs +113 -0
  22. package/dist/filter-BxwjEkZp.mjs.map +1 -0
  23. package/dist/filter-DUEKP3q1.d.mts +40 -0
  24. package/dist/gossip-3xkRRCm_.mjs +220 -0
  25. package/dist/gossip-3xkRRCm_.mjs.map +1 -0
  26. package/dist/gossip-C5RVB1UB.d.mts +71 -0
  27. package/dist/gossip.d.mts +3 -0
  28. package/dist/gossip.mjs +2 -0
  29. package/dist/http-6vevbBlu.d.mts +22 -0
  30. package/dist/http-D2rhokZo.mjs +24 -0
  31. package/dist/http-D2rhokZo.mjs.map +1 -0
  32. package/dist/index-BYMnhRIC.d.mts +161 -0
  33. package/dist/index-C0C4e-Gn.d.mts +55 -0
  34. package/dist/index-CaxrMD1A.d.mts +1 -0
  35. package/dist/index-DaRyjo7O.d.mts +77 -0
  36. package/dist/index-DgOugrpF.d.mts +275 -0
  37. package/dist/index-EwL6fHLo.d.mts +267 -0
  38. package/dist/index-k2YhCgsh.d.mts +57 -0
  39. package/dist/index.d.mts +22 -0
  40. package/dist/index.mjs +23 -0
  41. package/dist/key-BiGxGLhg.d.mts +37 -0
  42. package/dist/key-Dn-aX4x4.mjs +130 -0
  43. package/dist/key-Dn-aX4x4.mjs.map +1 -0
  44. package/dist/kind-BLl4cLOc.mjs +59 -0
  45. package/dist/kind-BLl4cLOc.mjs.map +1 -0
  46. package/dist/loaders-BNQszDAn.mjs +767 -0
  47. package/dist/loaders-BNQszDAn.mjs.map +1 -0
  48. package/dist/loaders.d.mts +3 -0
  49. package/dist/loaders.mjs +2 -0
  50. package/dist/memory-BCmhZPek.mjs +461 -0
  51. package/dist/memory-BCmhZPek.mjs.map +1 -0
  52. package/dist/message-C_Uj-Y-c.mjs +179 -0
  53. package/dist/message-C_Uj-Y-c.mjs.map +1 -0
  54. package/dist/message-DJC-Jz6T.d.mts +32 -0
  55. package/dist/nip17-BWq-Kfq5.d.mts +38 -0
  56. package/dist/nip19-D7905oIv.d.mts +63 -0
  57. package/dist/nip46-C_DoxiS_.d.mts +51 -0
  58. package/dist/nip59-DljW_sYh.d.mts +76 -0
  59. package/dist/nip65-Di5hZRuV.d.mts +21 -0
  60. package/dist/nip77-ByxlgoQO.d.mts +55 -0
  61. package/dist/nips/blossom.d.mts +93 -0
  62. package/dist/nips/blossom.mjs +325 -0
  63. package/dist/nips/blossom.mjs.map +1 -0
  64. package/dist/nips/nip04.d.mts +8 -0
  65. package/dist/nips/nip04.mjs +41 -0
  66. package/dist/nips/nip04.mjs.map +1 -0
  67. package/dist/nips/nip05.d.mts +89 -0
  68. package/dist/nips/nip05.mjs +147 -0
  69. package/dist/nips/nip05.mjs.map +1 -0
  70. package/dist/nips/nip10.d.mts +43 -0
  71. package/dist/nips/nip10.mjs +194 -0
  72. package/dist/nips/nip10.mjs.map +1 -0
  73. package/dist/nips/nip11.d.mts +43 -0
  74. package/dist/nips/nip11.mjs +99 -0
  75. package/dist/nips/nip11.mjs.map +1 -0
  76. package/dist/nips/nip13.d.mts +23 -0
  77. package/dist/nips/nip13.mjs +88 -0
  78. package/dist/nips/nip13.mjs.map +1 -0
  79. package/dist/nips/nip17.d.mts +2 -0
  80. package/dist/nips/nip17.mjs +138 -0
  81. package/dist/nips/nip17.mjs.map +1 -0
  82. package/dist/nips/nip19.d.mts +2 -0
  83. package/dist/nips/nip19.mjs +174 -0
  84. package/dist/nips/nip19.mjs.map +1 -0
  85. package/dist/nips/nip21.d.mts +17 -0
  86. package/dist/nips/nip21.mjs +29 -0
  87. package/dist/nips/nip21.mjs.map +1 -0
  88. package/dist/nips/nip27.d.mts +42 -0
  89. package/dist/nips/nip27.mjs +175 -0
  90. package/dist/nips/nip27.mjs.map +1 -0
  91. package/dist/nips/nip42.d.mts +12 -0
  92. package/dist/nips/nip42.mjs +22 -0
  93. package/dist/nips/nip42.mjs.map +1 -0
  94. package/dist/nips/nip44.d.mts +12 -0
  95. package/dist/nips/nip44.mjs +125 -0
  96. package/dist/nips/nip44.mjs.map +1 -0
  97. package/dist/nips/nip46.d.mts +2 -0
  98. package/dist/nips/nip46.mjs +132 -0
  99. package/dist/nips/nip46.mjs.map +1 -0
  100. package/dist/nips/nip49.d.mts +12 -0
  101. package/dist/nips/nip49.mjs +80 -0
  102. package/dist/nips/nip49.mjs.map +1 -0
  103. package/dist/nips/nip51.d.mts +88 -0
  104. package/dist/nips/nip51.mjs +246 -0
  105. package/dist/nips/nip51.mjs.map +1 -0
  106. package/dist/nips/nip57.d.mts +43 -0
  107. package/dist/nips/nip57.mjs +230 -0
  108. package/dist/nips/nip57.mjs.map +1 -0
  109. package/dist/nips/nip59.d.mts +2 -0
  110. package/dist/nips/nip59.mjs +171 -0
  111. package/dist/nips/nip59.mjs.map +1 -0
  112. package/dist/nips/nip65.d.mts +2 -0
  113. package/dist/nips/nip65.mjs +76 -0
  114. package/dist/nips/nip65.mjs.map +1 -0
  115. package/dist/nips/nip77.d.mts +2 -0
  116. package/dist/nips/nip77.mjs +421 -0
  117. package/dist/nips/nip77.mjs.map +1 -0
  118. package/dist/nips/nip96.d.mts +31 -0
  119. package/dist/nips/nip96.mjs +95 -0
  120. package/dist/nips/nip96.mjs.map +1 -0
  121. package/dist/nips/nip98.d.mts +28 -0
  122. package/dist/nips/nip98.mjs +91 -0
  123. package/dist/nips/nip98.mjs.map +1 -0
  124. package/dist/nostr_crypto_wasm_bg-hi5aXi5P.mjs +4 -0
  125. package/dist/nostr_crypto_wasm_bg.wasm +0 -0
  126. package/dist/pool-C_j1OmBB.mjs +1881 -0
  127. package/dist/pool-C_j1OmBB.mjs.map +1 -0
  128. package/dist/pool-DhRi7l-b.d.mts +297 -0
  129. package/dist/relay.d.mts +4 -0
  130. package/dist/relay.mjs +2 -0
  131. package/dist/signer-CZ6K6FUc.mjs +473 -0
  132. package/dist/signer-CZ6K6FUc.mjs.map +1 -0
  133. package/dist/signer.d.mts +3 -0
  134. package/dist/signer.mjs +2 -0
  135. package/dist/storage-DOy0iLRE.mjs +870 -0
  136. package/dist/storage-DOy0iLRE.mjs.map +1 -0
  137. package/dist/storage.d.mts +4 -0
  138. package/dist/storage.mjs +4 -0
  139. package/dist/types-BzyADqnw.d.mts +19 -0
  140. package/dist/types-Dn24TPFs.d.mts +43 -0
  141. package/dist/util-6tfLQ9JF.mjs +73 -0
  142. package/dist/util-6tfLQ9JF.mjs.map +1 -0
  143. package/dist/wasm.d.mts +19 -0
  144. package/dist/wasm.mjs +237 -0
  145. package/dist/wasm.mjs.map +1 -0
  146. package/package.json +204 -0
@@ -0,0 +1,113 @@
1
+ import { o as isReplaceableKind, r as isAddressableKind } from "./kind-BLl4cLOc.mjs";
2
+ //#region src/core/filter.ts
3
+ /** Local NIP-01 match. `search` is ignored; relays interpret NIP-50. */
4
+ function matchFilter(filter, event) {
5
+ if (filter.ids && !filter.ids.some((id) => id.toLowerCase() === event.id.toLowerCase())) return false;
6
+ if (filter.kinds && !filter.kinds.includes(event.kind)) return false;
7
+ if (filter.authors && !filter.authors.some((pk) => pk.toLowerCase() === event.pubkey.toLowerCase())) return false;
8
+ for (const key of Object.keys(filter)) {
9
+ if (key[0] !== "#") continue;
10
+ const tagName = key.slice(1);
11
+ const values = filter[`#${tagName}`];
12
+ if (!values) continue;
13
+ const hexTag = tagName === "e" || tagName === "p";
14
+ if (!event.tags.some((tag) => {
15
+ if (tag[0] !== tagName || tag[1] === void 0) return false;
16
+ if (hexTag) return values.some((v) => v.toLowerCase() === tag[1].toLowerCase());
17
+ return values.includes(tag[1]);
18
+ })) return false;
19
+ }
20
+ if (filter.since !== void 0 && event.created_at < filter.since) return false;
21
+ if (filter.until !== void 0 && event.created_at > filter.until) return false;
22
+ return true;
23
+ }
24
+ function matchFilters(filters, event) {
25
+ for (const filter of filters) if (matchFilter(filter, event)) return true;
26
+ return false;
27
+ }
28
+ /** Merge filters by unioning list fields; returns a new plain object. */
29
+ function mergeFilters(...filters) {
30
+ const result = {};
31
+ for (const filter of filters) {
32
+ for (const [property, values] of Object.entries(filter)) if (property === "kinds" || property === "ids" || property === "authors" || property[0] === "#") {
33
+ const list = result[property] ?? [];
34
+ for (const value of values) if (!list.includes(value)) list.push(value);
35
+ result[property] = list;
36
+ }
37
+ if (filter.limit !== void 0) {
38
+ const prev = result.limit;
39
+ if (prev === void 0 || filter.limit > prev) result.limit = filter.limit;
40
+ }
41
+ if (filter.until !== void 0) {
42
+ const prev = result.until;
43
+ if (prev === void 0 || filter.until > prev) result.until = filter.until;
44
+ }
45
+ if (filter.since !== void 0) {
46
+ const prev = result.since;
47
+ if (prev === void 0 || filter.since < prev) result.since = filter.since;
48
+ }
49
+ if (filter.search !== void 0) result.search = filter.search;
50
+ }
51
+ return result;
52
+ }
53
+ /**
54
+ * Intrinsic upper bound implied by the filter alone.
55
+ * Returns a positive integer, or `Infinity` when unbounded.
56
+ */
57
+ function getFilterLimit(filter) {
58
+ if (filter.ids && filter.ids.length === 0) return 0;
59
+ if (filter.kinds && filter.kinds.length === 0) return 0;
60
+ if (filter.authors && filter.authors.length === 0) return 0;
61
+ let limit = Infinity;
62
+ if (filter.ids) limit = Math.min(limit, filter.ids.length);
63
+ if (filter.limit !== void 0) limit = Math.min(limit, filter.limit);
64
+ if (filter.kinds && filter.authors) {
65
+ if (filter.kinds.every((k) => isReplaceableKind(k) || isAddressableKind(k))) {
66
+ const dTags = filter["#d"];
67
+ const perAuthor = dTags && dTags.length > 0 ? dTags.length : 1;
68
+ limit = Math.min(limit, filter.authors.length * filter.kinds.length * perAuthor);
69
+ }
70
+ }
71
+ return limit;
72
+ }
73
+ /** Shallow-clone a filter (for per-relay narrowing). */
74
+ function cloneFilter(filter) {
75
+ return { ...filter };
76
+ }
77
+ const HEX_LIST_KEYS = /* @__PURE__ */ new Set([
78
+ "ids",
79
+ "authors",
80
+ "#e",
81
+ "#p"
82
+ ]);
83
+ /** Lowercase hex lists and sort every array. Omits undefined so `[]` stays distinct from missing. */
84
+ function canonicalizeFilter(filter) {
85
+ const raw = filter;
86
+ const out = {};
87
+ for (const key of Object.keys(raw).sort()) {
88
+ const value = raw[key];
89
+ if (value === void 0) continue;
90
+ if (Array.isArray(value)) {
91
+ if (HEX_LIST_KEYS.has(key)) out[key] = value.map((v) => String(v).toLowerCase()).sort();
92
+ else if (key === "kinds") out[key] = value.map((v) => Number(v)).sort((a, b) => a - b);
93
+ else out[key] = value.map((v) => String(v)).sort();
94
+ } else out[key] = value;
95
+ }
96
+ return out;
97
+ }
98
+ function canonicalizeFilters(filters) {
99
+ return filters.map(canonicalizeFilter);
100
+ }
101
+ /**
102
+ * Canonical identity for live REQ coalescing.
103
+ * Relays treat a missing list key as unconstrained and `[]` as match-nothing.
104
+ */
105
+ function filterFingerprint(filters) {
106
+ const parts = filters.map((filter) => JSON.stringify(canonicalizeFilter(filter)));
107
+ parts.sort();
108
+ return `[${parts.join(",")}]`;
109
+ }
110
+ //#endregion
111
+ export { getFilterLimit as a, mergeFilters as c, filterFingerprint as i, canonicalizeFilters as n, matchFilter as o, cloneFilter as r, matchFilters as s, canonicalizeFilter as t };
112
+
113
+ //# sourceMappingURL=filter-BxwjEkZp.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter-BxwjEkZp.mjs","names":[],"sources":["../src/core/filter.ts"],"sourcesContent":["import type { Event } from \"./event.ts\";\nimport { isAddressableKind, isReplaceableKind } from \"./kind.ts\";\n\n/** NIP-01 filter object (immutable-friendly; callers may still pass plain objects). */\nexport type Filter = {\n readonly ids?: readonly string[];\n readonly kinds?: readonly number[];\n readonly authors?: readonly string[];\n readonly since?: number;\n readonly until?: number;\n readonly limit?: number;\n /**\n * NIP-50 full-text search. Relays interpret this.\n * Local `matchFilter` / `query` ignore it.\n */\n readonly search?: string;\n readonly [key: `#${string}`]: readonly string[] | undefined;\n};\n\n/** Local NIP-01 match. `search` is ignored; relays interpret NIP-50. */\nexport function matchFilter(filter: Filter, event: Event): boolean {\n if (filter.ids && !filter.ids.some((id) => id.toLowerCase() === event.id.toLowerCase())) {\n return false;\n }\n if (filter.kinds && !filter.kinds.includes(event.kind)) return false;\n if (\n filter.authors &&\n !filter.authors.some((pk) => pk.toLowerCase() === event.pubkey.toLowerCase())\n ) {\n return false;\n }\n\n for (const key of Object.keys(filter)) {\n if (key[0] !== \"#\") continue;\n const tagName = key.slice(1);\n const values = filter[`#${tagName}`];\n if (!values) continue;\n const hexTag = tagName === \"e\" || tagName === \"p\";\n const hit = event.tags.some((tag) => {\n if (tag[0] !== tagName || tag[1] === undefined) return false;\n if (hexTag) {\n return values.some((v) => v.toLowerCase() === tag[1]!.toLowerCase());\n }\n return values.includes(tag[1]);\n });\n if (!hit) return false;\n }\n\n if (filter.since !== undefined && event.created_at < filter.since) return false;\n if (filter.until !== undefined && event.created_at > filter.until) return false;\n\n return true;\n}\n\nexport function matchFilters(filters: readonly Filter[], event: Event): boolean {\n for (const filter of filters) {\n if (matchFilter(filter, event)) return true;\n }\n return false;\n}\n\n/** Merge filters by unioning list fields; returns a new plain object. */\nexport function mergeFilters(...filters: Filter[]): Filter {\n const result: Record<string, unknown> = {};\n for (const filter of filters) {\n for (const [property, values] of Object.entries(filter)) {\n if (\n property === \"kinds\" ||\n property === \"ids\" ||\n property === \"authors\" ||\n property[0] === \"#\"\n ) {\n const list = (result[property] as (string | number)[] | undefined) ?? [];\n for (const value of values as readonly (string | number)[]) {\n if (!list.includes(value)) list.push(value);\n }\n result[property] = list;\n }\n }\n if (filter.limit !== undefined) {\n const prev = result.limit as number | undefined;\n if (prev === undefined || filter.limit > prev) result.limit = filter.limit;\n }\n if (filter.until !== undefined) {\n const prev = result.until as number | undefined;\n if (prev === undefined || filter.until > prev) result.until = filter.until;\n }\n if (filter.since !== undefined) {\n const prev = result.since as number | undefined;\n if (prev === undefined || filter.since < prev) result.since = filter.since;\n }\n if (filter.search !== undefined) {\n result.search = filter.search;\n }\n }\n return result as Filter;\n}\n\n/**\n * Intrinsic upper bound implied by the filter alone.\n * Returns a positive integer, or `Infinity` when unbounded.\n */\nexport function getFilterLimit(filter: Filter): number {\n if (filter.ids && filter.ids.length === 0) return 0;\n if (filter.kinds && filter.kinds.length === 0) return 0;\n if (filter.authors && filter.authors.length === 0) return 0;\n\n let limit = Infinity;\n if (filter.ids) limit = Math.min(limit, filter.ids.length);\n if (filter.limit !== undefined) limit = Math.min(limit, filter.limit);\n\n if (filter.kinds && filter.authors) {\n const allReplaceable = filter.kinds.every((k) => isReplaceableKind(k) || isAddressableKind(k));\n if (allReplaceable) {\n const dTags = filter[\"#d\"];\n const perAuthor = dTags && dTags.length > 0 ? dTags.length : 1;\n limit = Math.min(limit, filter.authors.length * filter.kinds.length * perAuthor);\n }\n }\n\n return limit;\n}\n\n/** Shallow-clone a filter (for per-relay narrowing). */\nexport function cloneFilter(filter: Filter): Filter {\n return { ...filter };\n}\n\nconst HEX_LIST_KEYS = new Set([\"ids\", \"authors\", \"#e\", \"#p\"]);\n\n/** Lowercase hex lists and sort every array. Omits undefined so `[]` stays distinct from missing. */\nexport function canonicalizeFilter(filter: Filter): Filter {\n const raw = filter as Record<string, unknown>;\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(raw).sort()) {\n const value = raw[key];\n // omit undefined so a missing key is not `[]` / null\n if (value === undefined) continue;\n if (Array.isArray(value)) {\n if (HEX_LIST_KEYS.has(key)) {\n out[key] = value.map((v) => String(v).toLowerCase()).sort();\n } else if (key === \"kinds\") {\n out[key] = value.map((v) => Number(v)).sort((a, b) => a - b);\n } else {\n out[key] = value.map((v) => String(v)).sort();\n }\n } else {\n out[key] = value;\n }\n }\n return out as Filter;\n}\n\nexport function canonicalizeFilters(filters: readonly Filter[]): Filter[] {\n return filters.map(canonicalizeFilter);\n}\n\n/**\n * Canonical identity for live REQ coalescing.\n * Relays treat a missing list key as unconstrained and `[]` as match-nothing.\n */\nexport function filterFingerprint(filters: readonly Filter[]): string {\n const parts = filters.map((filter) => JSON.stringify(canonicalizeFilter(filter)));\n parts.sort();\n return `[${parts.join(\",\")}]`;\n}\n"],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import { t as Event } from "./event-B5D2jxt_.mjs";
2
+ //#region src/core/filter.d.ts
3
+ /** NIP-01 filter object (immutable-friendly; callers may still pass plain objects). */
4
+ type Filter = {
5
+ readonly ids?: readonly string[];
6
+ readonly kinds?: readonly number[];
7
+ readonly authors?: readonly string[];
8
+ readonly since?: number;
9
+ readonly until?: number;
10
+ readonly limit?: number;
11
+ /**
12
+ * NIP-50 full-text search. Relays interpret this.
13
+ * Local `matchFilter` / `query` ignore it.
14
+ */
15
+ readonly search?: string;
16
+ readonly [key: `#${string}`]: readonly string[] | undefined;
17
+ };
18
+ /** Local NIP-01 match. `search` is ignored; relays interpret NIP-50. */
19
+ declare function matchFilter(filter: Filter, event: Event): boolean;
20
+ declare function matchFilters(filters: readonly Filter[], event: Event): boolean;
21
+ /** Merge filters by unioning list fields; returns a new plain object. */
22
+ declare function mergeFilters(...filters: Filter[]): Filter;
23
+ /**
24
+ * Intrinsic upper bound implied by the filter alone.
25
+ * Returns a positive integer, or `Infinity` when unbounded.
26
+ */
27
+ declare function getFilterLimit(filter: Filter): number;
28
+ /** Shallow-clone a filter (for per-relay narrowing). */
29
+ declare function cloneFilter(filter: Filter): Filter;
30
+ /** Lowercase hex lists and sort every array. Omits undefined so `[]` stays distinct from missing. */
31
+ declare function canonicalizeFilter(filter: Filter): Filter;
32
+ declare function canonicalizeFilters(filters: readonly Filter[]): Filter[];
33
+ /**
34
+ * Canonical identity for live REQ coalescing.
35
+ * Relays treat a missing list key as unconstrained and `[]` as match-nothing.
36
+ */
37
+ declare function filterFingerprint(filters: readonly Filter[]): string;
38
+ //#endregion
39
+ export { filterFingerprint as a, matchFilters as c, cloneFilter as i, mergeFilters as l, canonicalizeFilter as n, getFilterLimit as o, canonicalizeFilters as r, matchFilter as s, Filter as t };
40
+ //# sourceMappingURL=filter-DUEKP3q1.d.mts.map
@@ -0,0 +1,220 @@
1
+ import { s as normalizeURL } from "./util-6tfLQ9JF.mjs";
2
+ import { t as Kind } from "./kind-BLl4cLOc.mjs";
3
+ import { i as isReplaceableWinner } from "./event-CzoQEFMF.mjs";
4
+ import { parseRelayList } from "./nips/nip65.mjs";
5
+ import { parseDmRelayList } from "./nips/nip17.mjs";
6
+ //#region src/gossip/gossip.ts
7
+ function emptyRoutes() {
8
+ return {
9
+ write: [],
10
+ read: [],
11
+ dm: [],
12
+ updatedAt: 0,
13
+ dmUpdatedAt: 0
14
+ };
15
+ }
16
+ /**
17
+ * Routing table for NIP-65 (10002) and NIP-17 DM relays (10050).
18
+ * Ingest replaceable list events, then route filters into per-relay REQs.
19
+ */
20
+ var Gossip = class {
21
+ #routes = /* @__PURE__ */ new Map();
22
+ #maxRelays;
23
+ constructor(opts = {}) {
24
+ this.#maxRelays = opts.maxRelaysPerPubkey ?? 4;
25
+ }
26
+ /**
27
+ * Ingest a routing list event.
28
+ * - kind:10002 → write/read (NIP-65)
29
+ * - kind:10050 → dm (NIP-17)
30
+ * Returns true if routes for that list type were updated.
31
+ */
32
+ ingest(event) {
33
+ if (event.kind === Kind.RelayList) {
34
+ let items;
35
+ try {
36
+ items = parseRelayList(event);
37
+ } catch {
38
+ return false;
39
+ }
40
+ return this.setRoutes(event.pubkey, items, event.created_at, event.id);
41
+ }
42
+ if (event.kind === Kind.DirectMessageRelaysList) {
43
+ let relays;
44
+ try {
45
+ relays = parseDmRelayList(event);
46
+ } catch {
47
+ return false;
48
+ }
49
+ return this.setDmRoutes(event.pubkey, relays, event.created_at, event.id);
50
+ }
51
+ return false;
52
+ }
53
+ setRoutes(pubkey, items, updatedAt = Math.floor(Date.now() / 1e3), eventId) {
54
+ const pk = pubkey.toLowerCase();
55
+ const prev = this.#routes.get(pk) ?? emptyRoutes();
56
+ if (prev.updatedAt > updatedAt) return false;
57
+ if (eventId && prev.relayListId && prev.updatedAt === updatedAt && !isReplaceableWinner({
58
+ created_at: updatedAt,
59
+ id: eventId
60
+ }, {
61
+ created_at: prev.updatedAt,
62
+ id: prev.relayListId
63
+ })) return false;
64
+ const write = [];
65
+ const read = [];
66
+ for (const item of items) {
67
+ let url;
68
+ try {
69
+ url = normalizeURL(item.url);
70
+ } catch {
71
+ continue;
72
+ }
73
+ if (item.write && !write.includes(url)) write.push(url);
74
+ if (item.read && !read.includes(url)) read.push(url);
75
+ }
76
+ this.#routes.set(pk, {
77
+ write: write.slice(0, this.#maxRelays),
78
+ read: read.slice(0, this.#maxRelays),
79
+ dm: prev.dm,
80
+ updatedAt,
81
+ dmUpdatedAt: prev.dmUpdatedAt,
82
+ relayListId: eventId ?? prev.relayListId,
83
+ dmListId: prev.dmListId
84
+ });
85
+ return true;
86
+ }
87
+ setDmRoutes(pubkey, relays, updatedAt = Math.floor(Date.now() / 1e3), eventId) {
88
+ const pk = pubkey.toLowerCase();
89
+ const prev = this.#routes.get(pk) ?? emptyRoutes();
90
+ if (prev.dmUpdatedAt > updatedAt) return false;
91
+ if (eventId && prev.dmListId && prev.dmUpdatedAt === updatedAt && !isReplaceableWinner({
92
+ created_at: updatedAt,
93
+ id: eventId
94
+ }, {
95
+ created_at: prev.dmUpdatedAt,
96
+ id: prev.dmListId
97
+ })) return false;
98
+ const dm = [];
99
+ for (const raw of relays) {
100
+ let url;
101
+ try {
102
+ url = normalizeURL(raw);
103
+ } catch {
104
+ continue;
105
+ }
106
+ if (!dm.includes(url)) dm.push(url);
107
+ }
108
+ this.#routes.set(pk, {
109
+ write: prev.write,
110
+ read: prev.read,
111
+ dm: dm.slice(0, this.#maxRelays),
112
+ updatedAt: prev.updatedAt,
113
+ dmUpdatedAt: updatedAt,
114
+ relayListId: prev.relayListId,
115
+ dmListId: eventId ?? prev.dmListId
116
+ });
117
+ return true;
118
+ }
119
+ getRoutes(pubkey) {
120
+ return this.#routes.get(pubkey.toLowerCase());
121
+ }
122
+ outboxRelays(pubkey) {
123
+ return this.#routes.get(pubkey.toLowerCase())?.write ?? [];
124
+ }
125
+ inboxRelays(pubkey) {
126
+ return this.#routes.get(pubkey.toLowerCase())?.read ?? [];
127
+ }
128
+ /** NIP-17 kind:10050 delivery relays for gift-wraps. */
129
+ dmRelays(pubkey) {
130
+ return this.#routes.get(pubkey.toLowerCase())?.dm ?? [];
131
+ }
132
+ clear(pubkey) {
133
+ if (pubkey) this.#routes.delete(pubkey.toLowerCase());
134
+ else this.#routes.clear();
135
+ }
136
+ get size() {
137
+ return this.#routes.size;
138
+ }
139
+ /**
140
+ * Route a user-facing filter into per-relay sub-filters plus leftover.
141
+ * - authors → outbox relays, filter narrowed per relay's authors
142
+ * - #p only → inbox relays of those pubkeys
143
+ * - neither / all unrouted → remainder is the original filter
144
+ * - mixed → remainder is the narrowed leftover; all routed → no remainder
145
+ */
146
+ route(filter) {
147
+ const authors = filter.authors?.map((a) => a.toLowerCase());
148
+ const pTags = filter["#p"]?.map((p) => p.toLowerCase());
149
+ if ((!authors || authors.length === 0) && (!pTags || pTags.length === 0)) return {
150
+ perRelay: /* @__PURE__ */ new Map(),
151
+ remainder: filter
152
+ };
153
+ if (authors && authors.length > 0 && (!pTags || pTags.length === 0)) return this.#breakAuthors(filter, authors, "write");
154
+ if (pTags && pTags.length > 0 && (!authors || authors.length === 0)) return this.#breakAuthors({
155
+ ...filter,
156
+ authors: void 0
157
+ }, pTags, "read", (base, pubkeys) => ({
158
+ ...base,
159
+ "#p": pubkeys
160
+ }));
161
+ const unionKeys = /* @__PURE__ */ new Set([...authors ?? [], ...pTags ?? []]);
162
+ const relays = /* @__PURE__ */ new Set();
163
+ let anyUnrouted = false;
164
+ for (const pk of unionKeys) {
165
+ const r = this.#routes.get(pk);
166
+ const urls = r ? [...r.write, ...r.read] : [];
167
+ if (urls.length === 0) {
168
+ anyUnrouted = true;
169
+ continue;
170
+ }
171
+ for (const u of urls) relays.add(u);
172
+ }
173
+ if (relays.size === 0) return {
174
+ perRelay: /* @__PURE__ */ new Map(),
175
+ remainder: filter
176
+ };
177
+ const map = /* @__PURE__ */ new Map();
178
+ for (const url of relays) map.set(url, { ...filter });
179
+ return anyUnrouted ? {
180
+ perRelay: map,
181
+ remainder: filter
182
+ } : { perRelay: map };
183
+ }
184
+ #breakAuthors(filter, pubkeys, direction, narrow = (base, pks) => ({
185
+ ...base,
186
+ authors: pks
187
+ })) {
188
+ const perRelay = /* @__PURE__ */ new Map();
189
+ const unrouted = [];
190
+ let anyRoute = false;
191
+ for (const pk of pubkeys) {
192
+ const routes = this.#routes.get(pk);
193
+ const urls = direction === "write" ? routes?.write : routes?.read;
194
+ if (!urls || urls.length === 0) {
195
+ unrouted.push(pk);
196
+ continue;
197
+ }
198
+ anyRoute = true;
199
+ for (const url of urls) {
200
+ const list = perRelay.get(url) ?? [];
201
+ list.push(pk);
202
+ perRelay.set(url, list);
203
+ }
204
+ }
205
+ if (!anyRoute) return {
206
+ perRelay: /* @__PURE__ */ new Map(),
207
+ remainder: filter
208
+ };
209
+ const map = /* @__PURE__ */ new Map();
210
+ for (const [url, pks] of perRelay) map.set(url, narrow(filter, pks));
211
+ return unrouted.length > 0 ? {
212
+ perRelay: map,
213
+ remainder: narrow(filter, unrouted)
214
+ } : { perRelay: map };
215
+ }
216
+ };
217
+ //#endregion
218
+ export { Gossip as t };
219
+
220
+ //# sourceMappingURL=gossip-3xkRRCm_.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gossip-3xkRRCm_.mjs","names":["#routes","#maxRelays","#breakAuthors"],"sources":["../src/gossip/gossip.ts"],"sourcesContent":["import type { Event } from \"../core/event.ts\";\nimport { isReplaceableWinner } from \"../core/event.ts\";\nimport type { Filter } from \"../core/filter.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport { parseDmRelayList } from \"../nips/nip17.ts\";\nimport { parseRelayList, type RelayListItem } from \"../nips/nip65.ts\";\nimport { normalizeURL } from \"../core/util.ts\";\n\nexport type PubkeyRoutes = {\n /** Relays the user writes to (outbox). NIP-65. */\n write: string[];\n /** Relays the user reads from (inbox). NIP-65. */\n read: string[];\n /** Relays for NIP-17 gift-wrap delivery. Kind 10050. */\n dm: string[];\n /** `created_at` of the last accepted kind:10002 list. */\n updatedAt: number;\n /** `created_at` of the last accepted kind:10050 list. */\n dmUpdatedAt: number;\n /** Event id of the last accepted kind:10002 (NIP-01 equal-timestamp tie-break). */\n relayListId?: string;\n /** Event id of the last accepted kind:10050. */\n dmListId?: string;\n};\n\nexport type RoutedFilter = {\n /** url → already-narrowed filter. Empty when nothing routed. */\n perRelay: Map<string, Filter>;\n /**\n * Unrouted work for the caller:\n * - no authors and no #p: original filter (today's \"generic\")\n * - authors/#p all unrouted: original filter (today's \"orphan\")\n * - mixed: narrowed leftover (today's fallback)\n * - all routed: undefined\n */\n remainder?: Filter;\n};\n\nexport type GossipOptions = {\n /** Max relays to keep per direction when ranking. Default 4. */\n maxRelaysPerPubkey?: number;\n};\n\nfunction emptyRoutes(): PubkeyRoutes {\n return {\n write: [],\n read: [],\n dm: [],\n updatedAt: 0,\n dmUpdatedAt: 0,\n };\n}\n\n/**\n * Routing table for NIP-65 (10002) and NIP-17 DM relays (10050).\n * Ingest replaceable list events, then route filters into per-relay REQs.\n */\nexport class Gossip {\n readonly #routes = new Map<string, PubkeyRoutes>();\n readonly #maxRelays: number;\n\n constructor(opts: GossipOptions = {}) {\n this.#maxRelays = opts.maxRelaysPerPubkey ?? 4;\n }\n\n /**\n * Ingest a routing list event.\n * - kind:10002 → write/read (NIP-65)\n * - kind:10050 → dm (NIP-17)\n * Returns true if routes for that list type were updated.\n */\n ingest(event: Event): boolean {\n if (event.kind === Kind.RelayList) {\n let items: RelayListItem[];\n try {\n items = parseRelayList(event);\n } catch {\n return false;\n }\n return this.setRoutes(event.pubkey, items, event.created_at, event.id);\n }\n if (event.kind === Kind.DirectMessageRelaysList) {\n let relays: string[];\n try {\n relays = parseDmRelayList(event);\n } catch {\n return false;\n }\n return this.setDmRoutes(event.pubkey, relays, event.created_at, event.id);\n }\n return false;\n }\n\n setRoutes(\n pubkey: string,\n items: RelayListItem[],\n updatedAt = Math.floor(Date.now() / 1000),\n eventId?: string,\n ): boolean {\n const pk = pubkey.toLowerCase();\n const prev = this.#routes.get(pk) ?? emptyRoutes();\n if (prev.updatedAt > updatedAt) return false;\n if (\n eventId &&\n prev.relayListId &&\n prev.updatedAt === updatedAt &&\n !isReplaceableWinner(\n { created_at: updatedAt, id: eventId },\n { created_at: prev.updatedAt, id: prev.relayListId },\n )\n ) {\n return false;\n }\n\n const write: string[] = [];\n const read: string[] = [];\n for (const item of items) {\n let url: string;\n try {\n url = normalizeURL(item.url);\n } catch {\n continue;\n }\n if (item.write && !write.includes(url)) write.push(url);\n if (item.read && !read.includes(url)) read.push(url);\n }\n\n this.#routes.set(pk, {\n write: write.slice(0, this.#maxRelays),\n read: read.slice(0, this.#maxRelays),\n dm: prev.dm,\n updatedAt,\n dmUpdatedAt: prev.dmUpdatedAt,\n relayListId: eventId ?? prev.relayListId,\n dmListId: prev.dmListId,\n });\n return true;\n }\n\n setDmRoutes(\n pubkey: string,\n relays: readonly string[],\n updatedAt = Math.floor(Date.now() / 1000),\n eventId?: string,\n ): boolean {\n const pk = pubkey.toLowerCase();\n const prev = this.#routes.get(pk) ?? emptyRoutes();\n if (prev.dmUpdatedAt > updatedAt) return false;\n if (\n eventId &&\n prev.dmListId &&\n prev.dmUpdatedAt === updatedAt &&\n !isReplaceableWinner(\n { created_at: updatedAt, id: eventId },\n { created_at: prev.dmUpdatedAt, id: prev.dmListId },\n )\n ) {\n return false;\n }\n\n const dm: string[] = [];\n for (const raw of relays) {\n let url: string;\n try {\n url = normalizeURL(raw);\n } catch {\n continue;\n }\n if (!dm.includes(url)) dm.push(url);\n }\n\n this.#routes.set(pk, {\n write: prev.write,\n read: prev.read,\n dm: dm.slice(0, this.#maxRelays),\n updatedAt: prev.updatedAt,\n dmUpdatedAt: updatedAt,\n relayListId: prev.relayListId,\n dmListId: eventId ?? prev.dmListId,\n });\n return true;\n }\n\n getRoutes(pubkey: string): PubkeyRoutes | undefined {\n return this.#routes.get(pubkey.toLowerCase());\n }\n\n outboxRelays(pubkey: string): string[] {\n return this.#routes.get(pubkey.toLowerCase())?.write ?? [];\n }\n\n inboxRelays(pubkey: string): string[] {\n return this.#routes.get(pubkey.toLowerCase())?.read ?? [];\n }\n\n /** NIP-17 kind:10050 delivery relays for gift-wraps. */\n dmRelays(pubkey: string): string[] {\n return this.#routes.get(pubkey.toLowerCase())?.dm ?? [];\n }\n\n clear(pubkey?: string): void {\n if (pubkey) this.#routes.delete(pubkey.toLowerCase());\n else this.#routes.clear();\n }\n\n get size(): number {\n return this.#routes.size;\n }\n\n /**\n * Route a user-facing filter into per-relay sub-filters plus leftover.\n * - authors → outbox relays, filter narrowed per relay's authors\n * - #p only → inbox relays of those pubkeys\n * - neither / all unrouted → remainder is the original filter\n * - mixed → remainder is the narrowed leftover; all routed → no remainder\n */\n route(filter: Filter): RoutedFilter {\n const authors = filter.authors?.map((a) => a.toLowerCase());\n const pTags = filter[\"#p\"]?.map((p) => p.toLowerCase());\n\n if ((!authors || authors.length === 0) && (!pTags || pTags.length === 0)) {\n return { perRelay: new Map(), remainder: filter };\n }\n\n if (authors && authors.length > 0 && (!pTags || pTags.length === 0)) {\n return this.#breakAuthors(filter, authors, \"write\");\n }\n\n if (pTags && pTags.length > 0 && (!authors || authors.length === 0)) {\n return this.#breakAuthors(\n { ...filter, authors: undefined },\n pTags,\n \"read\",\n (base, pubkeys) => ({ ...base, \"#p\": pubkeys }),\n );\n }\n\n // both authors and #p: send full filter to union of routes\n const unionKeys = new Set([...(authors ?? []), ...(pTags ?? [])]);\n const relays = new Set<string>();\n let anyUnrouted = false;\n for (const pk of unionKeys) {\n const r = this.#routes.get(pk);\n const urls = r ? [...r.write, ...r.read] : [];\n if (urls.length === 0) {\n anyUnrouted = true;\n continue;\n }\n for (const u of urls) relays.add(u);\n }\n if (relays.size === 0) return { perRelay: new Map(), remainder: filter };\n const map = new Map<string, Filter>();\n for (const url of relays) map.set(url, { ...filter });\n return anyUnrouted ? { perRelay: map, remainder: filter } : { perRelay: map };\n }\n\n #breakAuthors(\n filter: Filter,\n pubkeys: string[],\n direction: \"read\" | \"write\",\n narrow: (base: Filter, pubkeys: string[]) => Filter = (base, pks) => ({\n ...base,\n authors: pks,\n }),\n ): RoutedFilter {\n const perRelay = new Map<string, string[]>();\n const unrouted: string[] = [];\n let anyRoute = false;\n\n for (const pk of pubkeys) {\n const routes = this.#routes.get(pk);\n const urls = direction === \"write\" ? routes?.write : routes?.read;\n if (!urls || urls.length === 0) {\n unrouted.push(pk);\n continue;\n }\n anyRoute = true;\n for (const url of urls) {\n const list = perRelay.get(url) ?? [];\n list.push(pk);\n perRelay.set(url, list);\n }\n }\n\n if (!anyRoute) return { perRelay: new Map(), remainder: filter };\n\n const map = new Map<string, Filter>();\n for (const [url, pks] of perRelay) {\n map.set(url, narrow(filter, pks));\n }\n return unrouted.length > 0\n ? { perRelay: map, remainder: narrow(filter, unrouted) }\n : { perRelay: map };\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ import { t as Event } from "./event-B5D2jxt_.mjs";
2
+ import { t as Filter } from "./filter-DUEKP3q1.mjs";
3
+ import { t as RelayListItem } from "./nip65-Di5hZRuV.mjs";
4
+ //#region src/gossip/gossip.d.ts
5
+ type PubkeyRoutes = {
6
+ /** Relays the user writes to (outbox). NIP-65. */
7
+ write: string[];
8
+ /** Relays the user reads from (inbox). NIP-65. */
9
+ read: string[];
10
+ /** Relays for NIP-17 gift-wrap delivery. Kind 10050. */
11
+ dm: string[];
12
+ /** `created_at` of the last accepted kind:10002 list. */
13
+ updatedAt: number;
14
+ /** `created_at` of the last accepted kind:10050 list. */
15
+ dmUpdatedAt: number;
16
+ /** Event id of the last accepted kind:10002 (NIP-01 equal-timestamp tie-break). */
17
+ relayListId?: string;
18
+ /** Event id of the last accepted kind:10050. */
19
+ dmListId?: string;
20
+ };
21
+ type RoutedFilter = {
22
+ /** url → already-narrowed filter. Empty when nothing routed. */
23
+ perRelay: Map<string, Filter>;
24
+ /**
25
+ * Unrouted work for the caller:
26
+ * - no authors and no #p: original filter (today's "generic")
27
+ * - authors/#p all unrouted: original filter (today's "orphan")
28
+ * - mixed: narrowed leftover (today's fallback)
29
+ * - all routed: undefined
30
+ */
31
+ remainder?: Filter;
32
+ };
33
+ type GossipOptions = {
34
+ /** Max relays to keep per direction when ranking. Default 4. */
35
+ maxRelaysPerPubkey?: number;
36
+ };
37
+ /**
38
+ * Routing table for NIP-65 (10002) and NIP-17 DM relays (10050).
39
+ * Ingest replaceable list events, then route filters into per-relay REQs.
40
+ */
41
+ declare class Gossip {
42
+ #private;
43
+ constructor(opts?: GossipOptions);
44
+ /**
45
+ * Ingest a routing list event.
46
+ * - kind:10002 → write/read (NIP-65)
47
+ * - kind:10050 → dm (NIP-17)
48
+ * Returns true if routes for that list type were updated.
49
+ */
50
+ ingest(event: Event): boolean;
51
+ setRoutes(pubkey: string, items: RelayListItem[], updatedAt?: number, eventId?: string): boolean;
52
+ setDmRoutes(pubkey: string, relays: readonly string[], updatedAt?: number, eventId?: string): boolean;
53
+ getRoutes(pubkey: string): PubkeyRoutes | undefined;
54
+ outboxRelays(pubkey: string): string[];
55
+ inboxRelays(pubkey: string): string[];
56
+ /** NIP-17 kind:10050 delivery relays for gift-wraps. */
57
+ dmRelays(pubkey: string): string[];
58
+ clear(pubkey?: string): void;
59
+ get size(): number;
60
+ /**
61
+ * Route a user-facing filter into per-relay sub-filters plus leftover.
62
+ * - authors → outbox relays, filter narrowed per relay's authors
63
+ * - #p only → inbox relays of those pubkeys
64
+ * - neither / all unrouted → remainder is the original filter
65
+ * - mixed → remainder is the narrowed leftover; all routed → no remainder
66
+ */
67
+ route(filter: Filter): RoutedFilter;
68
+ }
69
+ //#endregion
70
+ export { RoutedFilter as i, GossipOptions as n, PubkeyRoutes as r, Gossip as t };
71
+ //# sourceMappingURL=gossip-C5RVB1UB.d.mts.map
@@ -0,0 +1,3 @@
1
+ import { i as RoutedFilter, n as GossipOptions, r as PubkeyRoutes, t as Gossip } from "./gossip-C5RVB1UB.mjs";
2
+ import "./index-CaxrMD1A.mjs";
3
+ export { Gossip, type GossipOptions, type PubkeyRoutes, type RoutedFilter };
@@ -0,0 +1,2 @@
1
+ import { t as Gossip } from "./gossip-3xkRRCm_.mjs";
2
+ export { Gossip };
@@ -0,0 +1,22 @@
1
+ //#region src/nips/http.d.ts
2
+ /**
3
+ * Shared nips HTTP primitive. Not a pack entry (core has zero network).
4
+ * Callers never pass `redirect`; sendManual always sets `"manual"`.
5
+ */
6
+ type ManualFetch = (url: string, init?: {
7
+ method?: string;
8
+ headers?: Record<string, string>;
9
+ body?: Blob | FormData | string | null;
10
+ signal?: AbortSignal;
11
+ }) => Promise<{
12
+ ok: boolean;
13
+ status: number;
14
+ headers: {
15
+ get(name: string): string | null;
16
+ };
17
+ json(): Promise<unknown>;
18
+ arrayBuffer(): Promise<ArrayBuffer>;
19
+ }>;
20
+ //#endregion
21
+ export { ManualFetch as t };
22
+ //# sourceMappingURL=http-6vevbBlu.d.mts.map
@@ -0,0 +1,24 @@
1
+ //#region src/nips/http.ts
2
+ function requireGlobalFetch(missing) {
3
+ if (typeof globalThis.fetch !== "function") throw missing();
4
+ return globalThis.fetch.bind(globalThis);
5
+ }
6
+ /** Always sets redirect:manual. Shared by fetchManual and headStatus. */
7
+ async function sendManual(fetchImpl, url, init) {
8
+ return await fetchImpl(url, {
9
+ ...init,
10
+ redirect: "manual"
11
+ });
12
+ }
13
+ async function fetchManual(fetchImpl, url, init, wrapNetwork) {
14
+ try {
15
+ return await sendManual(fetchImpl, url, init);
16
+ } catch (err) {
17
+ if (err instanceof Error && err.name === "AbortError") throw err;
18
+ throw wrapNetwork(err);
19
+ }
20
+ }
21
+ //#endregion
22
+ export { requireGlobalFetch as n, sendManual as r, fetchManual as t };
23
+
24
+ //# sourceMappingURL=http-D2rhokZo.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-D2rhokZo.mjs","names":[],"sources":["../src/nips/http.ts"],"sourcesContent":["/**\n * Shared nips HTTP primitive. Not a pack entry (core has zero network).\n * Callers never pass `redirect`; sendManual always sets `\"manual\"`.\n */\n\nexport type ManualFetch = (\n url: string,\n init?: {\n method?: string;\n headers?: Record<string, string>;\n body?: Blob | FormData | string | null;\n signal?: AbortSignal;\n },\n) => Promise<{\n ok: boolean;\n status: number;\n headers: { get(name: string): string | null };\n json(): Promise<unknown>;\n arrayBuffer(): Promise<ArrayBuffer>;\n}>;\n\ntype ManualInit = NonNullable<Parameters<ManualFetch>[1]>;\n\nexport function requireGlobalFetch(missing: () => Error): ManualFetch {\n if (typeof globalThis.fetch !== \"function\") throw missing();\n return globalThis.fetch.bind(globalThis) as ManualFetch;\n}\n\n/** Always sets redirect:manual. Shared by fetchManual and headStatus. */\nexport async function sendManual(\n fetchImpl: ManualFetch,\n url: string,\n init: ManualInit,\n): Promise<Awaited<ReturnType<ManualFetch>>> {\n return await fetchImpl(url, { ...init, redirect: \"manual\" } as Parameters<ManualFetch>[1]);\n}\n\nexport async function fetchManual(\n fetchImpl: ManualFetch,\n url: string,\n init: ManualInit,\n wrapNetwork: (err: unknown) => Error,\n): Promise<Awaited<ReturnType<ManualFetch>>> {\n try {\n return await sendManual(fetchImpl, url, init);\n } catch (err) {\n if (err instanceof Error && err.name === \"AbortError\") throw err;\n throw wrapNetwork(err);\n }\n}\n"],"mappings":";AAuBA,SAAgB,mBAAmB,SAAmC;CACpE,IAAI,OAAO,WAAW,UAAU,YAAY,MAAM,QAAQ;CAC1D,OAAO,WAAW,MAAM,KAAK,UAAU;AACzC;;AAGA,eAAsB,WACpB,WACA,KACA,MAC2C;CAC3C,OAAO,MAAM,UAAU,KAAK;EAAE,GAAG;EAAM,UAAU;CAAS,CAA+B;AAC3F;AAEA,eAAsB,YACpB,WACA,KACA,MACA,aAC2C;CAC3C,IAAI;EACF,OAAO,MAAM,WAAW,WAAW,KAAK,IAAI;CAC9C,SAAS,KAAK;EACZ,IAAI,eAAe,SAAS,IAAI,SAAS,cAAc,MAAM;EAC7D,MAAM,YAAY,GAAG;CACvB;AACF"}