@qntx/nostr 0.5.0 → 0.7.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 (178) hide show
  1. package/dist/{abort-Ba8tDrOI.mjs → abort-DBq2STPj.mjs} +13 -6
  2. package/dist/abort-DBq2STPj.mjs.map +1 -0
  3. package/dist/{builder-9tjI6WQ3.mjs → builder-CdLE4J6O.mjs} +11 -17
  4. package/dist/builder-CdLE4J6O.mjs.map +1 -0
  5. package/dist/{builder-sSflu6nB.d.mts → builder-DgkqDylK.d.mts} +5 -6
  6. package/dist/{client-BtIAtxrL.mjs → client-CILhdfsF.mjs} +104 -314
  7. package/dist/client-CILhdfsF.mjs.map +1 -0
  8. package/dist/client.d.mts +2 -2
  9. package/dist/client.mjs +2 -2
  10. package/dist/collections-DtwMc1NU.mjs +33 -0
  11. package/dist/collections-DtwMc1NU.mjs.map +1 -0
  12. package/dist/core.d.mts +9 -8
  13. package/dist/core.mjs +7 -7
  14. package/dist/{error-BW7re1UN.d.mts → error-BD_0QFKT.d.mts} +2 -2
  15. package/dist/{error-CR8Urc84.d.mts → error-BquXDmIN.d.mts} +4 -4
  16. package/dist/{error-DrEIM7FO.mjs → error-L_QdmSnV.mjs} +2 -2
  17. package/dist/{error-DrEIM7FO.mjs.map → error-L_QdmSnV.mjs.map} +1 -1
  18. package/dist/{error-B__OMBRY.mjs → error-gtJvV6YS.mjs} +2 -2
  19. package/dist/{error-B__OMBRY.mjs.map → error-gtJvV6YS.mjs.map} +1 -1
  20. package/dist/{event-fU3Y01PR.d.mts → event-DJeb_DIL.d.mts} +4 -2
  21. package/dist/{event-D8Nfoh4K.mjs → event-DinQMmJ1.mjs} +16 -10
  22. package/dist/event-DinQMmJ1.mjs.map +1 -0
  23. package/dist/{filter-C0IMByE3.d.mts → filter-jI_9htF7.d.mts} +2 -2
  24. package/dist/{gossip-DpIvkBuj.d.mts → gossip-C7HLW5kI.d.mts} +35 -26
  25. package/dist/{gossip-oloDMWEj.mjs → gossip-CcTp9VpU.mjs} +23 -33
  26. package/dist/gossip-CcTp9VpU.mjs.map +1 -0
  27. package/dist/gossip.d.mts +1 -1
  28. package/dist/gossip.mjs +1 -1
  29. package/dist/{index-CvkvqB16.d.mts → index-DU9tRFTZ.d.mts} +7 -20
  30. package/dist/{index-q4MDce_r.d.mts → index-DiXwNn2M.d.mts} +6 -8
  31. package/dist/{index-QxvrpMfF.d.mts → index-DocgD9K0.d.mts} +92 -167
  32. package/dist/{index-D1bqFHKB.d.mts → index-InQZpHyQ.d.mts} +18 -18
  33. package/dist/{index-MxC28S4P.d.mts → index-Tx15HGAD.d.mts} +26 -28
  34. package/dist/{index-Dmc8FJgO.d.mts → index-iLT8UzY3.d.mts} +19 -37
  35. package/dist/index.d.mts +23 -21
  36. package/dist/index.mjs +21 -19
  37. package/dist/instance-BcKzxHIP.mjs +220 -0
  38. package/dist/instance-BcKzxHIP.mjs.map +1 -0
  39. package/dist/instance-DjsL8h84.d.mts +15 -0
  40. package/dist/{key-Cv4_pN3x.d.mts → key-D_5OgGT7.d.mts} +11 -7
  41. package/dist/{key-Bps-LqX7.mjs → key-bshqfmZL.mjs} +13 -9
  42. package/dist/key-bshqfmZL.mjs.map +1 -0
  43. package/dist/{loaders-o6YXB_Ri.mjs → loaders-DD0XAGzw.mjs} +73 -82
  44. package/dist/loaders-DD0XAGzw.mjs.map +1 -0
  45. package/dist/loaders.d.mts +3 -3
  46. package/dist/loaders.mjs +2 -2
  47. package/dist/{memory-C2EVx0_O.mjs → memory-D_-5-T31.mjs} +2 -2
  48. package/dist/memory-D_-5-T31.mjs.map +1 -0
  49. package/dist/{memory-index-Do1mz0WF.mjs → memory-index-C6rujtoc.mjs} +30 -247
  50. package/dist/memory-index-C6rujtoc.mjs.map +1 -0
  51. package/dist/message-CWLcFTQ1.mjs +194 -0
  52. package/dist/message-CWLcFTQ1.mjs.map +1 -0
  53. package/dist/{message-iGPIqnn0.d.mts → message-CbBZOhq3.d.mts} +3 -3
  54. package/dist/{nip17-CKIufV98.d.mts → nip17-cTDFfFVt.d.mts} +5 -6
  55. package/dist/{nip19-BevCJZsp.d.mts → nip19-D1OnJ192.d.mts} +3 -9
  56. package/dist/{nip46-DDV4dC56.d.mts → nip46-DrsPI8EX.d.mts} +3 -5
  57. package/dist/nip51-DoPUvQ--.d.mts +87 -0
  58. package/dist/{nip59-zQgdLcEr.d.mts → nip59-BFIihLYY.d.mts} +3 -4
  59. package/dist/nip65-52HiwiyD.d.mts +23 -0
  60. package/dist/{nip77-DH4RhS5r.d.mts → nip77-C9QcZn7b.d.mts} +3 -4
  61. package/dist/nips/blossom.d.mts +12 -8
  62. package/dist/nips/blossom.mjs +30 -9
  63. package/dist/nips/blossom.mjs.map +1 -1
  64. package/dist/nips/nip04.mjs +17 -7
  65. package/dist/nips/nip04.mjs.map +1 -1
  66. package/dist/nips/nip05.d.mts +2 -3
  67. package/dist/nips/nip05.mjs +1 -4
  68. package/dist/nips/nip05.mjs.map +1 -1
  69. package/dist/nips/nip10.d.mts +3 -3
  70. package/dist/nips/nip10.mjs +3 -3
  71. package/dist/nips/nip11.d.mts +1 -1
  72. package/dist/nips/nip11.mjs +4 -4
  73. package/dist/nips/nip11.mjs.map +1 -1
  74. package/dist/nips/nip13.d.mts +7 -7
  75. package/dist/nips/nip13.mjs +32 -10
  76. package/dist/nips/nip13.mjs.map +1 -1
  77. package/dist/nips/nip17.d.mts +1 -1
  78. package/dist/nips/nip17.mjs +13 -33
  79. package/dist/nips/nip17.mjs.map +1 -1
  80. package/dist/nips/nip19.d.mts +2 -2
  81. package/dist/nips/nip19.mjs +16 -28
  82. package/dist/nips/nip19.mjs.map +1 -1
  83. package/dist/nips/nip21.d.mts +6 -1
  84. package/dist/nips/nip21.mjs +15 -5
  85. package/dist/nips/nip21.mjs.map +1 -1
  86. package/dist/nips/nip27.d.mts +4 -4
  87. package/dist/nips/nip27.mjs.map +1 -1
  88. package/dist/nips/nip42.d.mts +1 -1
  89. package/dist/nips/nip42.mjs +2 -1
  90. package/dist/nips/nip42.mjs.map +1 -1
  91. package/dist/nips/nip44.mjs +1 -1
  92. package/dist/nips/nip46.d.mts +2 -2
  93. package/dist/nips/nip46.mjs +15 -19
  94. package/dist/nips/nip46.mjs.map +1 -1
  95. package/dist/nips/nip49.d.mts +5 -5
  96. package/dist/nips/nip49.mjs +4 -4
  97. package/dist/nips/nip49.mjs.map +1 -1
  98. package/dist/nips/nip51.d.mts +2 -86
  99. package/dist/nips/nip51.mjs +7 -23
  100. package/dist/nips/nip51.mjs.map +1 -1
  101. package/dist/nips/nip57.d.mts +7 -5
  102. package/dist/nips/nip57.mjs +12 -17
  103. package/dist/nips/nip57.mjs.map +1 -1
  104. package/dist/nips/nip59.d.mts +1 -1
  105. package/dist/nips/nip59.mjs +16 -16
  106. package/dist/nips/nip59.mjs.map +1 -1
  107. package/dist/nips/nip65.d.mts +2 -2
  108. package/dist/nips/nip65.mjs +14 -37
  109. package/dist/nips/nip65.mjs.map +1 -1
  110. package/dist/nips/nip77.d.mts +1 -1
  111. package/dist/nips/nip77.mjs +33 -32
  112. package/dist/nips/nip77.mjs.map +1 -1
  113. package/dist/nips/nip98.d.mts +5 -5
  114. package/dist/nips/nip98.mjs +8 -8
  115. package/dist/nips/nip98.mjs.map +1 -1
  116. package/dist/nostr_crypto_wasm_bg.wasm +0 -0
  117. package/dist/{pool-DBIYMtwy.d.mts → pool-SzJKPIa3.d.mts} +96 -91
  118. package/dist/pool-YHRZgcQ3.mjs +596 -0
  119. package/dist/pool-YHRZgcQ3.mjs.map +1 -0
  120. package/dist/put-DS0yzVJx.mjs +187 -0
  121. package/dist/put-DS0yzVJx.mjs.map +1 -0
  122. package/dist/{reactive-DFpqtIf_.d.mts → reactive-BJ1ggn4d.d.mts} +9 -22
  123. package/dist/{reactive-DDlThDSZ.mjs → reactive-BJRoVHxU.mjs} +113 -64
  124. package/dist/reactive-BJRoVHxU.mjs.map +1 -0
  125. package/dist/{pool-As20OGxB.mjs → relay-CvxPiQ_m.mjs} +178 -721
  126. package/dist/relay-CvxPiQ_m.mjs.map +1 -0
  127. package/dist/relay.d.mts +4 -4
  128. package/dist/relay.mjs +3 -2
  129. package/dist/serial-Bzpw-xz3.mjs +21 -0
  130. package/dist/serial-Bzpw-xz3.mjs.map +1 -0
  131. package/dist/{signer-CYQutDzD.mjs → signer-yW3slDC2.mjs} +12 -14
  132. package/dist/signer-yW3slDC2.mjs.map +1 -0
  133. package/dist/signer.d.mts +2 -2
  134. package/dist/signer.mjs +2 -2
  135. package/dist/{storage-Dky3wuoH.mjs → storage-CZ52ifcQ.mjs} +17 -42
  136. package/dist/storage-CZ52ifcQ.mjs.map +1 -0
  137. package/dist/storage.d.mts +3 -3
  138. package/dist/storage.mjs +4 -4
  139. package/dist/store.d.mts +1 -1
  140. package/dist/store.mjs +1 -1
  141. package/dist/testing.d.mts +18 -18
  142. package/dist/testing.mjs +9 -9
  143. package/dist/testing.mjs.map +1 -1
  144. package/dist/{types-2pCUprRU.d.mts → types-Cm-XMgtH.d.mts} +6 -6
  145. package/dist/{types-CRQEk0Oi.d.mts → types-DNmiscfd.d.mts} +2 -2
  146. package/dist/{util-LfQirgDk.mjs → util-yFYzlbrm.mjs} +29 -7
  147. package/dist/util-yFYzlbrm.mjs.map +1 -0
  148. package/dist/{verifier-t5WoWbtY.mjs → verifier-BTLOpo1n.mjs} +3 -3
  149. package/dist/verifier-BTLOpo1n.mjs.map +1 -0
  150. package/dist/verifier-CgTFpgsC.d.mts +22 -0
  151. package/dist/wasm.browser.d.mts +8 -0
  152. package/dist/wasm.browser.mjs +19 -0
  153. package/dist/wasm.browser.mjs.map +1 -0
  154. package/dist/wasm.d.mts +3 -13
  155. package/dist/wasm.mjs +7 -208
  156. package/dist/wasm.mjs.map +1 -1
  157. package/package.json +13 -10
  158. package/dist/abort-Ba8tDrOI.mjs.map +0 -1
  159. package/dist/builder-9tjI6WQ3.mjs.map +0 -1
  160. package/dist/client-BtIAtxrL.mjs.map +0 -1
  161. package/dist/event-D8Nfoh4K.mjs.map +0 -1
  162. package/dist/gossip-oloDMWEj.mjs.map +0 -1
  163. package/dist/key-Bps-LqX7.mjs.map +0 -1
  164. package/dist/loaders-o6YXB_Ri.mjs.map +0 -1
  165. package/dist/memory-C2EVx0_O.mjs.map +0 -1
  166. package/dist/memory-index-Do1mz0WF.mjs.map +0 -1
  167. package/dist/message-DcY6Fi9o.mjs +0 -193
  168. package/dist/message-DcY6Fi9o.mjs.map +0 -1
  169. package/dist/nip65-CjgmdpPB.d.mts +0 -26
  170. package/dist/nips/nip96.d.mts +0 -40
  171. package/dist/nips/nip96.mjs +0 -118
  172. package/dist/nips/nip96.mjs.map +0 -1
  173. package/dist/pool-As20OGxB.mjs.map +0 -1
  174. package/dist/reactive-DDlThDSZ.mjs.map +0 -1
  175. package/dist/signer-CYQutDzD.mjs.map +0 -1
  176. package/dist/storage-Dky3wuoH.mjs.map +0 -1
  177. package/dist/util-LfQirgDk.mjs.map +0 -1
  178. package/dist/verifier-t5WoWbtY.mjs.map +0 -1
@@ -0,0 +1,187 @@
1
+ import { o as isHex32 } from "./util-yFYzlbrm.mjs";
2
+ import { i as isEphemeralKind, t as Kind } from "./kind-Cxwhhkx8.mjs";
3
+ import { a as isReplaceableWinner, g as eventAddress, m as validateSignedEvent, x as parseEventAddress } from "./event-DinQMmJ1.mjs";
4
+ //#region src/storage/deletion.ts
5
+ /**
6
+ * NIP-09 application plan for a kind 5 event.
7
+ *
8
+ * - `e` tags: delete only when the target exists, is not itself kind 5, and shares pubkey.
9
+ * - Unknown `e` ids are pending until the event arrives (client MUST validate pubkey).
10
+ * - `a` tags: tombstone that coordinate up to this deletion's `created_at` when pubkey matches.
11
+ * - Deleting a deletion request is a no-op.
12
+ */
13
+ function planDeletion(deletion, getById) {
14
+ const { pubkey } = deletion;
15
+ const removeIds = [];
16
+ const pendingIds = [];
17
+ const coordinates = [];
18
+ const seenIds = /* @__PURE__ */ new Set();
19
+ const seenCoords = /* @__PURE__ */ new Set();
20
+ for (const tag of deletion.tags) {
21
+ if (tag[0] === "e" && tag[1] !== void 0 && isHex32(tag[1].toLowerCase())) {
22
+ const id = tag[1].toLowerCase();
23
+ if (seenIds.has(id)) continue;
24
+ seenIds.add(id);
25
+ const existing = getById(id);
26
+ if (!existing) {
27
+ pendingIds.push({
28
+ id,
29
+ pubkey
30
+ });
31
+ continue;
32
+ }
33
+ if (existing.kind === Kind.EventDeletion) continue;
34
+ if (existing.pubkey !== pubkey) continue;
35
+ removeIds.push(id);
36
+ continue;
37
+ }
38
+ if (tag[0] === "a" && tag[1] !== void 0) {
39
+ const coord = parseEventAddress(tag[1]);
40
+ if (!coord || coord.pubkey !== pubkey) continue;
41
+ const key = `${coord.kind}:${coord.pubkey}:${coord.identifier}`;
42
+ if (seenCoords.has(key)) continue;
43
+ seenCoords.add(key);
44
+ coordinates.push({
45
+ key,
46
+ until: deletion.created_at
47
+ });
48
+ }
49
+ }
50
+ return {
51
+ removeIds,
52
+ pendingIds,
53
+ coordinates
54
+ };
55
+ }
56
+ /** In-memory NIP-09 tombstones shared by MemoryEventStore and IndexedDbEventStore. */
57
+ var DeletionState = class {
58
+ ids = /* @__PURE__ */ new Set();
59
+ pending = /* @__PURE__ */ new Map();
60
+ coordinates = /* @__PURE__ */ new Map();
61
+ clear() {
62
+ this.ids.clear();
63
+ this.pending.clear();
64
+ this.coordinates.clear();
65
+ }
66
+ covers(event) {
67
+ if (this.ids.has(event.id)) return true;
68
+ if (event.kind === Kind.EventDeletion) return false;
69
+ const pendingPk = this.pending.get(event.id);
70
+ if (pendingPk !== void 0 && event.pubkey === pendingPk) return true;
71
+ const addr = eventAddress(event);
72
+ if (addr === void 0) return false;
73
+ const until = this.coordinates.get(addr);
74
+ return until !== void 0 && event.created_at <= until;
75
+ }
76
+ absorb(plan) {
77
+ for (const id of plan.removeIds) {
78
+ this.ids.add(id);
79
+ this.pending.delete(id);
80
+ }
81
+ for (const p of plan.pendingIds) if (!this.ids.has(p.id)) this.pending.set(p.id, p.pubkey);
82
+ for (const c of plan.coordinates) {
83
+ const prev = this.coordinates.get(c.key) ?? Number.NEGATIVE_INFINITY;
84
+ this.coordinates.delete(c.key);
85
+ this.coordinates.set(c.key, Math.max(prev, c.until));
86
+ }
87
+ }
88
+ };
89
+ function coordinateRemovals(coordinates, getCurrent) {
90
+ const ids = [];
91
+ for (const c of coordinates) {
92
+ const current = getCurrent(c.key);
93
+ if (current && current.created_at <= c.until) ids.push(current.id);
94
+ }
95
+ return ids;
96
+ }
97
+ //#endregion
98
+ //#region src/storage/put.ts
99
+ function outboxBoundKey(pubkey, kind) {
100
+ return `${pubkey.toLowerCase()}:${kind}`;
101
+ }
102
+ function decidePut(raw, lookup) {
103
+ if (!validateSignedEvent(raw)) return {
104
+ action: "skip",
105
+ result: "invalid",
106
+ event: raw
107
+ };
108
+ const event = raw;
109
+ if (lookup.deletion.ids.has(event.id) || lookup.getById(event.id)) return {
110
+ action: "skip",
111
+ result: "duplicate",
112
+ event
113
+ };
114
+ if (event.kind === Kind.EventDeletion) {
115
+ const plan = planDeletion(event, lookup.getById);
116
+ return {
117
+ action: "delete",
118
+ result: "deleted",
119
+ event,
120
+ plan,
121
+ coordIds: coordinateRemovals(plan.coordinates, lookup.getReplaceable)
122
+ };
123
+ }
124
+ if (lookup.deletion.covers(event)) return {
125
+ action: "tombstone",
126
+ result: "duplicate",
127
+ event
128
+ };
129
+ if (isEphemeralKind(event.kind)) return {
130
+ action: "skip",
131
+ result: "ephemeral",
132
+ event
133
+ };
134
+ const address = eventAddress(event);
135
+ if (address !== void 0) {
136
+ const prev = lookup.getReplaceable(address);
137
+ if (prev && prev.id !== event.id && !isReplaceableWinner(event, prev)) return {
138
+ action: "skip",
139
+ result: "rejected",
140
+ event
141
+ };
142
+ if (prev !== void 0 && prev.evicted !== true) return {
143
+ action: "insert",
144
+ result: "replaced",
145
+ event,
146
+ address,
147
+ replaceId: prev.id
148
+ };
149
+ return {
150
+ action: "insert",
151
+ result: "accepted",
152
+ event,
153
+ address
154
+ };
155
+ }
156
+ return {
157
+ action: "insert",
158
+ result: "accepted",
159
+ event
160
+ };
161
+ }
162
+ function applyPutMemory(s, d) {
163
+ if (d.action === "skip") return d.result;
164
+ if (d.action === "tombstone") {
165
+ s.deletion.ids.add(d.event.id);
166
+ s.deletion.pending.delete(d.event.id);
167
+ return "duplicate";
168
+ }
169
+ if (d.action === "delete") {
170
+ s.deletion.pending.delete(d.event.id);
171
+ s.deletion.absorb(d.plan);
172
+ for (const id of d.plan.removeIds) s.indexRemove(id);
173
+ for (const id of d.coordIds) {
174
+ s.deletion.ids.add(id);
175
+ s.indexRemove(id);
176
+ }
177
+ s.indexInsert(d.event);
178
+ return "deleted";
179
+ }
180
+ if (d.replaceId !== void 0) s.indexRemove(d.replaceId);
181
+ s.indexInsert(d.event);
182
+ return d.result;
183
+ }
184
+ //#endregion
185
+ export { planDeletion as a, DeletionState as i, decidePut as n, outboxBoundKey as r, applyPutMemory as t };
186
+
187
+ //# sourceMappingURL=put-DS0yzVJx.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"put-DS0yzVJx.mjs","names":[],"sources":["../src/storage/deletion.ts","../src/storage/put.ts"],"sourcesContent":["import type { Event } from \"../core/event.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport { eventAddress, parseEventAddress } from \"../core/tag.ts\";\nimport { isHex32 } from \"../core/util.ts\";\n\nexport type DeletionPlan = {\n /** Stored events validated as same-pubkey (not kind 5) and to be removed now. */\n removeIds: string[];\n /** Referenced ids not in the store; remember deletion pubkey until the event arrives. */\n pendingIds: Array<{ id: string; pubkey: string }>;\n /** Replaceable/addressable coordinates tombstoned up to `until` (deletion created_at). */\n coordinates: Array<{ key: string; until: number }>;\n};\n\n/**\n * NIP-09 application plan for a kind 5 event.\n *\n * - `e` tags: delete only when the target exists, is not itself kind 5, and shares pubkey.\n * - Unknown `e` ids are pending until the event arrives (client MUST validate pubkey).\n * - `a` tags: tombstone that coordinate up to this deletion's `created_at` when pubkey matches.\n * - Deleting a deletion request is a no-op.\n */\nexport function planDeletion(\n deletion: Pick<Event, \"pubkey\" | \"created_at\" | \"tags\">,\n getById: (id: string) => Pick<Event, \"id\" | \"pubkey\" | \"kind\"> | undefined,\n): DeletionPlan {\n const { pubkey } = deletion;\n const removeIds: string[] = [];\n const pendingIds: Array<{ id: string; pubkey: string }> = [];\n const coordinates: Array<{ key: string; until: number }> = [];\n const seenIds = new Set<string>();\n const seenCoords = new Set<string>();\n\n for (const tag of deletion.tags) {\n if (tag[0] === \"e\" && tag[1] !== undefined && isHex32(tag[1].toLowerCase())) {\n const id = tag[1].toLowerCase();\n if (seenIds.has(id)) {\n continue;\n }\n seenIds.add(id);\n const existing = getById(id);\n if (!existing) {\n pendingIds.push({ id, pubkey });\n continue;\n }\n if (existing.kind === Kind.EventDeletion) {\n continue;\n }\n if (existing.pubkey !== pubkey) {\n continue;\n }\n removeIds.push(id);\n continue;\n }\n\n if (tag[0] === \"a\" && tag[1] !== undefined) {\n const coord = parseEventAddress(tag[1]);\n if (!coord || coord.pubkey !== pubkey) {\n continue;\n }\n const key = `${coord.kind}:${coord.pubkey}:${coord.identifier}`;\n if (seenCoords.has(key)) {\n continue;\n }\n seenCoords.add(key);\n coordinates.push({ key, until: deletion.created_at });\n }\n }\n\n return { removeIds, pendingIds, coordinates };\n}\n\n/** In-memory NIP-09 tombstones shared by MemoryEventStore and IndexedDbEventStore. */\nexport class DeletionState {\n readonly ids: Set<string> = new Set<string>();\n readonly pending: Map<string, string> = new Map<string, string>();\n readonly coordinates: Map<string, number> = new Map<string, number>();\n\n clear(): void {\n this.ids.clear();\n this.pending.clear();\n this.coordinates.clear();\n }\n\n covers(event: Pick<Event, \"id\" | \"pubkey\" | \"kind\" | \"created_at\" | \"tags\">): boolean {\n if (this.ids.has(event.id)) {\n return true;\n }\n if (event.kind === Kind.EventDeletion) {\n return false;\n }\n const pendingPk = this.pending.get(event.id);\n if (pendingPk !== undefined && event.pubkey === pendingPk) {\n return true;\n }\n const addr = eventAddress(event);\n if (addr === undefined) {\n return false;\n }\n const until = this.coordinates.get(addr);\n return until !== undefined && event.created_at <= until;\n }\n\n absorb(plan: DeletionPlan): void {\n for (const id of plan.removeIds) {\n this.ids.add(id);\n this.pending.delete(id);\n }\n for (const p of plan.pendingIds) {\n if (!this.ids.has(p.id)) {\n this.pending.set(p.id, p.pubkey);\n }\n }\n for (const c of plan.coordinates) {\n const prev = this.coordinates.get(c.key) ?? Number.NEGATIVE_INFINITY;\n // Re-absorbing a coordinate moves it to the newest end so FIFO\n // tombstone caps evict the oldest marker, not the freshest.\n this.coordinates.delete(c.key);\n this.coordinates.set(c.key, Math.max(prev, c.until));\n }\n }\n}\n\nexport function coordinateRemovals(\n coordinates: ReadonlyArray<{ key: string; until: number }>,\n getCurrent: (key: string) => Pick<Event, \"id\" | \"created_at\"> | undefined,\n): string[] {\n const ids: string[] = [];\n for (const c of coordinates) {\n const current = getCurrent(c.key);\n if (current && current.created_at <= c.until) {\n ids.push(current.id);\n }\n }\n return ids;\n}\n","import type { Event } from \"../core/event.ts\";\nimport { isReplaceableWinner, validateSignedEvent } from \"../core/event.ts\";\nimport { isEphemeralKind, Kind } from \"../core/kind.ts\";\nimport { eventAddress } from \"../core/tag.ts\";\nimport { coordinateRemovals, planDeletion } from \"./deletion.ts\";\nimport type { DeletionPlan, DeletionState } from \"./deletion.ts\";\nimport type { PutResult } from \"./types.ts\";\n\nexport function outboxBoundKey(pubkey: string, kind: number): string {\n return `${pubkey.toLowerCase()}:${kind}`;\n}\n\nexport type PutDecision =\n | { action: \"skip\"; result: \"duplicate\" | \"ephemeral\" | \"rejected\" | \"invalid\"; event: Event }\n | { action: \"tombstone\"; result: \"duplicate\"; event: Event }\n | { action: \"delete\"; result: \"deleted\"; event: Event; plan: DeletionPlan; coordIds: string[] }\n | {\n action: \"insert\";\n result: \"accepted\" | \"replaced\";\n event: Event;\n address?: string;\n replaceId?: string;\n };\n\nexport type PutLookup = {\n deletion: DeletionState;\n getById: (\n id: string,\n ) => Pick<Event, \"id\" | \"pubkey\" | \"kind\" | \"created_at\" | \"tags\"> | undefined;\n /**\n * Current replaceable winner for an address. `evicted: true` marks an eviction watermark: the\n * winning event body was dropped, but its id/created_at still reject older versions, and a re-put\n * of the watermarked id re-inserts it.\n */\n getReplaceable: (\n address: string,\n ) => (Pick<Event, \"id\" | \"created_at\"> & { evicted?: boolean }) | undefined;\n};\n\nexport function decidePut(raw: Event, lookup: PutLookup): PutDecision {\n // Canonical-input precondition: non-canonical events are never indexed.\n if (!validateSignedEvent(raw)) {\n return { action: \"skip\", result: \"invalid\", event: raw };\n }\n const event = raw;\n if (lookup.deletion.ids.has(event.id) || lookup.getById(event.id)) {\n return { action: \"skip\", result: \"duplicate\", event };\n }\n if (event.kind === Kind.EventDeletion) {\n const plan = planDeletion(event, lookup.getById);\n const coordIds = coordinateRemovals(plan.coordinates, lookup.getReplaceable);\n return { action: \"delete\", result: \"deleted\", event, plan, coordIds };\n }\n if (lookup.deletion.covers(event)) {\n return { action: \"tombstone\", result: \"duplicate\", event };\n }\n if (isEphemeralKind(event.kind)) {\n return { action: \"skip\", result: \"ephemeral\", event };\n }\n const address = eventAddress(event);\n if (address !== undefined) {\n const prev = lookup.getReplaceable(address);\n // A stored incumbent rejects stale versions; an eviction watermark does\n // the same, except a re-put of the watermarked id re-inserts it.\n if (prev && prev.id !== event.id && !isReplaceableWinner(event, prev)) {\n return { action: \"skip\", result: \"rejected\", event };\n }\n if (prev !== undefined && prev.evicted !== true) {\n return { action: \"insert\", result: \"replaced\", event, address, replaceId: prev.id };\n }\n return { action: \"insert\", result: \"accepted\", event, address };\n }\n return { action: \"insert\", result: \"accepted\", event };\n}\n\nexport function applyPutMemory(\n s: {\n deletion: DeletionState;\n indexInsert: (event: Event) => void;\n indexRemove: (id: string) => boolean;\n },\n d: PutDecision,\n): PutResult {\n if (d.action === \"skip\") {\n return d.result;\n }\n if (d.action === \"tombstone\") {\n s.deletion.ids.add(d.event.id);\n s.deletion.pending.delete(d.event.id);\n return \"duplicate\";\n }\n if (d.action === \"delete\") {\n s.deletion.pending.delete(d.event.id);\n s.deletion.absorb(d.plan);\n for (const id of d.plan.removeIds) {\n s.indexRemove(id);\n }\n for (const id of d.coordIds) {\n s.deletion.ids.add(id);\n s.indexRemove(id);\n }\n s.indexInsert(d.event);\n return \"deleted\";\n }\n if (d.replaceId !== undefined) {\n s.indexRemove(d.replaceId);\n }\n s.indexInsert(d.event);\n return d.result;\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
- import { t as Event } from "./event-fU3Y01PR.mjs";
2
- import { t as Filter } from "./filter-C0IMByE3.mjs";
3
- import { i as PutResult } from "./types-2pCUprRU.mjs";
1
+ import { t as Event } from "./event-DJeb_DIL.mjs";
2
+ import { t as Filter } from "./filter-jI_9htF7.mjs";
3
+ import { i as PutResult } from "./types-Cm-XMgtH.mjs";
4
4
  //#region src/store/reactive.d.ts
5
5
  /** A reactive snapshot source compatible with `useSyncExternalStore`. */
6
6
  type Watch<T> = {
@@ -10,23 +10,11 @@ type Watch<T> = {
10
10
  /** Capacity options for {@link ReactiveEventStore}. */
11
11
  type ReactiveEventStoreOptions = {
12
12
  /** Max stored events before LRU eviction. Default 50_000. */
13
- maxEvents?: number;
13
+ maxEvents?: number | undefined;
14
14
  /** Max ids tracked by `seenOn`. Default 20_000. */
15
- maxSeenOnEntries?: number;
15
+ maxSeenOnEntries?: number | undefined;
16
16
  /** FIFO cap on tombstoned deletion ids. Default 100_000. */
17
- maxTombstones?: number;
18
- };
19
- /** Category of a {@link Watch} subscription. */
20
- type WatchKind = "event" | "replaceable" | "query";
21
- /** Structural handle the store needs for invalidation; implemented by WatchImpl. */
22
- type WatchHandle = {
23
- readonly kind: WatchKind;
24
- readonly key: string;
25
- readonly filters: ReadonlyArray<Filter> | undefined;
26
- readonly subscribed: boolean;
27
- readonly pinnedIds: ReadonlyArray<string>;
28
- _markDirty: () => void;
29
- _notify: () => void;
17
+ maxTombstones?: number | undefined;
30
18
  };
31
19
  /**
32
20
  * Synchronous reactive in-memory event store backed by {@link MemoryIndex}. The single read source
@@ -36,7 +24,6 @@ type WatchHandle = {
36
24
  */
37
25
  declare class ReactiveEventStore {
38
26
  #private;
39
- _version: number;
40
27
  constructor(opts?: ReactiveEventStoreOptions);
41
28
  add(event: Event, relayUrl?: string): PutResult;
42
29
  addMany(events: ReadonlyArray<Event>, relayUrl?: string): PutResult[];
@@ -58,9 +45,9 @@ declare class ReactiveEventStore {
58
45
  watchQuery(filters: ReadonlyArray<Filter>): Watch<ReadonlyArray<Event>>;
59
46
  /** Synchronous listener for every physical index insert (after update). */
60
47
  onInsert(listener: (event: Event) => void): () => void;
61
- _register(watch: WatchHandle): void;
62
- _unregister(watch: WatchHandle): void;
48
+ /** Synchronous listener for every physical index remove (after watch invalidation). */
49
+ onRemove(listener: (event: Event) => void): () => void;
63
50
  }
64
51
  //#endregion
65
52
  export { ReactiveEventStoreOptions as n, Watch as r, ReactiveEventStore as t };
66
- //# sourceMappingURL=reactive-DFpqtIf_.d.mts.map
53
+ //# sourceMappingURL=reactive-BJ1ggn4d.d.mts.map
@@ -1,12 +1,14 @@
1
- import { l as normalizeURL } from "./util-LfQirgDk.mjs";
2
- import { _ as formatEventAddress, g as eventAddress, o as itemCompare } from "./event-D8Nfoh4K.mjs";
1
+ import { u as normalizeURL } from "./util-yFYzlbrm.mjs";
2
+ import { t as Kind } from "./kind-Cxwhhkx8.mjs";
3
+ import { g as eventAddress, o as itemCompare, v as formatEventAddress, x as parseEventAddress } from "./event-DinQMmJ1.mjs";
3
4
  import { o as matchFilters, r as filterFingerprint } from "./filter-BQl3GlHt.mjs";
4
5
  import { t as invokeSafely } from "./report-CHld4uWj.mjs";
5
- import { t as MemoryIndex } from "./memory-index-Do1mz0WF.mjs";
6
+ import { i as trimOldest, n as removeFromSetMap, t as addToSetMap } from "./collections-DtwMc1NU.mjs";
7
+ import { t as MemoryIndex } from "./memory-index-C6rujtoc.mjs";
6
8
  //#region src/store/reactive.ts
7
9
  const SEEN_ON_PER_ID = 16;
8
10
  var WatchImpl = class {
9
- store;
11
+ host;
10
12
  kind;
11
13
  key;
12
14
  filters;
@@ -18,8 +20,8 @@ var WatchImpl = class {
18
20
  #version = -1;
19
21
  #dirty = false;
20
22
  #pendingRemove = false;
21
- constructor(store, kind, key, compute, equal, snapshotIds, filters) {
22
- this.store = store;
23
+ constructor(host, kind, key, compute, equal, snapshotIds, filters) {
24
+ this.host = host;
23
25
  this.kind = kind;
24
26
  this.key = key;
25
27
  this.filters = filters;
@@ -27,7 +29,7 @@ var WatchImpl = class {
27
29
  this.equal = equal;
28
30
  this.snapshotIds = snapshotIds;
29
31
  this.#snapshot = compute();
30
- this.#version = store._version;
32
+ this.#version = host.version();
31
33
  }
32
34
  get subscribed() {
33
35
  return this.#subscribers.size > 0;
@@ -39,30 +41,33 @@ var WatchImpl = class {
39
41
  const wasUnsubscribed = this.#subscribers.size === 0;
40
42
  this.#subscribers.add(onChange);
41
43
  this.#pendingRemove = false;
42
- if (wasUnsubscribed && this.#version !== this.store._version) this.#dirty = true;
43
- this.store._register(this);
44
+ if (wasUnsubscribed && this.#version !== this.host.version()) this.#dirty = true;
45
+ this.host.register(this);
44
46
  return () => {
45
47
  this.#subscribers.delete(onChange);
46
48
  if (this.#subscribers.size > 0 || this.#pendingRemove) return;
47
49
  this.#pendingRemove = true;
48
50
  queueMicrotask(() => {
49
- if (this.#pendingRemove) this.store._unregister(this);
51
+ if (this.#pendingRemove) this.host.unregister(this);
50
52
  });
51
53
  };
52
54
  }
53
55
  getSnapshot() {
54
- if (this.subscribed ? this.#dirty : this.#version !== this.store._version) {
56
+ if (this.subscribed ? this.#dirty : this.#version !== this.host.version()) {
55
57
  const next = this.compute();
56
- if (!this.equal(next, this.#snapshot)) this.#snapshot = next;
57
- this.#version = this.store._version;
58
+ if (!this.equal(next, this.#snapshot)) {
59
+ this.#snapshot = next;
60
+ this.host.repin(this);
61
+ }
62
+ this.#version = this.host.version();
58
63
  this.#dirty = false;
59
64
  }
60
65
  return this.#snapshot;
61
66
  }
62
- _markDirty() {
67
+ markDirty() {
63
68
  this.#dirty = true;
64
69
  }
65
- _notify() {
70
+ notify() {
66
71
  for (const onChange of this.#subscribers) invokeSafely(onChange);
67
72
  }
68
73
  };
@@ -86,16 +91,49 @@ var ReactiveEventStore = class {
86
91
  #index;
87
92
  #maxEvents;
88
93
  #maxSeenOnEntries;
89
- _version = 0;
94
+ #version = 0;
90
95
  #recency = /* @__PURE__ */ new Set();
91
96
  #seenOn = /* @__PURE__ */ new Map();
92
97
  #idWatches = /* @__PURE__ */ new Map();
93
98
  #addressWatches = /* @__PURE__ */ new Map();
99
+ #eventCache = /* @__PURE__ */ new Map();
100
+ #addressCache = /* @__PURE__ */ new Map();
94
101
  #queryCache = /* @__PURE__ */ new Map();
95
102
  #queryRegistry = /* @__PURE__ */ new Set();
103
+ #pinned = /* @__PURE__ */ new Map();
104
+ #watchPins = /* @__PURE__ */ new Map();
96
105
  #dirty = /* @__PURE__ */ new Set();
97
106
  #flushScheduled = false;
98
107
  #insertListeners = /* @__PURE__ */ new Set();
108
+ #removeListeners = /* @__PURE__ */ new Set();
109
+ #watchHost = {
110
+ version: () => this.#version,
111
+ register: (watch) => {
112
+ this.#registerWatch(watch);
113
+ if (!this.#watchPins.has(watch)) {
114
+ const ids = watch.pinnedIds;
115
+ this.#watchPins.set(watch, ids);
116
+ this.#pin(ids);
117
+ }
118
+ },
119
+ unregister: (watch) => {
120
+ if (watch.subscribed) return;
121
+ this.#unregisterWatch(watch);
122
+ const ids = this.#watchPins.get(watch);
123
+ if (ids !== void 0) {
124
+ this.#watchPins.delete(watch);
125
+ this.#unpin(ids);
126
+ }
127
+ },
128
+ repin: (watch) => {
129
+ const old = this.#watchPins.get(watch);
130
+ if (old === void 0) return;
131
+ const next = watch.pinnedIds;
132
+ this.#unpin(old);
133
+ this.#pin(next);
134
+ this.#watchPins.set(watch, next);
135
+ }
136
+ };
99
137
  constructor(opts) {
100
138
  this.#maxEvents = opts?.maxEvents ?? 5e4;
101
139
  this.#maxSeenOnEntries = opts?.maxSeenOnEntries ?? 2e4;
@@ -138,7 +176,7 @@ var ReactiveEventStore = class {
138
176
  this.#index.clear();
139
177
  this.#seenOn.clear();
140
178
  this.#recency.clear();
141
- this._version += 1;
179
+ this.#version += 1;
142
180
  this.#invalidateAll();
143
181
  }
144
182
  get(id) {
@@ -170,18 +208,28 @@ var ReactiveEventStore = class {
170
208
  }
171
209
  watchEvent(id) {
172
210
  const key = id.toLowerCase();
173
- return new WatchImpl(this, "event", key, () => this.get(key), sameRef, EVENT_IDS);
211
+ let watch = this.#eventCache.get(key);
212
+ if (!watch) {
213
+ watch = new WatchImpl(this.#watchHost, "event", key, () => this.get(key), sameRef, EVENT_IDS);
214
+ this.#eventCache.set(key, watch);
215
+ }
216
+ return watch;
174
217
  }
175
218
  watchReplaceable(kind, pubkey, d) {
176
219
  const address = formatEventAddress(kind, pubkey, d ?? "");
177
- return new WatchImpl(this, "replaceable", address, () => this.getByAddress(address), sameRef, EVENT_IDS);
220
+ let watch = this.#addressCache.get(address);
221
+ if (!watch) {
222
+ watch = new WatchImpl(this.#watchHost, "replaceable", address, () => this.getByAddress(address), sameRef, EVENT_IDS);
223
+ this.#addressCache.set(address, watch);
224
+ }
225
+ return watch;
178
226
  }
179
227
  watchQuery(filters) {
180
228
  const key = filterFingerprint(filters);
181
229
  let watch = this.#queryCache.get(key);
182
230
  if (!watch) {
183
231
  const list = [...filters];
184
- watch = new WatchImpl(this, "query", key, () => this.query(list), sameList, LIST_IDS, list);
232
+ watch = new WatchImpl(this.#watchHost, "query", key, () => this.query(list), sameList, LIST_IDS, list);
185
233
  this.#queryCache.set(key, watch);
186
234
  }
187
235
  return watch;
@@ -193,25 +241,33 @@ var ReactiveEventStore = class {
193
241
  this.#insertListeners.delete(listener);
194
242
  };
195
243
  }
196
- _register(watch) {
244
+ /** Synchronous listener for every physical index remove (after watch invalidation). */
245
+ onRemove(listener) {
246
+ this.#removeListeners.add(listener);
247
+ return () => {
248
+ this.#removeListeners.delete(listener);
249
+ };
250
+ }
251
+ #registerWatch(watch) {
197
252
  switch (watch.kind) {
198
253
  case "event":
199
- addWatch(this.#idWatches, watch.key, watch);
254
+ addToSetMap(this.#idWatches, watch.key, watch);
200
255
  return;
201
256
  case "replaceable":
202
- addWatch(this.#addressWatches, watch.key, watch);
257
+ addToSetMap(this.#addressWatches, watch.key, watch);
203
258
  return;
204
259
  case "query": this.#queryRegistry.add(watch);
205
260
  }
206
261
  }
207
- _unregister(watch) {
208
- if (watch.subscribed) return;
262
+ #unregisterWatch(watch) {
209
263
  switch (watch.kind) {
210
264
  case "event":
211
- removeWatch(this.#idWatches, watch.key, watch);
265
+ removeFromSetMap(this.#idWatches, watch.key, watch);
266
+ if (this.#eventCache.get(watch.key) === watch) this.#eventCache.delete(watch.key);
212
267
  return;
213
268
  case "replaceable":
214
- removeWatch(this.#addressWatches, watch.key, watch);
269
+ removeFromSetMap(this.#addressWatches, watch.key, watch);
270
+ if (this.#addressCache.get(watch.key) === watch) this.#addressCache.delete(watch.key);
215
271
  return;
216
272
  case "query":
217
273
  this.#queryRegistry.delete(watch);
@@ -219,8 +275,8 @@ var ReactiveEventStore = class {
219
275
  }
220
276
  }
221
277
  #touch(id) {
222
- if (this.#recency.delete(id)) this.#recency.add(id);
223
- else this.#recency.add(id);
278
+ this.#recency.delete(id);
279
+ this.#recency.add(id);
224
280
  }
225
281
  #recordSeen(id, relayUrl) {
226
282
  const url = normalizeURL(relayUrl);
@@ -230,22 +286,19 @@ var ReactiveEventStore = class {
230
286
  this.#seenOn.set(id, urls);
231
287
  }
232
288
  if (!urls.includes(url) && urls.length < SEEN_ON_PER_ID) urls.push(url);
233
- while (this.#seenOn.size > this.#maxSeenOnEntries) {
234
- const oldest = this.#seenOn.keys().next().value;
235
- if (oldest === void 0) break;
236
- this.#seenOn.delete(oldest);
237
- }
289
+ trimOldest(this.#seenOn, this.#maxSeenOnEntries);
238
290
  }
239
291
  #onInsert(event) {
240
- this._version += 1;
292
+ this.#version += 1;
241
293
  this.#touch(event.id);
242
294
  this.#invalidateByEvent(event);
243
295
  for (const listener of this.#insertListeners) invokeSafely(() => listener(event));
244
296
  }
245
297
  #onRemove(event) {
246
- this._version += 1;
298
+ this.#version += 1;
247
299
  this.#recency.delete(event.id);
248
300
  this.#invalidateByEvent(event);
301
+ for (const listener of this.#removeListeners) invokeSafely(() => listener(event));
249
302
  }
250
303
  #invalidateByEvent(event) {
251
304
  const byId = this.#idWatches.get(event.id);
@@ -255,6 +308,17 @@ var ReactiveEventStore = class {
255
308
  const byAddress = this.#addressWatches.get(address);
256
309
  if (byAddress) for (const watch of byAddress) this.#markDirty(watch);
257
310
  }
311
+ if (event.kind === Kind.EventDeletion) {
312
+ for (const tag of event.tags) if (tag[0] === "e" && tag[1] !== void 0) {
313
+ const watches = this.#idWatches.get(tag[1].toLowerCase());
314
+ if (watches) for (const watch of watches) this.#markDirty(watch);
315
+ } else if (tag[0] === "a" && tag[1] !== void 0) {
316
+ const coord = parseEventAddress(tag[1]);
317
+ if (coord === void 0) continue;
318
+ const watches = this.#addressWatches.get(formatEventAddress(coord.kind, coord.pubkey, coord.identifier));
319
+ if (watches) for (const watch of watches) this.#markDirty(watch);
320
+ }
321
+ }
258
322
  for (const watch of this.#queryRegistry) if (watch.filters !== void 0 && matchFilters(watch.filters, event)) this.#markDirty(watch);
259
323
  }
260
324
  #invalidateAll() {
@@ -264,7 +328,7 @@ var ReactiveEventStore = class {
264
328
  this.#scheduleFlush();
265
329
  }
266
330
  #markDirty(watch) {
267
- watch._markDirty();
331
+ watch.markDirty();
268
332
  this.#dirty.add(watch);
269
333
  this.#scheduleFlush();
270
334
  }
@@ -275,28 +339,27 @@ var ReactiveEventStore = class {
275
339
  while (this.#dirty.size > 0) {
276
340
  const batch = [...this.#dirty];
277
341
  this.#dirty.clear();
278
- for (const watch of batch) watch._notify();
342
+ for (const watch of batch) watch.notify();
279
343
  }
280
344
  this.#flushScheduled = false;
281
345
  });
282
346
  }
283
- #pinnedIds() {
284
- const pinned = /* @__PURE__ */ new Set();
285
- const collect = (watch) => {
286
- for (const id of watch.pinnedIds) pinned.add(id);
287
- };
288
- for (const watches of this.#idWatches.values()) for (const watch of watches) collect(watch);
289
- for (const watches of this.#addressWatches.values()) for (const watch of watches) collect(watch);
290
- for (const watch of this.#queryRegistry) collect(watch);
291
- return pinned;
347
+ #pin(ids) {
348
+ for (const id of ids) this.#pinned.set(id, (this.#pinned.get(id) ?? 0) + 1);
349
+ }
350
+ #unpin(ids) {
351
+ for (const id of ids) {
352
+ const count = (this.#pinned.get(id) ?? 0) - 1;
353
+ if (count <= 0) this.#pinned.delete(id);
354
+ else this.#pinned.set(id, count);
355
+ }
292
356
  }
293
357
  #evictIfNeeded(protectedId) {
294
358
  if (this.#index.size <= this.#maxEvents) return;
295
- const pinned = this.#pinnedIds();
296
359
  const evicting = [];
297
360
  for (const id of this.#recency) {
298
361
  if (this.#index.size - evicting.length <= this.#maxEvents) break;
299
- if (pinned.has(id) || id === protectedId) continue;
362
+ if (this.#pinned.has(id) || id === protectedId) continue;
300
363
  if (this.#index.get(id) === void 0) continue;
301
364
  evicting.push(id);
302
365
  }
@@ -304,21 +367,7 @@ var ReactiveEventStore = class {
304
367
  for (const id of evicting) this.#recency.delete(id);
305
368
  }
306
369
  };
307
- function addWatch(map, key, watch) {
308
- let set = map.get(key);
309
- if (!set) {
310
- set = /* @__PURE__ */ new Set();
311
- map.set(key, set);
312
- }
313
- set.add(watch);
314
- }
315
- function removeWatch(map, key, watch) {
316
- const set = map.get(key);
317
- if (!set) return;
318
- set.delete(watch);
319
- if (set.size === 0) map.delete(key);
320
- }
321
370
  //#endregion
322
371
  export { ReactiveEventStore as t };
323
372
 
324
- //# sourceMappingURL=reactive-DDlThDSZ.mjs.map
373
+ //# sourceMappingURL=reactive-BJRoVHxU.mjs.map