atom.io 0.40.10 → 0.41.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.
@@ -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,193 @@ 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
+
98
+ export function packArrayUpdate<P extends primitive>(
99
+ update: ArrayUpdate<P>,
100
+ ): PackedArrayUpdate<P> {
101
+ let packed = ARRAY_UPDATE_ENUM[update.type] + `\u001F`
102
+ switch (update.type) {
103
+ case `set`:
104
+ packed += update.index + `\u001E` + packValue(update.next)
105
+ if (update.prev !== undefined) {
106
+ packed += `\u001E` + packValue(update.prev)
107
+ }
108
+ return packed
109
+ case `truncate`:
110
+ return (
111
+ packed +
112
+ update.length +
113
+ `\u001E` +
114
+ update.items.map(packValue).join(`\u001E`)
115
+ )
116
+ case `extend`:
117
+ return packed + update.next + `\u001E` + update.prev
118
+ case `pop`:
119
+ case `shift`:
120
+ if (update.value !== undefined) {
121
+ packed += packValue(update.value)
122
+ }
123
+ return packed
124
+ case `push`:
125
+ case `unshift`:
126
+ return packed + update.items.map(packValue).join(`\u001E`)
127
+ case `copyWithin`:
128
+ packed += update.target + `\u001E` + update.start
129
+ if (update.end !== undefined) {
130
+ packed += `\u001E` + update.end
131
+ }
132
+ packed += `\u001E\u001E` + update.prev.map(packValue).join(`\u001E`)
133
+ return packed
134
+ case `fill`:
135
+ packed += packValue(update.value)
136
+ if (update.start !== undefined) {
137
+ packed += `\u001E` + update.start
138
+ }
139
+ if (update.end !== undefined) {
140
+ packed += `\u001E` + update.end
141
+ }
142
+ packed += `\u001E\u001E` + update.prev.map(packValue).join(`\u001E`)
143
+ return packed
144
+ case `splice`:
145
+ return (
146
+ packed +
147
+ update.start +
148
+ `\u001E\u001E` +
149
+ update.deleteCount +
150
+ `\u001E\u001E` +
151
+ update.items.map(packValue).join(`\u001E`) +
152
+ `\u001E\u001E` +
153
+ update.deleted.map(packValue).join(`\u001E`)
154
+ )
155
+ case `reverse`:
156
+ return packed
157
+ case `sort`:
158
+ return (
159
+ packed +
160
+ update.next.map(packValue).join(`\u001E`) +
161
+ `\u001E\u001E` +
162
+ update.prev.map(packValue).join(`\u001E`)
163
+ )
164
+ }
165
+ }
166
+
167
+ export function unpackArrayUpdate<P extends primitive>(
168
+ packed: PackedArrayUpdate<P>,
169
+ ): ArrayUpdate<P> {
170
+ const [head, tail] = packed.split(`\u001F`) as [
171
+ Extract<keyof typeof ARRAY_UPDATE_ENUM, number>,
172
+ string,
173
+ ]
174
+ const type = ARRAY_UPDATE_ENUM[head]
175
+ switch (type) {
176
+ case `set`: {
177
+ const [i, n, p] = tail.split(`\u001E`)
178
+ const index = +i
179
+ const next = unpackValue(n) as P
180
+ if (p === undefined) {
181
+ return { type, index, next }
182
+ }
183
+ const prev = unpackValue(p) as P
184
+ return { type, index, next, prev }
185
+ }
186
+ case `truncate`: {
187
+ const [l, ...i] = tail.split(`\u001E`)
188
+ const length = +l
189
+ const items = i.map(unpackValue) as P[]
190
+ return { type, length, items }
191
+ }
192
+ case `extend`: {
193
+ const [n, p] = tail.split(`\u001E`)
194
+ const next = +n
195
+ const prev = +p
196
+ return { type, next, prev }
197
+ }
198
+ case `pop`:
199
+ case `shift`:
200
+ if (tail !== ``) {
201
+ const value = unpackValue(tail) as P
202
+ return { type, value }
203
+ }
204
+ return { type }
205
+ case `push`:
206
+ case `unshift`: {
207
+ const items = tail.split(`\u001E`).map(unpackValue) as P[]
208
+ return { type, items }
209
+ }
210
+ case `copyWithin`: {
211
+ const [numbers, data] = tail.split(`\u001E\u001E`)
212
+ const prev = data ? (data.split(`\u001E`).map(unpackValue) as P[]) : []
213
+ const [t, s, e] = numbers.split(`\u001E`)
214
+ const target = +t
215
+ const start = +s
216
+ if (e === undefined) {
217
+ return { type, target, start, prev }
218
+ }
219
+ const end = +e
220
+ return { type, target, start, prev, end }
221
+ }
222
+ case `fill`: {
223
+ const [numbers, data] = tail.split(`\u001E\u001E`)
224
+ const prev = data ? (data.split(`\u001E`).map(unpackValue) as P[]) : []
225
+ const [v, s, e] = numbers.split(`\u001E`)
226
+ const value = unpackValue(v) as P
227
+ if (s === undefined && e === undefined) {
228
+ return { type, value, prev }
229
+ }
230
+ const start = +s
231
+ if (e === undefined) {
232
+ return { type, value, prev, start }
233
+ }
234
+ const end = +e
235
+ return { type, value, prev, start, end }
236
+ }
237
+ case `splice`: {
238
+ const [s, c, i, d] = tail.split(`\u001E\u001E`)
239
+
240
+ const start = +s
241
+ const deleteCount = +c
242
+ const items = i ? (i.split(`\u001E`).map(unpackValue) as P[]) : []
243
+ const deleted = d ? (d.split(`\u001E`).map(unpackValue) as P[]) : []
244
+ return { type, start, deleteCount, items, deleted }
245
+ }
246
+ case `reverse`:
247
+ return { type }
248
+ case `sort`: {
249
+ const [n, p] = tail.split(`\u001E\u001E`)
250
+ const next = n ? (n.split(`\u001E`).map(unpackValue) as P[]) : []
251
+ const prev = p ? (p.split(`\u001E`).map(unpackValue) as P[]) : []
252
+ return { type, next, prev }
253
+ }
254
+ }
255
+ }
256
+
65
257
  export type ArrayMutationHandler = {
66
258
  [K in Exclude<OListUpdateType, `extend` | `set` | `truncate`>]: Fn
67
259
  }
@@ -69,13 +261,11 @@ export type ArrayMutationHandler = {
69
261
  export class OList<P extends primitive>
70
262
  extends Array<P>
71
263
  implements
72
- Transceiver<ReadonlyArray<P>, ArrayUpdate<P>, ReadonlyArray<P>>,
264
+ Transceiver<ReadonlyArray<P>, PackedArrayUpdate<P>, ReadonlyArray<P>>,
73
265
  ArrayMutationHandler
74
266
  {
75
267
  public mode: TransceiverMode = `record`
76
- public readonly subject: Subject<ArrayUpdate<P>> = new Subject<
77
- ArrayUpdate<P>
78
- >()
268
+ public readonly subject: Subject<PackedArrayUpdate<P>> = new Subject()
79
269
 
80
270
  public readonly READONLY_VIEW: ReadonlyArray<P> = this
81
271
 
@@ -198,6 +388,27 @@ export class OList<P extends primitive>
198
388
  return this
199
389
  }
200
390
 
391
+ public fill(value: P, start?: number, end?: number): this {
392
+ if (this.mode === `record`) {
393
+ this.mode = `playback`
394
+ const prev = this.slice(start, end)
395
+ super.fill(value, start, end)
396
+ if (start === undefined) {
397
+ this.emit({ type: `fill`, value, prev })
398
+ } else {
399
+ if (end === undefined) {
400
+ this.emit({ type: `fill`, value, start, prev })
401
+ } else {
402
+ this.emit({ type: `fill`, value, start, end, prev })
403
+ }
404
+ }
405
+ this.mode = `record`
406
+ } else {
407
+ super.fill(value, start, end)
408
+ }
409
+ return this
410
+ }
411
+
201
412
  public sort(compareFn?: (a: P, b: P) => number): this {
202
413
  if (this.mode === `record`) {
203
414
  this.mode = `playback`
@@ -256,13 +467,13 @@ export class OList<P extends primitive>
256
467
 
257
468
  public subscribe(
258
469
  key: string,
259
- fn: (update: ArrayUpdate<P>) => void,
470
+ fn: (update: PackedArrayUpdate<P>) => void,
260
471
  ): () => void {
261
472
  return this.subject.subscribe(key, fn)
262
473
  }
263
474
 
264
475
  public emit(update: ArrayUpdate<P>): void {
265
- this.subject.next(update)
476
+ this.subject.next(packArrayUpdate(update))
266
477
  }
267
478
 
268
479
  private doStep(update: ArrayUpdate<P>): void {
@@ -296,11 +507,7 @@ export class OList<P extends primitive>
296
507
  this.length = update.next.length
297
508
  break
298
509
  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
- }
510
+ this.splice(update.start, update.deleteCount, ...update.items)
304
511
  break
305
512
  case `truncate`:
306
513
  this.length = update.length
@@ -314,9 +521,10 @@ export class OList<P extends primitive>
314
521
  }
315
522
  }
316
523
 
317
- public do(update: ArrayUpdate<P>): null {
524
+ public do(update: PackedArrayUpdate<P>): null {
318
525
  this.mode = `playback`
319
- this.doStep(update)
526
+ const unpacked = unpackArrayUpdate(update)
527
+ this.doStep(unpacked)
320
528
  this.mode = `record`
321
529
  return null
322
530
  }
@@ -378,15 +586,7 @@ export class OList<P extends primitive>
378
586
  }
379
587
  break
380
588
  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
- }
589
+ this.splice(update.start, update.items.length, ...update.deleted)
390
590
  break
391
591
  case `truncate`:
392
592
  this.push(...update.items)
@@ -403,9 +603,10 @@ export class OList<P extends primitive>
403
603
  }
404
604
  }
405
605
 
406
- public undo(update: ArrayUpdate<P>): number | null {
606
+ public undo(update: PackedArrayUpdate<P>): number | null {
407
607
  this.mode = `playback`
408
- this.undoStep(update)
608
+ const unpacked = unpackArrayUpdate(update)
609
+ this.undoStep(unpacked)
409
610
  this.mode = `record`
410
611
  return null
411
612
  }
@@ -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,68 @@
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>
30
+ }
31
+
32
+ export const SET_UPDATE_ENUM: Enumeration<[`add`, `delete`, `clear`]> =
33
+ enumeration([`add`, `delete`, `clear`] as const)
34
+
35
+ export function packSetUpdate<P extends primitive>(
36
+ update: SetUpdate<P>,
37
+ ): PackedSetUpdate<P> {
38
+ const head = SET_UPDATE_ENUM[update.type] + `\u001F`
39
+ if (update.type === `clear`) {
40
+ return head + update.values.map(packValue).join(`\u001E`)
41
+ }
42
+ return head + packValue(update.value)
43
+ }
44
+ export function unpackSetUpdate<P extends primitive>(
45
+ packed: PackedSetUpdate<P>,
46
+ ): SetUpdate<P> {
47
+ const [type, tail] = packed.split(`\u001F`) as [0 | 1 | 2, string]
48
+ const head = SET_UPDATE_ENUM[type]
49
+ if (head === `clear`) {
50
+ const values = tail.split(`\u001E`).map(unpackValue) as P[]
51
+ return { type: `clear`, values }
52
+ }
53
+ return { type: head, value: unpackValue(tail) as P }
13
54
  }
55
+
56
+ export type SetMutationHandler = { [K in UListUpdateType]: Fn }
57
+
14
58
  export class UList<P extends primitive>
15
59
  extends Set<P>
16
- implements Transceiver<ReadonlySet<P>, UListUpdate<P>, UListJson<P>>
60
+ implements
61
+ Transceiver<ReadonlySet<P>, PackedSetUpdate<P>, ReadonlyArray<P>>,
62
+ SetMutationHandler
17
63
  {
18
64
  public mode: TransceiverMode = `record`
19
- public readonly subject: Subject<UListUpdate<P>> = new Subject<
20
- UListUpdate<P>
21
- >()
65
+ public readonly subject: Subject<PackedSetUpdate<P>> = new Subject()
22
66
 
23
67
  public constructor(values?: Iterable<P>) {
24
68
  super(values)
@@ -28,20 +72,18 @@ export class UList<P extends primitive>
28
72
 
29
73
  public readonly READONLY_VIEW: ReadonlySet<P> = this
30
74
 
31
- public toJSON(): UListJson<P> {
32
- return {
33
- members: [...this],
34
- }
75
+ public toJSON(): ReadonlyArray<P> {
76
+ return [...this]
35
77
  }
36
78
 
37
- public static fromJSON<P extends primitive>(json: UListJson<P>): UList<P> {
38
- return new UList<P>(json.members)
79
+ public static fromJSON<P extends primitive>(json: ReadonlyArray<P>): UList<P> {
80
+ return new UList<P>(json)
39
81
  }
40
82
 
41
83
  public add(value: P): this {
42
84
  const result = super.add(value)
43
85
  if (this.mode === `record`) {
44
- this.emit(`add:${stringifyJson<P>(value)}`)
86
+ this.emit({ type: `add`, value })
45
87
  }
46
88
  return result
47
89
  }
@@ -50,73 +92,61 @@ export class UList<P extends primitive>
50
92
  const capturedContents = this.mode === `record` ? [...this] : null
51
93
  super.clear()
52
94
  if (capturedContents) {
53
- this.emit(`clear:${stringifyJson(capturedContents)}`)
95
+ this.emit({ type: `clear`, values: capturedContents })
54
96
  }
55
97
  }
56
98
 
57
99
  public delete(value: P): boolean {
58
100
  const result = super.delete(value)
59
101
  if (this.mode === `record`) {
60
- this.emit(`del:${stringifyJson<P>(value)}`)
102
+ this.emit({ type: `delete`, value })
61
103
  }
62
104
  return result
63
105
  }
64
106
 
65
107
  public subscribe(
66
108
  key: string,
67
- fn: (update: UListUpdate<P>) => void,
109
+ fn: (update: PackedSetUpdate<P>) => void,
68
110
  ): () => void {
69
111
  return this.subject.subscribe(key, fn)
70
112
  }
71
113
 
72
- public emit(update: UListUpdate<P>): void {
73
- this.subject.next(update)
114
+ public emit(update: SetUpdate<P>): void {
115
+ this.subject.next(packSetUpdate(update))
74
116
  }
75
117
 
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) {
118
+ public do(packed: PackedSetUpdate<P>): null {
119
+ this.mode = `playback`
120
+ const update = unpackSetUpdate(packed)
121
+ switch (update.type) {
81
122
  case `add`:
82
- this.add(JSON.parse(value))
123
+ this.add(update.value)
83
124
  break
84
- case `del`:
85
- this.delete(JSON.parse(value))
125
+ case `delete`:
126
+ this.delete(update.value)
86
127
  break
87
128
  case `clear`:
88
129
  this.clear()
89
130
  }
90
- }
91
-
92
- public do(update: UListUpdate<P>): null {
93
- this.mode = `playback`
94
- this.doStep(update)
95
131
  this.mode = `record`
96
132
  return null
97
133
  }
98
134
 
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) {
135
+ public undo(packed: PackedSetUpdate<P>): number | null {
136
+ const update = unpackSetUpdate(packed)
137
+ this.mode = `playback`
138
+ switch (update.type) {
104
139
  case `add`:
105
- this.delete(JSON.parse(value))
140
+ this.delete(update.value)
106
141
  break
107
- case `del`:
108
- this.add(JSON.parse(value))
142
+ case `delete`:
143
+ this.add(update.value)
109
144
  break
110
145
  case `clear`: {
111
- const values = JSON.parse(value) as P[]
146
+ const values = update.values
112
147
  for (const v of values) this.add(v)
113
148
  }
114
149
  }
115
- }
116
-
117
- public undo(update: UListUpdate<P>): number | null {
118
- this.mode = `playback`
119
- this.undoStep(update)
120
150
  this.mode = `record`
121
151
  return null
122
152
  }