@welshman/app 0.12.5 → 0.13.0

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 (198) hide show
  1. package/dist/app/src/index.d.ts +1 -0
  2. package/dist/app/src/index.d.ts.map +1 -1
  3. package/dist/app/src/index.js +1 -0
  4. package/dist/app/src/index.js.map +1 -1
  5. package/dist/app/src/plugins/base.d.ts +18 -41
  6. package/dist/app/src/plugins/base.d.ts.map +1 -1
  7. package/dist/app/src/plugins/base.js +25 -51
  8. package/dist/app/src/plugins/base.js.map +1 -1
  9. package/dist/app/src/plugins/blockedRelayLists.d.ts +1 -1
  10. package/dist/app/src/plugins/blockedRelayLists.d.ts.map +1 -1
  11. package/dist/app/src/plugins/blossomServerLists.d.ts +1 -1
  12. package/dist/app/src/plugins/blossomServerLists.d.ts.map +1 -1
  13. package/dist/app/src/plugins/events.d.ts +5 -6
  14. package/dist/app/src/plugins/events.d.ts.map +1 -1
  15. package/dist/app/src/plugins/events.js +24 -38
  16. package/dist/app/src/plugins/events.js.map +1 -1
  17. package/dist/app/src/plugins/feedLists.d.ts +1 -1
  18. package/dist/app/src/plugins/feedLists.d.ts.map +1 -1
  19. package/dist/app/src/plugins/feeds.d.ts +1 -1
  20. package/dist/app/src/plugins/feeds.d.ts.map +1 -1
  21. package/dist/app/src/plugins/feeds.js +2 -1
  22. package/dist/app/src/plugins/feeds.js.map +1 -1
  23. package/dist/app/src/plugins/handles.d.ts +1 -1
  24. package/dist/app/src/plugins/handles.d.ts.map +1 -1
  25. package/dist/app/src/plugins/handles.js +2 -2
  26. package/dist/app/src/plugins/handles.js.map +1 -1
  27. package/dist/app/src/plugins/logger.d.ts +1 -1
  28. package/dist/app/src/plugins/logger.d.ts.map +1 -1
  29. package/dist/app/src/plugins/logger.js +1 -1
  30. package/dist/app/src/plugins/logger.js.map +1 -1
  31. package/dist/app/src/plugins/messagingRelayLists.d.ts +1 -1
  32. package/dist/app/src/plugins/messagingRelayLists.d.ts.map +1 -1
  33. package/dist/app/src/plugins/notify.d.ts +23 -0
  34. package/dist/app/src/plugins/notify.d.ts.map +1 -0
  35. package/dist/app/src/plugins/notify.js +28 -0
  36. package/dist/app/src/plugins/notify.js.map +1 -0
  37. package/dist/app/src/plugins/pinboards.d.ts +1 -1
  38. package/dist/app/src/plugins/pinboards.d.ts.map +1 -1
  39. package/dist/app/src/plugins/pinboards.js +2 -1
  40. package/dist/app/src/plugins/pinboards.js.map +1 -1
  41. package/dist/app/src/plugins/plaintext.d.ts +6 -2
  42. package/dist/app/src/plugins/plaintext.d.ts.map +1 -1
  43. package/dist/app/src/plugins/plaintext.js +34 -14
  44. package/dist/app/src/plugins/plaintext.js.map +1 -1
  45. package/dist/app/src/plugins/profiles.d.ts +2 -1
  46. package/dist/app/src/plugins/profiles.d.ts.map +1 -1
  47. package/dist/app/src/plugins/profiles.js +2 -2
  48. package/dist/app/src/plugins/profiles.js.map +1 -1
  49. package/dist/app/src/plugins/publisher.d.ts +9 -5
  50. package/dist/app/src/plugins/publisher.d.ts.map +1 -1
  51. package/dist/app/src/plugins/publisher.js +108 -75
  52. package/dist/app/src/plugins/publisher.js.map +1 -1
  53. package/dist/app/src/plugins/relayLists.d.ts +1 -1
  54. package/dist/app/src/plugins/relayLists.d.ts.map +1 -1
  55. package/dist/app/src/plugins/relayLists.js +1 -1
  56. package/dist/app/src/plugins/relayLists.js.map +1 -1
  57. package/dist/app/src/plugins/relayRoles.d.ts +1 -1
  58. package/dist/app/src/plugins/relayRoles.d.ts.map +1 -1
  59. package/dist/app/src/plugins/relayRoles.js +1 -1
  60. package/dist/app/src/plugins/relayRoles.js.map +1 -1
  61. package/dist/app/src/plugins/relays.d.ts +2 -1
  62. package/dist/app/src/plugins/relays.d.ts.map +1 -1
  63. package/dist/app/src/plugins/relays.js.map +1 -1
  64. package/dist/app/src/plugins/roomLists.d.ts +1 -1
  65. package/dist/app/src/plugins/roomLists.d.ts.map +1 -1
  66. package/dist/app/src/plugins/roomLists.js +2 -1
  67. package/dist/app/src/plugins/roomLists.js.map +1 -1
  68. package/dist/app/src/plugins/roomPins.d.ts +1 -1
  69. package/dist/app/src/plugins/roomPins.d.ts.map +1 -1
  70. package/dist/app/src/plugins/roomPins.js +1 -1
  71. package/dist/app/src/plugins/roomPins.js.map +1 -1
  72. package/dist/app/src/plugins/rooms.d.ts +1 -2
  73. package/dist/app/src/plugins/rooms.d.ts.map +1 -1
  74. package/dist/app/src/plugins/rooms.js +1 -2
  75. package/dist/app/src/plugins/rooms.js.map +1 -1
  76. package/dist/app/src/plugins/searchRelayLists.d.ts +1 -1
  77. package/dist/app/src/plugins/searchRelayLists.d.ts.map +1 -1
  78. package/dist/app/src/plugins/topics.d.ts +2 -1
  79. package/dist/app/src/plugins/topics.d.ts.map +1 -1
  80. package/dist/app/src/plugins/topics.js +2 -2
  81. package/dist/app/src/plugins/topics.js.map +1 -1
  82. package/dist/app/src/plugins/wot.d.ts +1 -1
  83. package/dist/app/src/plugins/wot.d.ts.map +1 -1
  84. package/dist/app/src/plugins/wot.js +1 -2
  85. package/dist/app/src/plugins/wot.js.map +1 -1
  86. package/dist/app/src/plugins/wraps.d.ts +5 -1
  87. package/dist/app/src/plugins/wraps.d.ts.map +1 -1
  88. package/dist/app/src/plugins/wraps.js +6 -10
  89. package/dist/app/src/plugins/wraps.js.map +1 -1
  90. package/dist/app/src/plugins/zapGoals.d.ts +1 -1
  91. package/dist/app/src/plugins/zapGoals.d.ts.map +1 -1
  92. package/dist/app/src/plugins/zapGoals.js +1 -1
  93. package/dist/app/src/plugins/zapGoals.js.map +1 -1
  94. package/dist/app/src/plugins/zappers.d.ts +1 -1
  95. package/dist/app/src/plugins/zappers.d.ts.map +1 -1
  96. package/dist/app/src/plugins/zappers.js +2 -2
  97. package/dist/app/src/plugins/zappers.js.map +1 -1
  98. package/dist/app/src/policy.d.ts +4 -0
  99. package/dist/app/src/policy.d.ts.map +1 -1
  100. package/dist/app/src/policy.js +12 -1
  101. package/dist/app/src/policy.js.map +1 -1
  102. package/dist/app/src/user.d.ts +2 -2
  103. package/dist/app/src/user.d.ts.map +1 -1
  104. package/dist/app/src/user.js.map +1 -1
  105. package/dist/domain/src/core/ListWriter.d.ts +6 -6
  106. package/dist/domain/src/kinds/Bolt12Offer.d.ts.map +1 -1
  107. package/dist/domain/src/kinds/Bolt12Offer.js +10 -9
  108. package/dist/domain/src/kinds/Bolt12Offer.js.map +1 -1
  109. package/dist/domain/src/kinds/Bolt12Zap.d.ts +6 -11
  110. package/dist/domain/src/kinds/Bolt12Zap.d.ts.map +1 -1
  111. package/dist/domain/src/kinds/Bolt12Zap.js +77 -105
  112. package/dist/domain/src/kinds/Bolt12Zap.js.map +1 -1
  113. package/dist/domain/src/kinds/Bolt12ZapIntent.d.ts +2 -0
  114. package/dist/domain/src/kinds/Bolt12ZapIntent.d.ts.map +1 -1
  115. package/dist/domain/src/kinds/Bolt12ZapIntent.js +31 -20
  116. package/dist/domain/src/kinds/Bolt12ZapIntent.js.map +1 -1
  117. package/dist/domain/src/other/Relay.d.ts +21 -0
  118. package/dist/domain/src/other/Relay.d.ts.map +1 -1
  119. package/dist/domain/src/other/Relay.js +6 -0
  120. package/dist/domain/src/other/Relay.js.map +1 -1
  121. package/dist/feeds/src/request.d.ts.map +1 -1
  122. package/dist/feeds/src/request.js +14 -5
  123. package/dist/feeds/src/request.js.map +1 -1
  124. package/dist/lib/src/TaskQueue.d.ts.map +1 -1
  125. package/dist/lib/src/TaskQueue.js +8 -2
  126. package/dist/lib/src/TaskQueue.js.map +1 -1
  127. package/dist/net/src/message.d.ts +4 -0
  128. package/dist/net/src/message.d.ts.map +1 -1
  129. package/dist/net/src/message.js +2 -0
  130. package/dist/net/src/message.js.map +1 -1
  131. package/dist/net/src/policy.d.ts.map +1 -1
  132. package/dist/net/src/policy.js +11 -3
  133. package/dist/net/src/policy.js.map +1 -1
  134. package/dist/net/src/repository.d.ts +24 -12
  135. package/dist/net/src/repository.d.ts.map +1 -1
  136. package/dist/net/src/repository.js +79 -70
  137. package/dist/net/src/repository.js.map +1 -1
  138. package/dist/net/src/util.d.ts +2 -1
  139. package/dist/net/src/util.d.ts.map +1 -1
  140. package/dist/net/src/util.js +6 -5
  141. package/dist/net/src/util.js.map +1 -1
  142. package/dist/net/src/wrapManager.d.ts +0 -2
  143. package/dist/net/src/wrapManager.d.ts.map +1 -1
  144. package/dist/net/src/wrapManager.js +8 -23
  145. package/dist/net/src/wrapManager.js.map +1 -1
  146. package/dist/signer/src/index.d.ts +1 -0
  147. package/dist/signer/src/index.d.ts.map +1 -1
  148. package/dist/signer/src/index.js +1 -0
  149. package/dist/signer/src/index.js.map +1 -1
  150. package/dist/signer/src/nip59.d.ts +13 -6
  151. package/dist/signer/src/nip59.d.ts.map +1 -1
  152. package/dist/signer/src/nip59.js +38 -19
  153. package/dist/signer/src/nip59.js.map +1 -1
  154. package/dist/signer/src/queue.d.ts +21 -0
  155. package/dist/signer/src/queue.d.ts.map +1 -0
  156. package/dist/signer/src/queue.js +40 -0
  157. package/dist/signer/src/queue.js.map +1 -0
  158. package/dist/signer/src/signers/nip01.d.ts +14 -2
  159. package/dist/signer/src/signers/nip01.d.ts.map +1 -1
  160. package/dist/signer/src/signers/nip01.js +6 -2
  161. package/dist/signer/src/signers/nip01.js.map +1 -1
  162. package/dist/signer/src/signers/nip07.d.ts +9 -9
  163. package/dist/signer/src/signers/nip07.d.ts.map +1 -1
  164. package/dist/signer/src/signers/nip07.js +15 -27
  165. package/dist/signer/src/signers/nip07.js.map +1 -1
  166. package/dist/signer/src/signers/nip46.d.ts +7 -3
  167. package/dist/signer/src/signers/nip46.d.ts.map +1 -1
  168. package/dist/signer/src/signers/nip46.js +12 -8
  169. package/dist/signer/src/signers/nip46.js.map +1 -1
  170. package/dist/signer/src/signers/nip55.d.ts +7 -7
  171. package/dist/signer/src/signers/nip55.d.ts.map +1 -1
  172. package/dist/signer/src/signers/nip55.js +33 -40
  173. package/dist/signer/src/signers/nip55.js.map +1 -1
  174. package/dist/signer/src/util.d.ts +27 -15
  175. package/dist/signer/src/util.d.ts.map +1 -1
  176. package/dist/signer/src/util.js +47 -14
  177. package/dist/signer/src/util.js.map +1 -1
  178. package/dist/store/src/misc.d.ts +18 -0
  179. package/dist/store/src/misc.d.ts.map +1 -1
  180. package/dist/store/src/misc.js +14 -0
  181. package/dist/store/src/misc.js.map +1 -1
  182. package/dist/store/src/repository.d.ts +29 -12
  183. package/dist/store/src/repository.d.ts.map +1 -1
  184. package/dist/store/src/repository.js +270 -206
  185. package/dist/store/src/repository.js.map +1 -1
  186. package/dist/tsconfig.build.tsbuildinfo +1 -1
  187. package/dist/util/src/Bolt12.d.ts.map +1 -1
  188. package/dist/util/src/Bolt12.js +11 -8
  189. package/dist/util/src/Bolt12.js.map +1 -1
  190. package/dist/util/src/Events.d.ts +1 -0
  191. package/dist/util/src/Events.d.ts.map +1 -1
  192. package/dist/util/src/Events.js +10 -1
  193. package/dist/util/src/Events.js.map +1 -1
  194. package/dist/util/src/Lightning.d.ts +1 -0
  195. package/dist/util/src/Lightning.d.ts.map +1 -1
  196. package/dist/util/src/Lightning.js +1 -0
  197. package/dist/util/src/Lightning.js.map +1 -1
  198. package/package.json +15 -15
@@ -1,7 +1,7 @@
1
- import { readable } from "svelte/store";
1
+ import { derived, readable } from "svelte/store";
2
2
  import { on, gt, now, noop, mapPop, LRUCache, call, sortBy, first, } from "@welshman/lib";
3
3
  import { matchFilters, getIdFilters, sortEventsAsc, sortEventsDesc, getIdOrAddress, } from "@welshman/util";
4
- import { deriveDeduplicated } from "./misc.js";
4
+ import { deriveDeduplicated, projection, projectFrom } from "./misc.js";
5
5
  export const getEventsById = ({ filters, repository, includeDeleted }) => {
6
6
  const eventsById = new Map();
7
7
  for (const event of repository.query(filters, { includeDeleted })) {
@@ -9,38 +9,42 @@ export const getEventsById = ({ filters, repository, includeDeleted }) => {
9
9
  }
10
10
  return eventsById;
11
11
  };
12
- export const deriveEventsById = ({ filters, repository, includeDeleted }) => readable(new Map(), set => {
13
- const eventsById = getEventsById({ filters, repository, includeDeleted });
14
- set(eventsById);
15
- return on(repository, "update", ({ added, removed }) => {
16
- let dirty = false;
17
- for (const event of added) {
18
- if (matchFilters(filters, event)) {
19
- dirty = true;
20
- eventsById.set(event.id, event);
21
- }
22
- }
23
- if (!includeDeleted) {
24
- for (const id of removed) {
25
- if (mapPop(id, eventsById)) {
12
+ export const deriveEventsById = (options) => {
13
+ const { filters, repository, includeDeleted } = options;
14
+ const store = readable(new Map(), set => {
15
+ const eventsById = getEventsById(options);
16
+ set(eventsById);
17
+ return on(repository, "update", ({ added, removed }) => {
18
+ let dirty = false;
19
+ for (const event of added) {
20
+ if (matchFilters(filters, event)) {
26
21
  dirty = true;
22
+ eventsById.set(event.id, event);
27
23
  }
28
24
  }
29
- }
30
- if (dirty) {
31
- set(eventsById);
32
- }
25
+ if (!includeDeleted) {
26
+ for (const id of removed) {
27
+ if (mapPop(id, eventsById)) {
28
+ dirty = true;
29
+ }
30
+ }
31
+ }
32
+ if (dirty) {
33
+ set(eventsById);
34
+ }
35
+ });
33
36
  });
34
- });
35
- export const deriveArray = (itemsByIdStore) => deriveDeduplicated(itemsByIdStore, itemsById => Array.from(itemsById.values()));
36
- export const deriveEventsAsc = (eventsByIdStore) => deriveDeduplicated(eventsByIdStore, eventsById => sortEventsAsc(eventsById.values()));
37
- export const deriveEventsDesc = (eventsByIdStore) => deriveDeduplicated(eventsByIdStore, eventsById => sortEventsDesc(eventsById.values()));
37
+ return projection(store, () => getEventsById(options));
38
+ };
39
+ export const deriveArray = (itemsById) => projectFrom(itemsById, $itemsById => Array.from($itemsById.values()));
40
+ export const deriveEventsAsc = (eventsById) => projectFrom(eventsById, $eventsById => sortEventsAsc($eventsById.values()));
41
+ export const deriveEventsDesc = (eventsById) => projectFrom(eventsById, $eventsById => sortEventsDesc($eventsById.values()));
38
42
  export const deriveEvents = (options) => deriveArray(deriveEventsById(options));
39
43
  export const makeDeriveEvent = ({ repository, includeDeleted = false, onDerive }) => {
40
44
  return (idOrAddress, ...args) => {
41
45
  const filters = getIdFilters([idOrAddress]);
42
46
  onDerive?.(filters, ...args);
43
- return readable(undefined, set => {
47
+ const store = readable(undefined, set => {
44
48
  let event = first(repository.query(filters, { includeDeleted }));
45
49
  set(event);
46
50
  return on(repository, "update", ({ added, removed }) => {
@@ -59,6 +63,7 @@ export const makeDeriveEvent = ({ repository, includeDeleted = false, onDerive }
59
63
  }
60
64
  });
61
65
  });
66
+ return projection(store, () => first(repository.query(filters, { includeDeleted })));
62
67
  };
63
68
  };
64
69
  export const getEventsByIdByUrl = ({ filters, tracker, repository, includeDeleted, }) => {
@@ -75,97 +80,107 @@ export const getEventsByIdByUrl = ({ filters, tracker, repository, includeDelete
75
80
  }
76
81
  return eventsByIdByUrl;
77
82
  };
78
- export const deriveEventsByIdByUrl = ({ filters, tracker, repository, includeDeleted, }) => readable(new Map(), set => {
79
- const eventsByIdByUrl = getEventsByIdByUrl({ filters, tracker, repository, includeDeleted });
80
- const addEvent = (url, event) => {
81
- if (!matchFilters(filters, event))
82
- return false;
83
- const eventsById = eventsByIdByUrl.get(url);
84
- // The repository swaps a changed copy of a held event in under the same id.
85
- if (eventsById?.get(event.id) === event)
86
- return false;
87
- // Create a new map so we can detect which key changed
88
- const newEventsById = new Map(eventsById);
89
- newEventsById.set(event.id, event);
90
- eventsByIdByUrl.set(url, newEventsById);
91
- return true;
92
- };
93
- const removeEvent = (url, id) => {
94
- const eventsById = eventsByIdByUrl.get(url);
95
- if (eventsById?.has(id)) {
96
- eventsById.delete(id);
97
- if (eventsById.size === 0) {
98
- eventsByIdByUrl.delete(url);
99
- }
100
- else {
101
- // Create a new map so we can detect which key changed
102
- eventsByIdByUrl.set(url, new Map(eventsById));
103
- }
83
+ export const deriveEventsByIdByUrl = (options) => {
84
+ const { filters, tracker, repository, includeDeleted } = options;
85
+ const store = readable(new Map(), set => {
86
+ const eventsByIdByUrl = getEventsByIdByUrl(options);
87
+ const addEvent = (url, event) => {
88
+ if (!matchFilters(filters, event))
89
+ return false;
90
+ const eventsById = eventsByIdByUrl.get(url);
91
+ // The repository swaps a changed copy of a held event in under the same id.
92
+ if (eventsById?.get(event.id) === event)
93
+ return false;
94
+ // Create a new map so we can detect which key changed
95
+ const newEventsById = new Map(eventsById);
96
+ newEventsById.set(event.id, event);
97
+ eventsByIdByUrl.set(url, newEventsById);
104
98
  return true;
105
- }
106
- return false;
107
- };
108
- set(eventsByIdByUrl);
109
- const unsubscribers = [
110
- on(repository, "update", ({ added, removed }) => {
111
- let dirty = false;
112
- for (const event of added) {
113
- for (const url of tracker.getRelays(event.id)) {
114
- dirty = dirty || addEvent(url, event);
99
+ };
100
+ const removeEvent = (url, id) => {
101
+ const eventsById = eventsByIdByUrl.get(url);
102
+ if (eventsById?.has(id)) {
103
+ eventsById.delete(id);
104
+ if (eventsById.size === 0) {
105
+ eventsByIdByUrl.delete(url);
115
106
  }
107
+ else {
108
+ // Create a new map so we can detect which key changed
109
+ eventsByIdByUrl.set(url, new Map(eventsById));
110
+ }
111
+ return true;
116
112
  }
117
- if (!includeDeleted) {
118
- for (const id of removed) {
119
- for (const url of tracker.getRelays(id)) {
120
- dirty = dirty || removeEvent(url, id);
113
+ return false;
114
+ };
115
+ set(eventsByIdByUrl);
116
+ const unsubscribers = [
117
+ on(repository, "update", ({ added, removed }) => {
118
+ let dirty = false;
119
+ for (const event of added) {
120
+ for (const url of tracker.getRelays(event.id)) {
121
+ dirty = addEvent(url, event) || dirty;
122
+ }
123
+ }
124
+ if (!includeDeleted) {
125
+ for (const id of removed) {
126
+ for (const url of tracker.getRelays(id)) {
127
+ dirty = removeEvent(url, id) || dirty;
128
+ }
129
+ }
130
+ }
131
+ if (dirty) {
132
+ set(eventsByIdByUrl);
133
+ }
134
+ }),
135
+ on(tracker, "add", (id, url) => {
136
+ const event = repository.getEvent(id);
137
+ if (event && addEvent(url, event)) {
138
+ set(eventsByIdByUrl);
139
+ }
140
+ }),
141
+ on(tracker, "remove", (id, url) => {
142
+ if (removeEvent(url, id)) {
143
+ set(eventsByIdByUrl);
144
+ }
145
+ }),
146
+ on(tracker, "load", () => {
147
+ eventsByIdByUrl.clear();
148
+ for (const event of repository.query(filters, { includeDeleted })) {
149
+ for (const url of tracker.getRelays(event.id)) {
150
+ addEvent(url, event);
121
151
  }
122
152
  }
123
- }
124
- if (dirty) {
125
- set(eventsByIdByUrl);
126
- }
127
- }),
128
- on(tracker, "add", (id, url) => {
129
- const event = repository.getEvent(id);
130
- if (event && addEvent(url, event)) {
131
153
  set(eventsByIdByUrl);
132
- }
133
- }),
134
- on(tracker, "remove", (id, url) => {
135
- if (removeEvent(url, id)) {
154
+ }),
155
+ on(tracker, "clear", () => {
156
+ eventsByIdByUrl.clear();
136
157
  set(eventsByIdByUrl);
137
- }
138
- }),
139
- on(tracker, "load", () => {
140
- eventsByIdByUrl.clear();
141
- for (const event of repository.query(filters, { includeDeleted })) {
142
- for (const url of tracker.getRelays(event.id)) {
143
- addEvent(url, event);
144
- }
145
- }
146
- set(eventsByIdByUrl);
147
- }),
148
- on(tracker, "clear", () => {
149
- eventsByIdByUrl.clear();
150
- set(eventsByIdByUrl);
151
- }),
152
- ];
153
- return () => unsubscribers.forEach(call);
154
- });
158
+ }),
159
+ ];
160
+ return () => unsubscribers.forEach(call);
161
+ });
162
+ return projection(store, () => getEventsByIdByUrl(options));
163
+ };
164
+ /**
165
+ * The events one relay has been seen to hold, narrowed by the filters. The filters choose the
166
+ * index and the tracker answers provenance per event, so the cost tracks what the caller asked
167
+ * for, and a limit counts only events on the relay.
168
+ */
155
169
  export const getEventsByIdForUrl = ({ url, filters, tracker, repository, includeDeleted, }) => {
156
- const initialIds = Array.from(tracker.getIds(url));
157
- const initialFilters = filters.map(filter => ({ ids: initialIds, ...filter }));
158
170
  const eventsById = new Map();
159
- for (const event of repository.query(initialFilters, { includeDeleted })) {
171
+ const where = (event) => tracker.hasRelay(event.id, url);
172
+ for (const event of repository.query(filters, { includeDeleted, where })) {
160
173
  eventsById.set(event.id, event);
161
174
  }
162
175
  return eventsById;
163
176
  };
164
- export const deriveEventsByIdForUrl = ({ url, filters, tracker, repository, includeDeleted, }) => {
165
- let eventsById = getEventsByIdForUrl({ url, filters, tracker, repository, includeDeleted });
166
- return readable(eventsById, set => {
177
+ export const deriveEventsByIdForUrl = (options) => {
178
+ const { url, filters, tracker, repository, includeDeleted } = options;
179
+ const belongs = (event) => tracker.hasRelay(event.id, url) && matchFilters(filters, event);
180
+ let eventsById = getEventsByIdForUrl(options);
181
+ const store = readable(eventsById, set => {
167
182
  const reset = () => {
168
- eventsById = getEventsByIdForUrl({ url, filters, tracker, repository, includeDeleted });
183
+ eventsById = getEventsByIdForUrl(options);
169
184
  set(eventsById);
170
185
  };
171
186
  reset();
@@ -173,7 +188,7 @@ export const deriveEventsByIdForUrl = ({ url, filters, tracker, repository, incl
173
188
  on(repository, "update", ({ added, removed }) => {
174
189
  let dirty = false;
175
190
  for (const event of added) {
176
- if (tracker.hasRelay(event.id, url) && matchFilters(filters, event)) {
191
+ if (belongs(event)) {
177
192
  eventsById.set(event.id, event);
178
193
  dirty = true;
179
194
  }
@@ -193,7 +208,7 @@ export const deriveEventsByIdForUrl = ({ url, filters, tracker, repository, incl
193
208
  on(tracker, "add", (id, trackedUrl) => {
194
209
  if (trackedUrl === url) {
195
210
  const event = repository.getEvent(id);
196
- if (event && tracker.hasRelay(id, trackedUrl) && matchFilters(filters, event)) {
211
+ if (event && belongs(event)) {
197
212
  eventsById.set(id, event);
198
213
  set(eventsById);
199
214
  }
@@ -210,50 +225,95 @@ export const deriveEventsByIdForUrl = ({ url, filters, tracker, repository, incl
210
225
  ];
211
226
  return () => unsubscribers.forEach(call);
212
227
  });
228
+ return projection(store, () => getEventsByIdForUrl(options));
229
+ };
230
+ const asCollection = (store, pending) => {
231
+ const self = projection(store);
232
+ // Subscribing queues the parses, so a key the index lacks is read, drained, and read again
233
+ const read = (key) => self.get().get(key) ?? Promise.all(pending.values()).then(() => self.get().get(key));
234
+ return { ...self, read };
235
+ };
236
+ const onParseError = (error) => console.warn("Failed to build an item from an event", error);
237
+ /**
238
+ * Build `event`'s item and pass it to `commit`, synchronously when the parse needs no IO.
239
+ * An async parse waits in `pending`, and deleting its entry there cancels the commit.
240
+ */
241
+ const buildItem = (event, eventToItem, pending, commit) => {
242
+ let changed = false;
243
+ try {
244
+ const itemOrPromise = eventToItem(event);
245
+ if (itemOrPromise instanceof Promise) {
246
+ const isPending = () => pending.get(event.id) === parse;
247
+ const parse = itemOrPromise
248
+ .then(item => {
249
+ if (isPending())
250
+ commit(item);
251
+ })
252
+ .catch(onParseError)
253
+ .finally(() => {
254
+ if (isPending())
255
+ pending.delete(event.id);
256
+ });
257
+ pending.set(event.id, parse);
258
+ }
259
+ else {
260
+ changed = Boolean(commit(itemOrPromise));
261
+ }
262
+ }
263
+ catch (error) {
264
+ onParseError(error);
265
+ }
266
+ return changed;
213
267
  };
214
268
  export const deriveItemsByKey = ({ getKey, filters, repository, eventToItem, includeDeleted, }) => {
215
- const deferred = new Map();
269
+ const pending = new Map();
216
270
  const itemsByKey = new Map();
217
- const idsByKey = new Map();
218
- const keysById = new Map();
219
- return readable(itemsByKey, set => {
220
- const onError = (error) => console.warn("Failed to build an item from an event", error);
271
+ // The event owning each key, so removing a superseded event leaves its successor's entry
272
+ const ownerByKey = new Map();
273
+ // The key each event contributed, for teardown.
274
+ const keyById = new Map();
275
+ const store = readable(itemsByKey, set => {
276
+ const commit = (id, item) => {
277
+ if (item) {
278
+ const key = getKey(item);
279
+ itemsByKey.set(key, item);
280
+ ownerByKey.set(key, id);
281
+ keyById.set(id, key);
282
+ set(itemsByKey);
283
+ }
284
+ };
221
285
  const addEvent = (event) => {
222
- if (deferred.has(event.id))
223
- return;
224
- if (keysById.has(event.id))
225
- return;
226
- const commit = (item) => {
227
- if (item) {
228
- const key = getKey(item);
229
- itemsByKey.set(key, item);
230
- idsByKey.set(key, event.id);
231
- keysById.set(event.id, key);
232
- set(itemsByKey);
233
- }
234
- };
235
- try {
236
- const itemOrPromise = eventToItem(event);
237
- // Commit synchronously when `eventToItem` doesn't need IO, so items are in
238
- // place before the initial subscription returns rather than a microtask later
239
- if (itemOrPromise instanceof Promise) {
240
- deferred.set(event.id, itemOrPromise);
241
- itemOrPromise
242
- .then(commit)
243
- .catch(onError)
244
- .finally(() => deferred.delete(event.id));
245
- }
246
- else {
247
- commit(itemOrPromise);
286
+ if (!pending.has(event.id) && !keyById.has(event.id)) {
287
+ buildItem(event, eventToItem, pending, item => commit(event.id, item));
288
+ }
289
+ };
290
+ const removeEvent = (id) => {
291
+ const key = mapPop(id, keyById);
292
+ const owns = key !== undefined && ownerByKey.get(key) === id;
293
+ pending.delete(id);
294
+ if (owns) {
295
+ ownerByKey.delete(key);
296
+ itemsByKey.delete(key);
297
+ }
298
+ return owns;
299
+ };
300
+ // The index outlives a subscription, so a subscribe drops what left the repository meanwhile
301
+ const catchUp = () => {
302
+ const events = repository.query(filters, { includeDeleted });
303
+ const ids = new Set(events.map(event => event.id));
304
+ let dirty = false;
305
+ for (const id of Array.from(keyById.keys())) {
306
+ if (!ids.has(id)) {
307
+ dirty = removeEvent(id) || dirty;
248
308
  }
249
309
  }
250
- catch (error) {
251
- onError(error);
310
+ if (dirty)
311
+ set(itemsByKey);
312
+ for (const event of events) {
313
+ addEvent(event);
252
314
  }
253
315
  };
254
- for (const event of repository.query(filters, { includeDeleted })) {
255
- addEvent(event);
256
- }
316
+ catchUp();
257
317
  return on(repository, "update", ({ added, removed }) => {
258
318
  for (const event of added) {
259
319
  if (matchFilters(filters, event)) {
@@ -263,15 +323,7 @@ export const deriveItemsByKey = ({ getKey, filters, repository, eventToItem, inc
263
323
  if (!includeDeleted) {
264
324
  let dirty = false;
265
325
  for (const id of removed) {
266
- const key = mapPop(id, keysById);
267
- // Only remove if this id still owns the key — replacing a replaceable event
268
- // reports the old id as removed in the same update that adds the new one,
269
- // and the newer event has already taken the key over.
270
- if (key && idsByKey.get(key) === id) {
271
- idsByKey.delete(key);
272
- itemsByKey.delete(key);
273
- dirty = true;
274
- }
326
+ dirty = removeEvent(id) || dirty;
275
327
  }
276
328
  if (dirty) {
277
329
  set(itemsByKey);
@@ -279,6 +331,7 @@ export const deriveItemsByKey = ({ getKey, filters, repository, eventToItem, inc
279
331
  }
280
332
  });
281
333
  });
334
+ return asCollection(store, pending);
282
335
  };
283
336
  /**
284
337
  * Like `deriveItemsByKey`, but relay-scoped: an event is keyed once per relay
@@ -288,14 +341,17 @@ export const deriveItemsByKey = ({ getKey, filters, repository, eventToItem, inc
288
341
  * on multiple relays.
289
342
  */
290
343
  export const deriveItemsByKeyByUrl = ({ getKey, filters, repository, tracker, eventToItem, includeDeleted, revalidateOn, }) => {
291
- const deferred = new Set();
344
+ const pending = new Map();
292
345
  const itemsByKey = new Map();
293
- // Which event currently owns each composite key, so a stale event's removal
294
- // doesn't clobber the entry a newer replaceable event has taken over.
346
+ // Which event currently owns each key, as in `deriveItemsByKey`.
295
347
  const ownerByKey = new Map();
296
- // The composite keys contributed by each event, per relay url, for teardown.
348
+ // The key each event contributed on each relay url, for teardown.
297
349
  const keysById = new Map();
298
- return readable(itemsByKey, set => {
350
+ // What each event parsed to, so a rebuild re-keys it without parsing it again.
351
+ const itemsById = new Map();
352
+ const store = readable(itemsByKey, set => {
353
+ // Held while a catch-up keys its events, so a rebuild emits once rather than per event
354
+ let holding = false;
299
355
  const setKey = (key, id, url, item) => {
300
356
  itemsByKey.set(key, item);
301
357
  ownerByKey.set(key, id);
@@ -316,49 +372,42 @@ export const deriveItemsByKeyByUrl = ({ getKey, filters, repository, tracker, ev
316
372
  }
317
373
  return false;
318
374
  };
319
- const onError = (error) => console.warn("Failed to build an item from an event", error);
320
- const addEvent = (event, urls) => {
321
- const pending = Array.from(urls).filter(url => keysById.get(event.id)?.get(url) === undefined);
322
- if (pending.length === 0)
323
- return;
324
- if (deferred.has(event.id))
325
- return;
326
- const commit = (item) => {
327
- if (item) {
328
- for (const url of pending) {
329
- const key = getKey(item, url);
330
- if (key !== undefined) {
331
- setKey(key, event.id, url, item);
332
- }
375
+ // Keys the item on each tracked relay it lacks a key for, including relays seen mid-parse
376
+ const commit = (id, item) => {
377
+ let keyed = false;
378
+ if (item) {
379
+ itemsById.set(id, item);
380
+ for (const url of tracker.getRelays(id)) {
381
+ const key = keysById.get(id)?.has(url) ? undefined : getKey(item, url);
382
+ if (key !== undefined) {
383
+ setKey(key, id, url, item);
384
+ keyed = true;
333
385
  }
334
- set(itemsByKey);
335
- }
336
- };
337
- try {
338
- const itemOrPromise = eventToItem(event);
339
- // Commit synchronously when `eventToItem` doesn't need IO, so items are in
340
- // place before the initial subscription returns rather than a microtask later
341
- if (itemOrPromise instanceof Promise) {
342
- deferred.add(event.id);
343
- itemOrPromise
344
- .then(commit)
345
- .catch(onError)
346
- .finally(() => deferred.delete(event.id));
347
- }
348
- else {
349
- commit(itemOrPromise);
350
386
  }
387
+ // Emit only when some url keyed the item
388
+ if (keyed && !holding)
389
+ set(itemsByKey);
390
+ }
391
+ return keyed;
392
+ };
393
+ const addEvent = (event) => {
394
+ const parsed = itemsById.get(event.id);
395
+ let keyed = false;
396
+ // A rebuild clears the keys and keeps the items, so re-keying needs no parse
397
+ if (parsed) {
398
+ keyed = commit(event.id, parsed);
351
399
  }
352
- catch (error) {
353
- onError(error);
400
+ else if (!pending.has(event.id)) {
401
+ keyed = buildItem(event, eventToItem, pending, item => commit(event.id, item));
354
402
  }
403
+ return keyed;
355
404
  };
356
405
  const removeEvent = (id) => {
357
406
  const byUrl = mapPop(id, keysById);
358
- if (!byUrl)
359
- return false;
360
407
  let dirty = false;
361
- for (const key of byUrl.values()) {
408
+ pending.delete(id);
409
+ itemsById.delete(id);
410
+ for (const key of byUrl?.values() ?? []) {
362
411
  dirty = dropKey(key, id) || dirty;
363
412
  }
364
413
  return dirty;
@@ -374,21 +423,37 @@ export const deriveItemsByKeyByUrl = ({ getKey, filters, repository, tracker, ev
374
423
  }
375
424
  return dropKey(key, id);
376
425
  };
377
- const rebuild = () => {
426
+ const catchUp = (cleared = false) => {
427
+ const events = repository.query(filters, { includeDeleted });
428
+ const ids = new Set(events.map(event => event.id));
429
+ let dirty = cleared;
430
+ for (const id of Array.from(keysById.keys())) {
431
+ if (!ids.has(id)) {
432
+ dirty = removeEvent(id) || dirty;
433
+ }
434
+ }
435
+ holding = true;
436
+ for (const event of events) {
437
+ dirty = addEvent(event) || dirty;
438
+ }
439
+ holding = false;
440
+ if (dirty)
441
+ set(itemsByKey);
442
+ };
443
+ const clear = () => {
444
+ const dirty = itemsByKey.size > 0;
378
445
  itemsByKey.clear();
379
446
  ownerByKey.clear();
380
447
  keysById.clear();
381
- for (const event of repository.query(filters, { includeDeleted })) {
382
- addEvent(event, tracker.getRelays(event.id));
383
- }
384
- set(itemsByKey);
448
+ return dirty;
385
449
  };
386
- rebuild();
450
+ const rebuild = () => catchUp(clear());
451
+ catchUp();
387
452
  const unsubscribers = [
388
453
  on(repository, "update", ({ added, removed }) => {
389
454
  for (const event of added) {
390
455
  if (matchFilters(filters, event)) {
391
- addEvent(event, tracker.getRelays(event.id));
456
+ addEvent(event);
392
457
  }
393
458
  }
394
459
  if (!includeDeleted) {
@@ -403,7 +468,7 @@ export const deriveItemsByKeyByUrl = ({ getKey, filters, repository, tracker, ev
403
468
  on(tracker, "add", (id, url) => {
404
469
  const event = repository.getEvent(id);
405
470
  if (event && matchFilters(filters, event)) {
406
- addEvent(event, [url]);
471
+ addEvent(event);
407
472
  }
408
473
  }),
409
474
  on(tracker, "remove", (id, url) => {
@@ -413,10 +478,8 @@ export const deriveItemsByKeyByUrl = ({ getKey, filters, repository, tracker, ev
413
478
  }),
414
479
  on(tracker, "load", rebuild),
415
480
  on(tracker, "clear", () => {
416
- itemsByKey.clear();
417
- ownerByKey.clear();
418
- keysById.clear();
419
- set(itemsByKey);
481
+ if (clear())
482
+ set(itemsByKey);
420
483
  }),
421
484
  ];
422
485
  // Re-evaluate everything when external key state (e.g. relay self pubkeys)
@@ -435,9 +498,10 @@ export const deriveItemsByKeyByUrl = ({ getKey, filters, repository, tracker, ev
435
498
  }
436
499
  return () => unsubscribers.forEach(call);
437
500
  });
501
+ return asCollection(store, pending);
438
502
  };
439
- export const deriveItems = (itemsByKeyStore) => deriveDeduplicated(itemsByKeyStore, itemsByKey => Array.from(itemsByKey.values()));
440
- export const deriveItemsSorted = (sortFn, itemsStore) => deriveDeduplicated(itemsStore, items => sortBy(sortFn, items));
503
+ export const deriveItems = (itemsByKeyStore) => derived(itemsByKeyStore, itemsByKey => Array.from(itemsByKey.values()));
504
+ export const deriveItemsSorted = (sortFn, itemsStore) => derived(itemsStore, items => sortBy(sortFn, items));
441
505
  export const makeDeriveItem = (itemsByKeyStore, onDerive) => {
442
506
  return (key, ...args) => {
443
507
  if (!key)
@@ -464,7 +528,7 @@ export const makeLoadItem = (loadItem, getItem, options = {}) => {
464
528
  const attempts = new LRUCache(maxSize);
465
529
  const pending = new Map();
466
530
  return async (key, ...args) => {
467
- const stale = getItem(key);
531
+ const stale = await getItem(key);
468
532
  // If we have an item, reload if it's relatively recent
469
533
  if (stale && getFetched(key) > now() - timeout) {
470
534
  return stale;
@@ -499,7 +563,7 @@ export const makeLoadItem = (loadItem, getItem, options = {}) => {
499
563
  };
500
564
  };
501
565
  // Miscellaneous other stuff
502
- export const deriveIsDeleted = (repository, event) => readable(false, set => {
566
+ export const deriveIsDeleted = (repository, event) => projection(readable(false, set => {
503
567
  const idOrAddress = getIdOrAddress(event);
504
568
  set(repository.isDeleted(event));
505
569
  return on(repository, "update", ({ removed, added }) => {
@@ -512,5 +576,5 @@ export const deriveIsDeleted = (repository, event) => readable(false, set => {
512
576
  }
513
577
  }
514
578
  });
515
- });
579
+ }), () => repository.isDeleted(event));
516
580
  //# sourceMappingURL=repository.js.map