applesauce-core 6.0.2 → 6.0.3

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.
@@ -137,14 +137,24 @@ export class AsyncEventStore extends EventModels {
137
137
  addSeenRelay(event, fromRelay);
138
138
  // Get the replaceable identifier
139
139
  const identifier = isReplaceable(event.kind) ? getReplaceableIdentifier(event) : undefined;
140
- // Don't insert the event if there is already a newer version
140
+ // Don't insert the event if there is already a winning version
141
+ // (NIP-01: newer created_at wins; on tie, lexicographically lower id wins).
141
142
  if (this.keepOldVersions === false && isReplaceable(event.kind)) {
142
143
  const existing = await this.database.getReplaceableHistory(event.kind, event.pubkey, identifier);
143
- // If there is already a newer version, copy cached symbols and return existing event
144
- if (existing && existing.length > 0 && existing[0].created_at >= event.created_at) {
145
- if (EventStore.copySymbolsToDuplicateEvent(event, existing[0]))
146
- await this.update(existing[0]);
147
- return existing[0];
144
+ // If the existing set has any event that beats the incoming one per
145
+ // NIP-01, the incoming event is rejected.
146
+ if (existing && existing.length > 0) {
147
+ let winner = existing[0];
148
+ for (const e of existing) {
149
+ if (e.created_at > winner.created_at || (e.created_at === winner.created_at && e.id < winner.id))
150
+ winner = e;
151
+ }
152
+ const incomingBeatsWinner = event.created_at > winner.created_at || (event.created_at === winner.created_at && event.id < winner.id);
153
+ if (!incomingBeatsWinner) {
154
+ if (EventStore.copySymbolsToDuplicateEvent(event, winner))
155
+ await this.update(winner);
156
+ return winner;
157
+ }
148
158
  }
149
159
  }
150
160
  // Verify event before inserting into the database
@@ -173,17 +183,24 @@ export class AsyncEventStore extends EventModels {
173
183
  if (EventStore.copySymbolsToDuplicateEvent(event, inserted))
174
184
  await this.update(inserted);
175
185
  }
176
- // remove all old version of the replaceable event
186
+ // remove all losing versions of the replaceable event
187
+ // (NIP-01: keep newest created_at; on tie, keep lowest id).
177
188
  if (this.keepOldVersions === false && isReplaceable(event.kind)) {
178
189
  const existing = await this.database.getReplaceableHistory(event.kind, event.pubkey, identifier);
179
190
  if (existing && existing.length > 0) {
180
- const older = Array.from(existing).filter((e) => e.created_at < event.created_at);
181
- for (const old of older)
191
+ // Find the NIP-01 winner across all stored versions.
192
+ let winner = existing[0];
193
+ for (const e of existing) {
194
+ if (e.created_at > winner.created_at || (e.created_at === winner.created_at && e.id < winner.id))
195
+ winner = e;
196
+ }
197
+ const losers = existing.filter((e) => e !== winner);
198
+ for (const old of losers)
182
199
  await this.remove(old);
183
- // return the newest version of the replaceable event
200
+ // return the winning version of the replaceable event
184
201
  // most of the time this will be === event, but not always
185
- if (existing.length !== older.length)
186
- return existing[0];
202
+ if (losers.length > 0)
203
+ return winner;
187
204
  }
188
205
  }
189
206
  // Add event to expiration manager if it has an expiration tag
@@ -161,14 +161,24 @@ export class EventStore extends EventModels {
161
161
  addSeenRelay(event, fromRelay);
162
162
  // Get the replaceable identifier
163
163
  const identifier = isReplaceable(event.kind) ? getReplaceableIdentifier(event) : undefined;
164
- // Don't insert the event if there is already a newer version
164
+ // Don't insert the event if there is already a winning version
165
+ // (NIP-01: newer created_at wins; on tie, lexicographically lower id wins).
165
166
  if (this.keepOldVersions === false && isReplaceable(event.kind)) {
166
167
  const existing = this.database.getReplaceableHistory(event.kind, event.pubkey, identifier);
167
- // If there is already a newer version, copy cached symbols and return existing event
168
- if (existing && existing.length > 0 && existing[0].created_at >= event.created_at) {
169
- if (EventStore.copySymbolsToDuplicateEvent(event, existing[0]))
170
- this.update(existing[0]);
171
- return existing[0];
168
+ // If the existing set has any event that beats the incoming one per
169
+ // NIP-01, the incoming event is rejected.
170
+ if (existing && existing.length > 0) {
171
+ let winner = existing[0];
172
+ for (const e of existing) {
173
+ if (e.created_at > winner.created_at || (e.created_at === winner.created_at && e.id < winner.id))
174
+ winner = e;
175
+ }
176
+ const incomingBeatsWinner = event.created_at > winner.created_at || (event.created_at === winner.created_at && event.id < winner.id);
177
+ if (!incomingBeatsWinner) {
178
+ if (EventStore.copySymbolsToDuplicateEvent(event, winner))
179
+ this.update(winner);
180
+ return winner;
181
+ }
172
182
  }
173
183
  }
174
184
  // Verify event before inserting into the database
@@ -197,17 +207,24 @@ export class EventStore extends EventModels {
197
207
  if (EventStore.copySymbolsToDuplicateEvent(event, inserted))
198
208
  this.update(inserted);
199
209
  }
200
- // remove all old version of the replaceable event
210
+ // remove all losing versions of the replaceable event
211
+ // (NIP-01: keep newest created_at; on tie, keep lowest id).
201
212
  if (this.keepOldVersions === false && isReplaceable(event.kind)) {
202
213
  const existing = this.database.getReplaceableHistory(event.kind, event.pubkey, identifier);
203
214
  if (existing && existing.length > 0) {
204
- const older = Array.from(existing).filter((e) => e.created_at < event.created_at);
205
- for (const old of older)
215
+ // Find the NIP-01 winner across all stored versions.
216
+ let winner = existing[0];
217
+ for (const e of existing) {
218
+ if (e.created_at > winner.created_at || (e.created_at === winner.created_at && e.id < winner.id))
219
+ winner = e;
220
+ }
221
+ const losers = existing.filter((e) => e !== winner);
222
+ for (const old of losers)
206
223
  this.remove(old);
207
- // return the newest version of the replaceable event
224
+ // return the winning version of the replaceable event
208
225
  // most of the time this will be === event, but not always
209
- if (existing.length !== older.length)
210
- return existing[0];
226
+ if (losers.length > 0)
227
+ return winner;
211
228
  }
212
229
  }
213
230
  // Add event to expiration manager if it has an expiration tag
@@ -34,10 +34,10 @@ export const Tokens = {
34
34
  return Expressions.link;
35
35
  },
36
36
  get cashu() {
37
- return new RegExp(`(?<=^|\\s)${Expressions.cashu.source}`, "gi");
37
+ return new RegExp(`(?<=^|\\s|\\s\\()${Expressions.cashu.source}`, "gi");
38
38
  },
39
39
  get nostrLink() {
40
- return new RegExp(`(?<=^|\\s)${Expressions.nostrLink.source}`, "gi");
40
+ return new RegExp(`(?<=^|\\s|\\s\\()${Expressions.nostrLink.source}`, "gi");
41
41
  },
42
42
  get emoji() {
43
43
  return Expressions.emoji;
@@ -46,9 +46,9 @@ export const Tokens = {
46
46
  return Expressions.hashtag;
47
47
  },
48
48
  get lightning() {
49
- return new RegExp(`(?<=^|\\s)${Expressions.lightning.source}`, "gim");
49
+ return new RegExp(`(?<=^|\\s|\\s\\()${Expressions.lightning.source}`, "gim");
50
50
  },
51
51
  get blossom() {
52
- return new RegExp(`(?<=^|\\s)${Expressions.blossom.source}`, "g");
52
+ return new RegExp(`(?<=^|\\s|\\s\\()${Expressions.blossom.source}`, "g");
53
53
  },
54
54
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "applesauce-core",
3
- "version": "6.0.2",
3
+ "version": "6.0.3",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",