@peerbit/shared-log 13.2.24 → 13.2.26

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 (81) hide show
  1. package/dist/src/checked-prune.d.ts +12 -0
  2. package/dist/src/checked-prune.d.ts.map +1 -1
  3. package/dist/src/checked-prune.js +33 -0
  4. package/dist/src/checked-prune.js.map +1 -1
  5. package/dist/src/coordinate-persistence.d.ts +211 -0
  6. package/dist/src/coordinate-persistence.d.ts.map +1 -0
  7. package/dist/src/coordinate-persistence.js +1133 -0
  8. package/dist/src/coordinate-persistence.js.map +1 -0
  9. package/dist/src/errors.d.ts +1 -0
  10. package/dist/src/errors.d.ts.map +1 -1
  11. package/dist/src/errors.js +19 -0
  12. package/dist/src/errors.js.map +1 -1
  13. package/dist/src/index.d.ts +127 -133
  14. package/dist/src/index.d.ts.map +1 -1
  15. package/dist/src/index.js +1400 -4269
  16. package/dist/src/index.js.map +1 -1
  17. package/dist/src/instance-lifecycle.d.ts +117 -0
  18. package/dist/src/instance-lifecycle.d.ts.map +1 -0
  19. package/dist/src/instance-lifecycle.js +254 -0
  20. package/dist/src/instance-lifecycle.js.map +1 -0
  21. package/dist/src/join-warmup.d.ts +86 -0
  22. package/dist/src/join-warmup.d.ts.map +1 -0
  23. package/dist/src/join-warmup.js +351 -0
  24. package/dist/src/join-warmup.js.map +1 -0
  25. package/dist/src/native-write-through-block-store.d.ts +123 -0
  26. package/dist/src/native-write-through-block-store.d.ts.map +1 -0
  27. package/dist/src/native-write-through-block-store.js +989 -0
  28. package/dist/src/native-write-through-block-store.js.map +1 -0
  29. package/dist/src/peer-session.d.ts +117 -0
  30. package/dist/src/peer-session.d.ts.map +1 -0
  31. package/dist/src/peer-session.js +259 -0
  32. package/dist/src/peer-session.js.map +1 -0
  33. package/dist/src/replication-announcement.d.ts +116 -0
  34. package/dist/src/replication-announcement.d.ts.map +1 -0
  35. package/dist/src/replication-announcement.js +515 -0
  36. package/dist/src/replication-announcement.js.map +1 -0
  37. package/dist/src/replicator-liveness.d.ts +59 -0
  38. package/dist/src/replicator-liveness.d.ts.map +1 -0
  39. package/dist/src/replicator-liveness.js +304 -0
  40. package/dist/src/replicator-liveness.js.map +1 -0
  41. package/dist/src/sync/dispatch-lifecycle.d.ts +48 -0
  42. package/dist/src/sync/dispatch-lifecycle.d.ts.map +1 -0
  43. package/dist/src/sync/dispatch-lifecycle.js +141 -0
  44. package/dist/src/sync/dispatch-lifecycle.js.map +1 -0
  45. package/dist/src/sync/factory.d.ts +29 -0
  46. package/dist/src/sync/factory.d.ts.map +1 -0
  47. package/dist/src/sync/factory.js +87 -0
  48. package/dist/src/sync/factory.js.map +1 -0
  49. package/dist/src/sync/pending-sync-store.d.ts +105 -0
  50. package/dist/src/sync/pending-sync-store.d.ts.map +1 -0
  51. package/dist/src/sync/pending-sync-store.js +877 -0
  52. package/dist/src/sync/pending-sync-store.js.map +1 -0
  53. package/dist/src/sync/rateless-iblt.d.ts +4 -1
  54. package/dist/src/sync/rateless-iblt.d.ts.map +1 -1
  55. package/dist/src/sync/rateless-iblt.js +102 -106
  56. package/dist/src/sync/rateless-iblt.js.map +1 -1
  57. package/dist/src/sync/simple.d.ts +10 -41
  58. package/dist/src/sync/simple.d.ts.map +1 -1
  59. package/dist/src/sync/simple.js +259 -952
  60. package/dist/src/sync/simple.js.map +1 -1
  61. package/dist/src/sync/sync-peer-state.d.ts +16 -0
  62. package/dist/src/sync/sync-peer-state.d.ts.map +1 -0
  63. package/dist/src/sync/sync-peer-state.js +82 -0
  64. package/dist/src/sync/sync-peer-state.js.map +1 -0
  65. package/package.json +12 -12
  66. package/src/checked-prune.ts +35 -0
  67. package/src/coordinate-persistence.ts +1796 -0
  68. package/src/errors.ts +21 -0
  69. package/src/index.ts +4014 -7858
  70. package/src/instance-lifecycle.ts +351 -0
  71. package/src/join-warmup.ts +507 -0
  72. package/src/native-write-through-block-store.ts +1170 -0
  73. package/src/peer-session.ts +336 -0
  74. package/src/replication-announcement.ts +744 -0
  75. package/src/replicator-liveness.ts +439 -0
  76. package/src/sync/dispatch-lifecycle.ts +230 -0
  77. package/src/sync/factory.ts +154 -0
  78. package/src/sync/pending-sync-store.ts +1085 -0
  79. package/src/sync/rateless-iblt.ts +122 -132
  80. package/src/sync/simple.ts +334 -1144
  81. package/src/sync/sync-peer-state.ts +107 -0
@@ -0,0 +1,507 @@
1
+ const JOIN_WARMUP_SEND_SPACING_MS = 250;
2
+
3
+ /**
4
+ * One WarmupSession per join-warmup attempt window for a target. The
5
+ * instance IS the legacy opaque generation token — always compared by
6
+ * identity, never inspected — now carrying diagnostics. Rotation points are
7
+ * unchanged: created on demand, dropped at cancelJoinWarmupTarget so the
8
+ * next touch mints a fresh identity. Deliberately NOT hosted on
9
+ * PeerSession: warmup windows rotate independently of subscription epochs
10
+ * (poison and committed removals leave the subscription session alone), and
11
+ * a warmup target may have no session at all — auto-creating one as a
12
+ * warmup side effect would flip `isCurrent(peer, null)` from true to false
13
+ * at every receive-admission seat.
14
+ */
15
+ export class WarmupSession {
16
+ readonly target: string;
17
+ readonly createdAt = Date.now(); // diagnostics only
18
+
19
+ constructor(target: string) {
20
+ this.target = target;
21
+ }
22
+ }
23
+
24
+ export type JoinWarmupSendState<E> = {
25
+ bypassKnownPeerHints: boolean;
26
+ entries: Map<string, E>;
27
+ // Per-TARGET send state adopts the session it is currently draining for.
28
+ // lastCompletedAt spacing survives session rotation by design; do NOT
29
+ // move send state onto the session.
30
+ session: WarmupSession;
31
+ lastCompletedAt: number;
32
+ pending: boolean;
33
+ running: boolean;
34
+ };
35
+
36
+ export type JoinWarmupRetryTimer = {
37
+ handle: ReturnType<typeof setTimeout>;
38
+ resolve?: () => void;
39
+ };
40
+
41
+ export type JoinWarmupScheduledRetryBatch<E> = {
42
+ bypassKnownPeerHints: boolean;
43
+ entries: Map<string, E>;
44
+ remainingAttempts: number;
45
+ };
46
+
47
+ export type JoinWarmupScheduledRetryCohort<E> = {
48
+ batches: JoinWarmupScheduledRetryBatch<E>[];
49
+ dueAt: number;
50
+ };
51
+
52
+ export type JoinWarmupScheduledRetrySlot<E> = {
53
+ cohorts: JoinWarmupScheduledRetryCohort<E>[];
54
+ head: number;
55
+ timer?: JoinWarmupRetryTimer;
56
+ timerDueAt?: number;
57
+ };
58
+
59
+ export type JoinWarmupScheduledRetries<E> = {
60
+ session: WarmupSession;
61
+ slotsByDelay: Map<number, JoinWarmupScheduledRetrySlot<E>>;
62
+ };
63
+
64
+ export type JoinWarmupDeps<E> = {
65
+ isLifecycleActive: (controller: AbortController) => boolean;
66
+ getCurrentLifecycleController: () => AbortController;
67
+ getRepairRetryTimers: () => Set<ReturnType<typeof setTimeout>>;
68
+ isClosed: () => boolean;
69
+ onTargetCancelled: (target: string) => void;
70
+ bumpSimpleFallbackPasses: () => void;
71
+ sendEntriesSimple: (
72
+ target: string,
73
+ entries: ReadonlyMap<string, E>,
74
+ options: {
75
+ bypassKnownPeers: boolean;
76
+ bypassRecentKnownPeers: boolean;
77
+ isStillCurrent: () => boolean;
78
+ signal: AbortSignal;
79
+ },
80
+ ) => Promise<void>;
81
+ logError: (error: any) => void;
82
+ };
83
+
84
+ export class JoinWarmupCoordinator<E> {
85
+ _warmupSessionsByTarget!: Map<string, WarmupSession>;
86
+ _joinWarmupSendStateByTarget!: Map<string, JoinWarmupSendState<E>>;
87
+ _joinWarmupRetryTimersByTarget!: Map<string, Set<JoinWarmupRetryTimer>>;
88
+ _joinWarmupScheduledRetriesByTarget!: Map<
89
+ string,
90
+ JoinWarmupScheduledRetries<E>
91
+ >;
92
+ _repairSweepWarmupSessionByTarget!: Map<string, WarmupSession>;
93
+
94
+ constructor(private readonly deps: JoinWarmupDeps<E>) {
95
+ this.reset();
96
+ }
97
+
98
+ reset() {
99
+ this._warmupSessionsByTarget = new Map();
100
+ this._joinWarmupSendStateByTarget = new Map();
101
+ this._joinWarmupRetryTimersByTarget = new Map();
102
+ this._joinWarmupScheduledRetriesByTarget = new Map();
103
+ this._repairSweepWarmupSessionByTarget = new Map();
104
+ }
105
+
106
+ ensureWarmupSession(target: string): WarmupSession {
107
+ let session = this._warmupSessionsByTarget.get(target);
108
+ if (!session) {
109
+ session = new WarmupSession(target);
110
+ this._warmupSessionsByTarget.set(target, session);
111
+ }
112
+ return session;
113
+ }
114
+
115
+
116
+ clearRepairSweepWarmupSessions(): void {
117
+ this._repairSweepWarmupSessionByTarget.clear();
118
+ }
119
+
120
+ trackJoinWarmupTimer(target: string, timer: JoinWarmupRetryTimer) {
121
+ let timers = this._joinWarmupRetryTimersByTarget.get(target);
122
+ if (!timers) {
123
+ timers = new Set();
124
+ this._joinWarmupRetryTimersByTarget.set(target, timers);
125
+ }
126
+ timers.add(timer);
127
+ this.deps.getRepairRetryTimers().add(timer.handle);
128
+ }
129
+
130
+ untrackJoinWarmupTimer(target: string, timer: JoinWarmupRetryTimer) {
131
+ this.deps.getRepairRetryTimers().delete(timer.handle);
132
+ const timers = this._joinWarmupRetryTimersByTarget.get(target);
133
+ if (!timers) {
134
+ return;
135
+ }
136
+ timers.delete(timer);
137
+ if (timers.size === 0) {
138
+ this._joinWarmupRetryTimersByTarget.delete(target);
139
+ }
140
+ }
141
+
142
+ cancelJoinWarmupTimers(target: string) {
143
+ const timers = this._joinWarmupRetryTimersByTarget.get(target);
144
+ if (!timers) {
145
+ return;
146
+ }
147
+ for (const timer of [...timers]) {
148
+ clearTimeout(timer.handle);
149
+ timer.resolve?.();
150
+ this.untrackJoinWarmupTimer(target, timer);
151
+ }
152
+ }
153
+
154
+ cancelJoinWarmupTarget(target: string) {
155
+ this._warmupSessionsByTarget.delete(target);
156
+ this.deps.onTargetCancelled(target);
157
+ this._repairSweepWarmupSessionByTarget.delete(target);
158
+ this.cancelJoinWarmupTimers(target);
159
+ this._joinWarmupScheduledRetriesByTarget.delete(target);
160
+ const state = this._joinWarmupSendStateByTarget.get(target);
161
+ if (!state) {
162
+ return;
163
+ }
164
+ state.bypassKnownPeerHints = false;
165
+ state.entries.clear();
166
+ state.pending = false;
167
+ if (!state.running) {
168
+ this._joinWarmupSendStateByTarget.delete(target);
169
+ }
170
+ }
171
+
172
+ cancelAllJoinWarmupTargets() {
173
+ const targets = new Set([
174
+ ...this._warmupSessionsByTarget.keys(),
175
+ ...this._joinWarmupRetryTimersByTarget.keys(),
176
+ ...this._joinWarmupScheduledRetriesByTarget.keys(),
177
+ ...this._joinWarmupSendStateByTarget.keys(),
178
+ ]);
179
+ for (const target of targets) {
180
+ this.cancelJoinWarmupTarget(target);
181
+ }
182
+ }
183
+
184
+ async sleepJoinWarmupTracked(
185
+ target: string,
186
+ delayMs: number,
187
+ repairLifecycleController: AbortController,
188
+ ) {
189
+ if (delayMs <= 0) {
190
+ return;
191
+ }
192
+ await new Promise<void>((resolve) => {
193
+ let settled = false;
194
+ let trackedTimer!: JoinWarmupRetryTimer;
195
+ const settle = () => {
196
+ if (settled) {
197
+ return;
198
+ }
199
+ settled = true;
200
+ if (
201
+ repairLifecycleController ===
202
+ this.deps.getCurrentLifecycleController()
203
+ ) {
204
+ this.untrackJoinWarmupTimer(target, trackedTimer);
205
+ }
206
+ resolve();
207
+ };
208
+ const handle = setTimeout(settle, delayMs);
209
+ handle.unref?.();
210
+ trackedTimer = { handle, resolve: settle };
211
+ this.trackJoinWarmupTimer(target, trackedTimer);
212
+ });
213
+ }
214
+
215
+ scheduleJoinWarmupRetries(
216
+ target: string,
217
+ session: WarmupSession,
218
+ delaysMs: Iterable<number>,
219
+ entries: ReadonlyMap<string, E>,
220
+ bypassKnownPeerHints: boolean,
221
+ repairLifecycleController: AbortController = this.deps.getCurrentLifecycleController(),
222
+ ) {
223
+ if (
224
+ !this.deps.isLifecycleActive(repairLifecycleController) ||
225
+ this._warmupSessionsByTarget.get(target) !== session
226
+ ) {
227
+ return;
228
+ }
229
+ let scheduled = this._joinWarmupScheduledRetriesByTarget.get(target);
230
+ if (scheduled?.session !== session) {
231
+ this.cancelJoinWarmupTimers(target);
232
+ this._joinWarmupScheduledRetriesByTarget.delete(target);
233
+ scheduled = undefined;
234
+ }
235
+ if (!scheduled) {
236
+ scheduled = {
237
+ session,
238
+ slotsByDelay: new Map(),
239
+ };
240
+ this._joinWarmupScheduledRetriesByTarget.set(target, scheduled);
241
+ }
242
+ const delays = [...new Set(delaysMs)];
243
+ const batch: JoinWarmupScheduledRetryBatch<E> = {
244
+ bypassKnownPeerHints,
245
+ entries: new Map(entries),
246
+ remainingAttempts: delays.length,
247
+ };
248
+ const now = Date.now();
249
+ for (const delayMs of delays) {
250
+ let slot = scheduled.slotsByDelay.get(delayMs);
251
+ if (!slot) {
252
+ slot = { cohorts: [], head: 0 };
253
+ scheduled.slotsByDelay.set(delayMs, slot);
254
+ }
255
+ const tail = slot.cohorts.at(-1);
256
+ const dueAt = Math.max(tail?.dueAt ?? 0, now + delayMs);
257
+ if (tail?.dueAt === dueAt) {
258
+ tail.batches.push(batch);
259
+ } else {
260
+ slot.cohorts.push({
261
+ batches: [batch],
262
+ dueAt,
263
+ });
264
+ }
265
+ this.armJoinWarmupRetrySlot(
266
+ target,
267
+ scheduled,
268
+ delayMs,
269
+ slot,
270
+ repairLifecycleController,
271
+ );
272
+ }
273
+ }
274
+
275
+ armJoinWarmupRetrySlot(
276
+ target: string,
277
+ scheduled: JoinWarmupScheduledRetries<E>,
278
+ delayMs: number,
279
+ slot: JoinWarmupScheduledRetrySlot<E>,
280
+ repairLifecycleController: AbortController,
281
+ ) {
282
+ if (!this.deps.isLifecycleActive(repairLifecycleController)) {
283
+ return;
284
+ }
285
+ const nextDueAt = slot.cohorts[slot.head]?.dueAt;
286
+ if (nextDueAt == null) {
287
+ return;
288
+ }
289
+ if (slot.timer && slot.timerDueAt === nextDueAt) {
290
+ return;
291
+ }
292
+ if (slot.timer) {
293
+ clearTimeout(slot.timer.handle);
294
+ this.untrackJoinWarmupTimer(target, slot.timer);
295
+ }
296
+ let trackedTimer!: JoinWarmupRetryTimer;
297
+ const handle = setTimeout(
298
+ () => {
299
+ if (!this.deps.isLifecycleActive(repairLifecycleController)) {
300
+ return;
301
+ }
302
+ this.untrackJoinWarmupTimer(target, trackedTimer);
303
+ if (slot.timer !== trackedTimer) {
304
+ return;
305
+ }
306
+ slot.timer = undefined;
307
+ slot.timerDueAt = undefined;
308
+ const current = this._joinWarmupScheduledRetriesByTarget.get(target);
309
+ if (
310
+ current !== scheduled ||
311
+ current.slotsByDelay.get(delayMs) !== slot
312
+ ) {
313
+ return;
314
+ }
315
+
316
+ const dueEntries = new Map<string, E>();
317
+ let bypassKnownPeerHints = false;
318
+ const now = Date.now();
319
+ while (
320
+ slot.head < slot.cohorts.length &&
321
+ slot.cohorts[slot.head].dueAt <= now
322
+ ) {
323
+ const cohort = slot.cohorts[slot.head++];
324
+ for (const batch of cohort.batches) {
325
+ for (const [hash, entry] of batch.entries) {
326
+ dueEntries.set(hash, entry);
327
+ }
328
+ bypassKnownPeerHints ||= batch.bypassKnownPeerHints;
329
+ batch.remainingAttempts -= 1;
330
+ if (batch.remainingAttempts === 0) {
331
+ batch.entries.clear();
332
+ }
333
+ }
334
+ cohort.batches.length = 0;
335
+ }
336
+ if (
337
+ dueEntries.size > 0 &&
338
+ !this.deps.isClosed() &&
339
+ this._warmupSessionsByTarget.get(target) === scheduled.session
340
+ ) {
341
+ this.queueJoinWarmupSend(
342
+ target,
343
+ scheduled.session,
344
+ dueEntries,
345
+ bypassKnownPeerHints,
346
+ repairLifecycleController,
347
+ );
348
+ }
349
+ if (slot.head === slot.cohorts.length) {
350
+ current.slotsByDelay.delete(delayMs);
351
+ if (current.slotsByDelay.size === 0) {
352
+ this._joinWarmupScheduledRetriesByTarget.delete(target);
353
+ }
354
+ return;
355
+ }
356
+ if (slot.head >= 1_024 && slot.head * 2 >= slot.cohorts.length) {
357
+ slot.cohorts = slot.cohorts.slice(slot.head);
358
+ slot.head = 0;
359
+ }
360
+ this.armJoinWarmupRetrySlot(
361
+ target,
362
+ current,
363
+ delayMs,
364
+ slot,
365
+ repairLifecycleController,
366
+ );
367
+ },
368
+ Math.max(0, nextDueAt - Date.now()),
369
+ );
370
+ handle.unref?.();
371
+ trackedTimer = { handle };
372
+ slot.timer = trackedTimer;
373
+ slot.timerDueAt = nextDueAt;
374
+ this.trackJoinWarmupTimer(target, trackedTimer);
375
+ }
376
+
377
+ queueJoinWarmupSend(
378
+ target: string,
379
+ session: WarmupSession,
380
+ entries: ReadonlyMap<string, E>,
381
+ bypassKnownPeerHints: boolean,
382
+ repairLifecycleController: AbortController = this.deps.getCurrentLifecycleController(),
383
+ ) {
384
+ if (
385
+ !this.deps.isLifecycleActive(repairLifecycleController) ||
386
+ this._warmupSessionsByTarget.get(target) !== session
387
+ ) {
388
+ return;
389
+ }
390
+ let state = this._joinWarmupSendStateByTarget.get(target);
391
+ if (!state) {
392
+ state = {
393
+ bypassKnownPeerHints: false,
394
+ entries: new Map(),
395
+ session,
396
+ lastCompletedAt: Number.NEGATIVE_INFINITY,
397
+ pending: false,
398
+ running: false,
399
+ };
400
+ this._joinWarmupSendStateByTarget.set(target, state);
401
+ } else if (state.session !== session) {
402
+ state.bypassKnownPeerHints = false;
403
+ state.entries.clear();
404
+ state.pending = false;
405
+ }
406
+ for (const [hash, entry] of entries) {
407
+ state.entries.set(hash, entry);
408
+ }
409
+ state.bypassKnownPeerHints ||= bypassKnownPeerHints;
410
+ state.session = session;
411
+ state.pending = true;
412
+ if (state.running) {
413
+ return;
414
+ }
415
+ void this.drainJoinWarmupSends(
416
+ target,
417
+ state,
418
+ repairLifecycleController,
419
+ ).catch((error: any) => {
420
+ if (this.deps.isLifecycleActive(repairLifecycleController)) {
421
+ this.deps.logError(error);
422
+ }
423
+ });
424
+ }
425
+
426
+ async drainJoinWarmupSends(
427
+ target: string,
428
+ state: JoinWarmupSendState<E>,
429
+ repairLifecycleController: AbortController,
430
+ ) {
431
+ if (
432
+ state.running ||
433
+ !this.deps.isLifecycleActive(repairLifecycleController)
434
+ ) {
435
+ return;
436
+ }
437
+ state.running = true;
438
+ try {
439
+ while (state.pending) {
440
+ if (!this.deps.isLifecycleActive(repairLifecycleController)) {
441
+ return;
442
+ }
443
+ state.pending = false;
444
+ const session = state.session;
445
+ const entries = new Map(state.entries);
446
+ state.entries.clear();
447
+ const bypassKnownPeerHints = state.bypassKnownPeerHints;
448
+ state.bypassKnownPeerHints = false;
449
+ const spacingMs = Math.max(
450
+ 0,
451
+ state.lastCompletedAt + JOIN_WARMUP_SEND_SPACING_MS - Date.now(),
452
+ );
453
+ await this.sleepJoinWarmupTracked(
454
+ target,
455
+ spacingMs,
456
+ repairLifecycleController,
457
+ );
458
+ if (
459
+ !this.deps.isLifecycleActive(repairLifecycleController) ||
460
+ state.session !== session ||
461
+ this._warmupSessionsByTarget.get(target) !== session
462
+ ) {
463
+ continue;
464
+ }
465
+ if (entries.size === 0) {
466
+ continue;
467
+ }
468
+ this.deps.bumpSimpleFallbackPasses();
469
+ try {
470
+ await this.deps.sendEntriesSimple(target, entries, {
471
+ bypassKnownPeers: bypassKnownPeerHints,
472
+ bypassRecentKnownPeers: bypassKnownPeerHints,
473
+ isStillCurrent: () =>
474
+ this.deps.isLifecycleActive(repairLifecycleController),
475
+ signal: repairLifecycleController.signal,
476
+ });
477
+ } catch (error: any) {
478
+ if (this.deps.isLifecycleActive(repairLifecycleController)) {
479
+ this.deps.logError(error);
480
+ }
481
+ } finally {
482
+ if (this.deps.isLifecycleActive(repairLifecycleController)) {
483
+ state.lastCompletedAt = Date.now();
484
+ }
485
+ }
486
+ }
487
+ } finally {
488
+ state.running = false;
489
+ if (this._joinWarmupSendStateByTarget.get(target) === state) {
490
+ const currentRepairLifecycleController =
491
+ this.deps.getCurrentLifecycleController();
492
+ if (
493
+ state.pending &&
494
+ this.deps.isLifecycleActive(currentRepairLifecycleController)
495
+ ) {
496
+ void this.drainJoinWarmupSends(
497
+ target,
498
+ state,
499
+ currentRepairLifecycleController,
500
+ ).catch((error: any) => this.deps.logError(error));
501
+ } else if (!this._warmupSessionsByTarget.has(target)) {
502
+ this._joinWarmupSendStateByTarget.delete(target);
503
+ }
504
+ }
505
+ }
506
+ }
507
+ }