atom.io 0.40.10 → 0.41.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,13 @@
1
- import type { Fn, Transceiver, TransceiverMode } from "atom.io/internal"
2
- import { Subject } from "atom.io/internal"
1
+ import type {
2
+ Enumeration,
3
+ Fn,
4
+ Transceiver,
5
+ TransceiverMode,
6
+ } from "atom.io/internal"
7
+ import { enumeration, packValue, Subject, unpackValue } from "atom.io/internal"
3
8
  import type { primitive } from "atom.io/json"
4
9
 
5
- type ArrayMutations = Exclude<keyof Array<any>, keyof ReadonlyArray<any>>
10
+ export type ArrayMutations = Exclude<keyof Array<any>, keyof ReadonlyArray<any>>
6
11
  export type ArrayUpdate<P extends primitive> =
7
12
  | {
8
13
  type: `copyWithin`
@@ -48,9 +53,9 @@ export type ArrayUpdate<P extends primitive> =
48
53
  | {
49
54
  type: `splice`
50
55
  start: number
51
- deleteCount?: number
52
- items?: readonly P[]
53
- deleted?: readonly P[]
56
+ deleteCount: number
57
+ items: readonly P[]
58
+ deleted: readonly P[]
54
59
  }
55
60
  | {
56
61
  type: `truncate`
@@ -62,6 +67,34 @@ true satisfies ArrayMutations extends OListUpdateType
62
67
  ? true
63
68
  : Exclude<ArrayMutations, OListUpdateType>
64
69
 
70
+ export type PackedArrayUpdate<P extends primitive> = string & {
71
+ update?: ArrayUpdate<P>
72
+ type?: P
73
+ }
74
+
75
+ const ARRAY_UPDATES = [
76
+ // virtual methods
77
+ `set`,
78
+ `truncate`,
79
+ `extend`,
80
+ // actual methods
81
+ `pop`,
82
+ `push`,
83
+ `shift`,
84
+ `unshift`,
85
+ `copyWithin`,
86
+ `fill`,
87
+ `splice`,
88
+ `reverse`,
89
+ `sort`,
90
+ ] as const
91
+ true satisfies ArrayUpdate<any>[`type`] extends (typeof ARRAY_UPDATES)[number]
92
+ ? true
93
+ : Exclude<ArrayUpdate<any>[`type`], (typeof ARRAY_UPDATES)[number]>
94
+
95
+ export const ARRAY_UPDATE_ENUM: Enumeration<typeof ARRAY_UPDATES> =
96
+ enumeration(ARRAY_UPDATES)
97
+
65
98
  export type ArrayMutationHandler = {
66
99
  [K in Exclude<OListUpdateType, `extend` | `set` | `truncate`>]: Fn
67
100
  }
@@ -69,13 +102,11 @@ export type ArrayMutationHandler = {
69
102
  export class OList<P extends primitive>
70
103
  extends Array<P>
71
104
  implements
72
- Transceiver<ReadonlyArray<P>, ArrayUpdate<P>, ReadonlyArray<P>>,
105
+ Transceiver<ReadonlyArray<P>, PackedArrayUpdate<P>, ReadonlyArray<P>>,
73
106
  ArrayMutationHandler
74
107
  {
75
108
  public mode: TransceiverMode = `record`
76
- public readonly subject: Subject<ArrayUpdate<P>> = new Subject<
77
- ArrayUpdate<P>
78
- >()
109
+ public readonly subject: Subject<PackedArrayUpdate<P>> = new Subject()
79
110
 
80
111
  public readonly READONLY_VIEW: ReadonlyArray<P> = this
81
112
 
@@ -198,6 +229,27 @@ export class OList<P extends primitive>
198
229
  return this
199
230
  }
200
231
 
232
+ public fill(value: P, start?: number, end?: number): this {
233
+ if (this.mode === `record`) {
234
+ this.mode = `playback`
235
+ const prev = this.slice(start, end)
236
+ super.fill(value, start, end)
237
+ if (start === undefined) {
238
+ this.emit({ type: `fill`, value, prev })
239
+ } else {
240
+ if (end === undefined) {
241
+ this.emit({ type: `fill`, value, start, prev })
242
+ } else {
243
+ this.emit({ type: `fill`, value, start, end, prev })
244
+ }
245
+ }
246
+ this.mode = `record`
247
+ } else {
248
+ super.fill(value, start, end)
249
+ }
250
+ return this
251
+ }
252
+
201
253
  public sort(compareFn?: (a: P, b: P) => number): this {
202
254
  if (this.mode === `record`) {
203
255
  this.mode = `playback`
@@ -256,13 +308,13 @@ export class OList<P extends primitive>
256
308
 
257
309
  public subscribe(
258
310
  key: string,
259
- fn: (update: ArrayUpdate<P>) => void,
311
+ fn: (update: PackedArrayUpdate<P>) => void,
260
312
  ): () => void {
261
313
  return this.subject.subscribe(key, fn)
262
314
  }
263
315
 
264
316
  public emit(update: ArrayUpdate<P>): void {
265
- this.subject.next(update)
317
+ this.subject.next(OList.packUpdate(update))
266
318
  }
267
319
 
268
320
  private doStep(update: ArrayUpdate<P>): void {
@@ -296,11 +348,7 @@ export class OList<P extends primitive>
296
348
  this.length = update.next.length
297
349
  break
298
350
  case `splice`:
299
- if (update.deleteCount !== undefined && update.items) {
300
- this.splice(update.start, update.deleteCount, ...update.items)
301
- } else {
302
- this.splice(update.start)
303
- }
351
+ this.splice(update.start, update.deleteCount, ...update.items)
304
352
  break
305
353
  case `truncate`:
306
354
  this.length = update.length
@@ -314,9 +362,10 @@ export class OList<P extends primitive>
314
362
  }
315
363
  }
316
364
 
317
- public do(update: ArrayUpdate<P>): null {
365
+ public do(update: PackedArrayUpdate<P>): null {
318
366
  this.mode = `playback`
319
- this.doStep(update)
367
+ const unpacked = OList.unpackUpdate(update)
368
+ this.doStep(unpacked)
320
369
  this.mode = `record`
321
370
  return null
322
371
  }
@@ -378,15 +427,7 @@ export class OList<P extends primitive>
378
427
  }
379
428
  break
380
429
  case `splice`:
381
- if (update.deleted) {
382
- if (update.items) {
383
- this.splice(update.start, update.items.length, ...update.deleted)
384
- } else {
385
- this.splice(update.start, 0, ...update.deleted)
386
- }
387
- } else if (update.items) {
388
- this.splice(update.start, update.items.length)
389
- }
430
+ this.splice(update.start, update.items.length, ...update.deleted)
390
431
  break
391
432
  case `truncate`:
392
433
  this.push(...update.items)
@@ -403,10 +444,170 @@ export class OList<P extends primitive>
403
444
  }
404
445
  }
405
446
 
406
- public undo(update: ArrayUpdate<P>): number | null {
447
+ public undo(update: PackedArrayUpdate<P>): number | null {
407
448
  this.mode = `playback`
408
- this.undoStep(update)
449
+ const unpacked = OList.unpackUpdate(update)
450
+ this.undoStep(unpacked)
409
451
  this.mode = `record`
410
452
  return null
411
453
  }
454
+
455
+ public static packUpdate<P extends primitive>(
456
+ update: ArrayUpdate<P>,
457
+ ): PackedArrayUpdate<P> {
458
+ let packed = ARRAY_UPDATE_ENUM[update.type] + `\u001F`
459
+ switch (update.type) {
460
+ case `set`:
461
+ packed += update.index + `\u001E` + packValue(update.next)
462
+ if (update.prev !== undefined) {
463
+ packed += `\u001E` + packValue(update.prev)
464
+ }
465
+ return packed
466
+ case `truncate`:
467
+ return (
468
+ packed +
469
+ update.length +
470
+ `\u001E` +
471
+ update.items.map(packValue).join(`\u001E`)
472
+ )
473
+ case `extend`:
474
+ return packed + update.next + `\u001E` + update.prev
475
+ case `pop`:
476
+ case `shift`:
477
+ if (update.value !== undefined) {
478
+ packed += packValue(update.value)
479
+ }
480
+ return packed
481
+ case `push`:
482
+ case `unshift`:
483
+ return packed + update.items.map(packValue).join(`\u001E`)
484
+ case `copyWithin`:
485
+ packed += update.target + `\u001E` + update.start
486
+ if (update.end !== undefined) {
487
+ packed += `\u001E` + update.end
488
+ }
489
+ packed += `\u001E\u001E` + update.prev.map(packValue).join(`\u001E`)
490
+ return packed
491
+ case `fill`:
492
+ packed += packValue(update.value)
493
+ if (update.start !== undefined) {
494
+ packed += `\u001E` + update.start
495
+ }
496
+ if (update.end !== undefined) {
497
+ packed += `\u001E` + update.end
498
+ }
499
+ packed += `\u001E\u001E` + update.prev.map(packValue).join(`\u001E`)
500
+ return packed
501
+ case `splice`:
502
+ return (
503
+ packed +
504
+ update.start +
505
+ `\u001E\u001E` +
506
+ update.deleteCount +
507
+ `\u001E\u001E` +
508
+ update.items.map(packValue).join(`\u001E`) +
509
+ `\u001E\u001E` +
510
+ update.deleted.map(packValue).join(`\u001E`)
511
+ )
512
+ case `reverse`:
513
+ return packed
514
+ case `sort`:
515
+ return (
516
+ packed +
517
+ update.next.map(packValue).join(`\u001E`) +
518
+ `\u001E\u001E` +
519
+ update.prev.map(packValue).join(`\u001E`)
520
+ )
521
+ }
522
+ }
523
+
524
+ public static unpackUpdate<P extends primitive>(
525
+ packed: PackedArrayUpdate<P>,
526
+ ): ArrayUpdate<P> {
527
+ const [head, tail] = packed.split(`\u001F`) as [
528
+ Extract<keyof typeof ARRAY_UPDATE_ENUM, number>,
529
+ string,
530
+ ]
531
+ const type = ARRAY_UPDATE_ENUM[head]
532
+ switch (type) {
533
+ case `set`: {
534
+ const [i, n, p] = tail.split(`\u001E`)
535
+ const index = +i
536
+ const next = unpackValue(n) as P
537
+ if (p === undefined) {
538
+ return { type, index, next }
539
+ }
540
+ const prev = unpackValue(p) as P
541
+ return { type, index, next, prev }
542
+ }
543
+ case `truncate`: {
544
+ const [l, ...i] = tail.split(`\u001E`)
545
+ const length = +l
546
+ const items = i.map(unpackValue) as P[]
547
+ return { type, length, items }
548
+ }
549
+ case `extend`: {
550
+ const [n, p] = tail.split(`\u001E`)
551
+ const next = +n
552
+ const prev = +p
553
+ return { type, next, prev }
554
+ }
555
+ case `pop`:
556
+ case `shift`:
557
+ if (tail !== ``) {
558
+ const value = unpackValue(tail) as P
559
+ return { type, value }
560
+ }
561
+ return { type }
562
+ case `push`:
563
+ case `unshift`: {
564
+ const items = tail.split(`\u001E`).map(unpackValue) as P[]
565
+ return { type, items }
566
+ }
567
+ case `copyWithin`: {
568
+ const [numbers, data] = tail.split(`\u001E\u001E`)
569
+ const prev = data ? (data.split(`\u001E`).map(unpackValue) as P[]) : []
570
+ const [t, s, e] = numbers.split(`\u001E`)
571
+ const target = +t
572
+ const start = +s
573
+ if (e === undefined) {
574
+ return { type, target, start, prev }
575
+ }
576
+ const end = +e
577
+ return { type, target, start, prev, end }
578
+ }
579
+ case `fill`: {
580
+ const [numbers, data] = tail.split(`\u001E\u001E`)
581
+ const prev = data ? (data.split(`\u001E`).map(unpackValue) as P[]) : []
582
+ const [v, s, e] = numbers.split(`\u001E`)
583
+ const value = unpackValue(v) as P
584
+ if (s === undefined && e === undefined) {
585
+ return { type, value, prev }
586
+ }
587
+ const start = +s
588
+ if (e === undefined) {
589
+ return { type, value, prev, start }
590
+ }
591
+ const end = +e
592
+ return { type, value, prev, start, end }
593
+ }
594
+ case `splice`: {
595
+ const [s, c, i, d] = tail.split(`\u001E\u001E`)
596
+
597
+ const start = +s
598
+ const deleteCount = +c
599
+ const items = i ? (i.split(`\u001E`).map(unpackValue) as P[]) : []
600
+ const deleted = d ? (d.split(`\u001E`).map(unpackValue) as P[]) : []
601
+ return { type, start, deleteCount, items, deleted }
602
+ }
603
+ case `reverse`:
604
+ return { type }
605
+ case `sort`: {
606
+ const [n, p] = tail.split(`\u001E\u001E`)
607
+ const next = n ? (n.split(`\u001E`).map(unpackValue) as P[]) : []
608
+ const prev = p ? (p.split(`\u001E`).map(unpackValue) as P[]) : []
609
+ return { type, next, prev }
610
+ }
611
+ }
612
+ }
412
613
  }
@@ -4,11 +4,11 @@ import type { Json, primitive } from "atom.io/json"
4
4
  import { stringifyJson } from "atom.io/json"
5
5
 
6
6
  export type SetUpdateType = `add` | `clear` | `del` | `tx`
7
- export type SetUpdate = `${SetUpdateType}:${string}`
8
- export type NumberedSetUpdate = `${number | `*`}=${SetUpdate}`
7
+ export type SetUpdateString = `${SetUpdateType}:${string}`
8
+ export type NumberedSetUpdateString = `${number | `*`}=${SetUpdateString}`
9
9
 
10
10
  export interface SetRTXView<P extends primitive> extends ReadonlySet<P> {
11
- readonly cache: ReadonlyArray<NumberedSetUpdate | null>
11
+ readonly cache: ReadonlyArray<NumberedSetUpdateString | null>
12
12
  readonly cacheLimit: number
13
13
  readonly cacheIdx: number
14
14
  readonly cacheUpdateNumber: number
@@ -16,7 +16,7 @@ export interface SetRTXView<P extends primitive> extends ReadonlySet<P> {
16
16
 
17
17
  export interface SetRTXJson<P extends primitive> extends Json.Object {
18
18
  members: P[]
19
- cache: (NumberedSetUpdate | null)[]
19
+ cache: (NumberedSetUpdateString | null)[]
20
20
  cacheLimit: number
21
21
  cacheIdx: number
22
22
  cacheUpdateNumber: number
@@ -24,13 +24,14 @@ export interface SetRTXJson<P extends primitive> extends Json.Object {
24
24
  export class SetRTX<P extends primitive>
25
25
  extends Set<P>
26
26
  implements
27
- Transceiver<SetRTXView<P>, NumberedSetUpdate, SetRTXJson<P>>,
27
+ Transceiver<SetRTXView<P>, NumberedSetUpdateString, SetRTXJson<P>>,
28
28
  Lineage
29
29
  {
30
30
  public mode: TransceiverMode = `record`
31
- public readonly subject: Subject<SetUpdate> = new Subject<SetUpdate>()
31
+ public readonly subject: Subject<SetUpdateString> =
32
+ new Subject<SetUpdateString>()
32
33
  public cacheLimit = 0
33
- public cache: (NumberedSetUpdate | null)[] = []
34
+ public cache: (NumberedSetUpdateString | null)[] = []
34
35
  public cacheIdx = -1
35
36
  public cacheUpdateNumber = -1
36
37
 
@@ -100,7 +101,7 @@ export class SetRTX<P extends primitive>
100
101
 
101
102
  public readonly parent: SetRTX<P> | null = null
102
103
  public child: SetRTX<P> | null = null
103
- public transactionUpdates: SetUpdate[] | null = null
104
+ public transactionUpdates: SetUpdateString[] | null = null
104
105
  public transaction(run: (child: SetRTX<P>) => boolean): void {
105
106
  this.mode = `transaction`
106
107
  this.transactionUpdates = []
@@ -134,24 +135,24 @@ export class SetRTX<P extends primitive>
134
135
 
135
136
  protected _subscribe(
136
137
  key: string,
137
- fn: (update: SetUpdate) => void,
138
+ fn: (update: SetUpdateString) => void,
138
139
  ): () => void {
139
140
  return this.subject.subscribe(key, fn)
140
141
  }
141
142
  public subscribe(
142
143
  key: string,
143
- fn: (update: NumberedSetUpdate) => void,
144
+ fn: (update: NumberedSetUpdateString) => void,
144
145
  ): () => void {
145
146
  return this.subject.subscribe(key, (update) => {
146
147
  fn(`${this.cacheUpdateNumber}=${update}`)
147
148
  })
148
149
  }
149
150
 
150
- public emit(update: SetUpdate): void {
151
+ public emit(update: SetUpdateString): void {
151
152
  this.subject.next(update)
152
153
  }
153
154
 
154
- private doStep(update: SetUpdate): void {
155
+ private doStep(update: SetUpdateString): void {
155
156
  const typeValueBreak = update.indexOf(`:`)
156
157
  const type = update.substring(0, typeValueBreak) as SetUpdateType
157
158
  const value = update.substring(typeValueBreak + 1)
@@ -167,17 +168,17 @@ export class SetRTX<P extends primitive>
167
168
  break
168
169
  case `tx`:
169
170
  for (const subUpdate of value.split(`;`)) {
170
- this.doStep(subUpdate as SetUpdate)
171
+ this.doStep(subUpdate as SetUpdateString)
171
172
  }
172
173
  }
173
174
  }
174
175
 
175
- public getUpdateNumber(update: NumberedSetUpdate): number {
176
+ public getUpdateNumber(update: NumberedSetUpdateString): number {
176
177
  const breakpoint = update.indexOf(`=`)
177
178
  return Number(update.substring(0, breakpoint))
178
179
  }
179
180
 
180
- public do(update: NumberedSetUpdate): number | `OUT_OF_RANGE` | null {
181
+ public do(update: NumberedSetUpdateString): number | `OUT_OF_RANGE` | null {
181
182
  const breakpoint = update.indexOf(`=`)
182
183
  const updateNumber = Number(update.substring(0, breakpoint))
183
184
  const eventOffset = updateNumber - this.cacheUpdateNumber
@@ -185,7 +186,7 @@ export class SetRTX<P extends primitive>
185
186
  if (isFuture || Number.isNaN(eventOffset)) {
186
187
  if (eventOffset === 1 || Number.isNaN(eventOffset)) {
187
188
  this.mode = `playback`
188
- const innerUpdate = update.substring(breakpoint + 1) as SetUpdate
189
+ const innerUpdate = update.substring(breakpoint + 1) as SetUpdateString
189
190
  this.doStep(innerUpdate)
190
191
  this.mode = `record`
191
192
  this.cacheUpdateNumber = updateNumber
@@ -211,7 +212,7 @@ export class SetRTX<P extends primitive>
211
212
  this.undo(u)
212
213
  done = this.cacheIdx === eventIdx - 1
213
214
  }
214
- const innerUpdate = update.substring(breakpoint + 1) as SetUpdate
215
+ const innerUpdate = update.substring(breakpoint + 1) as SetUpdateString
215
216
  this.doStep(innerUpdate)
216
217
  this.mode = `record`
217
218
  this.cacheUpdateNumber = updateNumber
@@ -220,7 +221,7 @@ export class SetRTX<P extends primitive>
220
221
  return `OUT_OF_RANGE`
221
222
  }
222
223
 
223
- public undoStep(update: SetUpdate): void {
224
+ public undoStep(update: SetUpdateString): void {
224
225
  const breakpoint = update.indexOf(`:`)
225
226
  const type = update.substring(0, breakpoint) as SetUpdateType
226
227
  const value = update.substring(breakpoint + 1)
@@ -237,7 +238,7 @@ export class SetRTX<P extends primitive>
237
238
  break
238
239
  }
239
240
  case `tx`: {
240
- const updates = value.split(`;`) as SetUpdate[]
241
+ const updates = value.split(`;`) as SetUpdateString[]
241
242
  for (let i = updates.length - 1; i >= 0; i--) {
242
243
  this.undoStep(updates[i])
243
244
  }
@@ -245,12 +246,12 @@ export class SetRTX<P extends primitive>
245
246
  }
246
247
  }
247
248
 
248
- public undo(update: NumberedSetUpdate): number | null {
249
+ public undo(update: NumberedSetUpdateString): number | null {
249
250
  const breakpoint = update.indexOf(`=`)
250
251
  const updateNumber = Number(update.substring(0, breakpoint))
251
252
  if (updateNumber === this.cacheUpdateNumber) {
252
253
  this.mode = `playback`
253
- const innerUpdate = update.substring(breakpoint + 1) as SetUpdate
254
+ const innerUpdate = update.substring(breakpoint + 1) as SetUpdateString
254
255
  this.undoStep(innerUpdate)
255
256
  this.mode = `record`
256
257
  this.cacheUpdateNumber--
@@ -1,24 +1,47 @@
1
- import type { Transceiver, TransceiverMode } from "atom.io/internal"
2
- import { Subject } from "atom.io/internal"
3
- import type { Json, primitive, stringified } from "atom.io/json"
4
- import { stringifyJson } from "atom.io/json"
5
-
6
- export type UListUpdateType = `add` | `clear` | `del`
7
- export type UListUpdate<P extends primitive> =
8
- | `${`add` | `del`}:${stringified<P>}`
9
- | `clear:${stringified<P[]>}`
10
-
11
- export interface UListJson<P extends primitive> extends Json.Object {
12
- members: P[]
1
+ import type {
2
+ Enumeration,
3
+ Fn,
4
+ Transceiver,
5
+ TransceiverMode,
6
+ } from "atom.io/internal"
7
+ import { enumeration, packValue, Subject, unpackValue } from "atom.io/internal"
8
+ import type { primitive } from "atom.io/json"
9
+
10
+ export type SetMutations = Exclude<
11
+ keyof Set<any>,
12
+ symbol | keyof ReadonlySet<any>
13
+ >
14
+ export type SetUpdate<P extends primitive> =
15
+ | {
16
+ type: `add` | `delete`
17
+ value: P
18
+ }
19
+ | {
20
+ type: `clear`
21
+ values: P[]
22
+ }
23
+ export type UListUpdateType = SetUpdate<any>[`type`]
24
+ true satisfies SetMutations extends UListUpdateType
25
+ ? true
26
+ : Exclude<SetMutations, UListUpdateType>
27
+
28
+ export type PackedSetUpdate<P extends primitive> = string & {
29
+ update?: SetUpdate<P>
13
30
  }
31
+
32
+ export const SET_UPDATE_ENUM: Enumeration<[`add`, `delete`, `clear`]> =
33
+ enumeration([`add`, `delete`, `clear`] as const)
34
+
35
+ export type SetMutationHandler = { [K in UListUpdateType]: Fn }
36
+
14
37
  export class UList<P extends primitive>
15
38
  extends Set<P>
16
- implements Transceiver<ReadonlySet<P>, UListUpdate<P>, UListJson<P>>
39
+ implements
40
+ Transceiver<ReadonlySet<P>, PackedSetUpdate<P>, ReadonlyArray<P>>,
41
+ SetMutationHandler
17
42
  {
18
43
  public mode: TransceiverMode = `record`
19
- public readonly subject: Subject<UListUpdate<P>> = new Subject<
20
- UListUpdate<P>
21
- >()
44
+ public readonly subject: Subject<PackedSetUpdate<P>> = new Subject()
22
45
 
23
46
  public constructor(values?: Iterable<P>) {
24
47
  super(values)
@@ -28,20 +51,18 @@ export class UList<P extends primitive>
28
51
 
29
52
  public readonly READONLY_VIEW: ReadonlySet<P> = this
30
53
 
31
- public toJSON(): UListJson<P> {
32
- return {
33
- members: [...this],
34
- }
54
+ public toJSON(): ReadonlyArray<P> {
55
+ return [...this]
35
56
  }
36
57
 
37
- public static fromJSON<P extends primitive>(json: UListJson<P>): UList<P> {
38
- return new UList<P>(json.members)
58
+ public static fromJSON<P extends primitive>(json: ReadonlyArray<P>): UList<P> {
59
+ return new UList<P>(json)
39
60
  }
40
61
 
41
62
  public add(value: P): this {
42
63
  const result = super.add(value)
43
64
  if (this.mode === `record`) {
44
- this.emit(`add:${stringifyJson<P>(value)}`)
65
+ this.emit({ type: `add`, value })
45
66
  }
46
67
  return result
47
68
  }
@@ -50,74 +71,83 @@ export class UList<P extends primitive>
50
71
  const capturedContents = this.mode === `record` ? [...this] : null
51
72
  super.clear()
52
73
  if (capturedContents) {
53
- this.emit(`clear:${stringifyJson(capturedContents)}`)
74
+ this.emit({ type: `clear`, values: capturedContents })
54
75
  }
55
76
  }
56
77
 
57
78
  public delete(value: P): boolean {
58
79
  const result = super.delete(value)
59
80
  if (this.mode === `record`) {
60
- this.emit(`del:${stringifyJson<P>(value)}`)
81
+ this.emit({ type: `delete`, value })
61
82
  }
62
83
  return result
63
84
  }
64
85
 
65
86
  public subscribe(
66
87
  key: string,
67
- fn: (update: UListUpdate<P>) => void,
88
+ fn: (update: PackedSetUpdate<P>) => void,
68
89
  ): () => void {
69
90
  return this.subject.subscribe(key, fn)
70
91
  }
71
92
 
72
- public emit(update: UListUpdate<P>): void {
73
- this.subject.next(update)
93
+ public emit(update: SetUpdate<P>): void {
94
+ this.subject.next(UList.packUpdate(update))
74
95
  }
75
96
 
76
- private doStep(update: UListUpdate<P>): void {
77
- const typeValueBreak = update.indexOf(`:`)
78
- const type = update.substring(0, typeValueBreak) as UListUpdateType
79
- const value = update.substring(typeValueBreak + 1)
80
- switch (type) {
97
+ public do(packed: PackedSetUpdate<P>): null {
98
+ this.mode = `playback`
99
+ const update = UList.unpackUpdate(packed)
100
+ switch (update.type) {
81
101
  case `add`:
82
- this.add(JSON.parse(value))
102
+ this.add(update.value)
83
103
  break
84
- case `del`:
85
- this.delete(JSON.parse(value))
104
+ case `delete`:
105
+ this.delete(update.value)
86
106
  break
87
107
  case `clear`:
88
108
  this.clear()
89
109
  }
90
- }
91
-
92
- public do(update: UListUpdate<P>): null {
93
- this.mode = `playback`
94
- this.doStep(update)
95
110
  this.mode = `record`
96
111
  return null
97
112
  }
98
113
 
99
- public undoStep(update: UListUpdate<P>): void {
100
- const breakpoint = update.indexOf(`:`)
101
- const type = update.substring(0, breakpoint) as UListUpdateType
102
- const value = update.substring(breakpoint + 1)
103
- switch (type) {
114
+ public undo(packed: PackedSetUpdate<P>): number | null {
115
+ const update = UList.unpackUpdate(packed)
116
+ this.mode = `playback`
117
+ switch (update.type) {
104
118
  case `add`:
105
- this.delete(JSON.parse(value))
119
+ this.delete(update.value)
106
120
  break
107
- case `del`:
108
- this.add(JSON.parse(value))
121
+ case `delete`:
122
+ this.add(update.value)
109
123
  break
110
124
  case `clear`: {
111
- const values = JSON.parse(value) as P[]
125
+ const values = update.values
112
126
  for (const v of values) this.add(v)
113
127
  }
114
128
  }
115
- }
116
-
117
- public undo(update: UListUpdate<P>): number | null {
118
- this.mode = `playback`
119
- this.undoStep(update)
120
129
  this.mode = `record`
121
130
  return null
122
131
  }
132
+
133
+ public static packUpdate<P extends primitive>(
134
+ update: SetUpdate<P>,
135
+ ): PackedSetUpdate<P> {
136
+ const head = SET_UPDATE_ENUM[update.type] + `\u001F`
137
+ if (update.type === `clear`) {
138
+ return head + update.values.map(packValue).join(`\u001E`)
139
+ }
140
+ return head + packValue(update.value)
141
+ }
142
+ public static unpackUpdate<P extends primitive>(
143
+ packed: PackedSetUpdate<P>,
144
+ ): SetUpdate<P> {
145
+ const [type, tail] = packed.split(`\u001F`) as [0 | 1 | 2, string]
146
+ const head = SET_UPDATE_ENUM[type]
147
+ if (head === `clear`) {
148
+ const values = tail.split(`\u001E`).map(unpackValue) as P[]
149
+ return { type: `clear`, values }
150
+ }
151
+ return { type: head, value: unpackValue(tail) as P }
152
+ }
123
153
  }