• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

live-input-vector-output-node / livon-ts / 26078594527

19 May 2026 05:40AM UTC coverage: 69.413% (-5.6%) from 74.971%
26078594527

push

github

cr15p1
Remove remaining duplicated Sonar blocks

1990 of 3317 branches covered (59.99%)

Branch coverage included in aggregate %.

9 of 23 new or added lines in 3 files covered. (39.13%)

304 existing lines in 15 files now uncovered.

4321 of 5775 relevant lines covered (74.82%)

25.6 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.18
/packages/sync/src/utils/structuredSerialization.ts
1
interface UnknownRecord {
2
  [key: string]: unknown;
3
}
4

5
interface SerializedRecord {
6
  [key: string]: SerializedValue;
7
}
8

9
interface SerializedArray extends Array<SerializedValue> {}
10

11
interface SerializedMapEntry {
12
  key: SerializedValue;
13
  value: SerializedValue;
14
}
15

16
interface SerializedBaseMarker {
17
  __livonSerializedValue__: 1;
18
  __livonType__: string;
19
}
20

21
interface SerializedUndefined extends SerializedBaseMarker {
22
  __livonType__: 'undefined';
23
}
24

25
interface SerializedSpecialNumber extends SerializedBaseMarker {
26
  __livonType__: 'number';
27
  value: 'NaN' | 'Infinity' | '-Infinity' | '-0';
28
}
29

30
interface SerializedBigInt extends SerializedBaseMarker {
31
  __livonType__: 'bigint';
32
  value: string;
33
}
34

35
interface SerializedDate extends SerializedBaseMarker {
36
  __livonType__: 'date';
37
  value: string;
38
}
39

40
interface SerializedRegExp extends SerializedBaseMarker {
41
  __livonType__: 'regexp';
42
  source: string;
43
  flags: string;
44
}
45

46
interface SerializedMap extends SerializedBaseMarker {
47
  __livonType__: 'map';
48
  entries: readonly SerializedMapEntry[];
49
}
50

51
interface SerializedSet extends SerializedBaseMarker {
52
  __livonType__: 'set';
53
  values: readonly SerializedValue[];
54
}
55

56
interface SerializedEscapedObject extends SerializedBaseMarker {
57
  __livonType__: 'escaped-object';
58
  value: SerializedRecord;
59
}
60

61
interface SerializedStringifiedValue extends SerializedBaseMarker {
62
  __livonType__: 'stringified';
63
  sourceType: 'function' | 'symbol' | 'unsupported-object';
64
  value: string;
65
}
66

67
type SerializedPrimitive = null | boolean | number | string;
68

69
type SerializedMarker =
70
  | SerializedBigInt
71
  | SerializedDate
72
  | SerializedEscapedObject
73
  | SerializedMap
74
  | SerializedRegExp
75
  | SerializedSet
76
  | SerializedSpecialNumber
77
  | SerializedStringifiedValue
78
  | SerializedUndefined;
79

80
type SerializedValue = SerializedPrimitive | SerializedArray | SerializedMarker | SerializedRecord;
81

82
type UnsupportedValueBehavior = 'stringify' | 'throw';
83

84
interface BuildSerializedValueInput {
85
  input: unknown;
86
  unsupportedValueBehavior: UnsupportedValueBehavior;
87
  sortCollections: boolean;
88
  parents: WeakSet<object>;
89
}
90

91
interface BuildSerializedArrayInput {
92
  input: readonly unknown[];
93
  unsupportedValueBehavior: UnsupportedValueBehavior;
94
  sortCollections: boolean;
95
  parents: WeakSet<object>;
96
}
97

98
interface BuildSerializedObjectInput extends BuildSerializedValueInput {
99
  input: object;
100
}
101

102
interface SerializeStructuredValueInput {
103
  input: unknown;
104
  unsupportedValueBehavior?: UnsupportedValueBehavior;
105
}
106

107
interface WithParentTrackingInput<TResult> {
108
  input: object;
109
  parents: WeakSet<object>;
110
  run: () => TResult;
111
}
112

113
interface SerializeUnsupportedValueInput {
114
  input: unknown;
115
  unsupportedValueBehavior: UnsupportedValueBehavior;
116
  sourceType: SerializedStringifiedValue['sourceType'];
117
}
118

119
const SERIALIZED_VALUE_MARKER = 1;
20✔
120

121
const createMarker = <TMarker extends SerializedMarker>(input: Omit<TMarker, '__livonSerializedValue__'>): TMarker => {
20✔
122
  return {
20✔
123
    __livonSerializedValue__: SERIALIZED_VALUE_MARKER,
124
    ...input,
125
  } as TMarker;
126
};
127

128
const isSerializedMarker = (input: SerializedValue): input is SerializedMarker => {
20✔
129
  return typeof input === 'object'
24✔
130
    && input !== null
131
    && !Array.isArray(input)
132
    && '__livonSerializedValue__' in input
133
    && input.__livonSerializedValue__ === SERIALIZED_VALUE_MARKER
134
    && '__livonType__' in input
135
    && typeof input.__livonType__ === 'string';
136
};
137

138
const isPlainObject = (input: object): boolean => {
20✔
UNCOV
139
  const prototype = Object.getPrototypeOf(input);
×
140

141
  return prototype === Object.prototype || prototype === null;
×
142
};
143

144
const withParentTracking = <TResult>(
20✔
145
  { input, parents, run }: WithParentTrackingInput<TResult>,
146
): TResult => {
147
  if (parents.has(input)) {
14!
UNCOV
148
    throw new TypeError('Cannot serialize circular structures in @livon/sync.');
×
149
  }
150

151
  parents.add(input);
14✔
152

153
  try {
14✔
154
    return run();
14✔
155
  } finally {
156
    parents.delete(input);
14✔
157
  }
158
};
159

160
const serializeUnsupportedValue = (
20✔
161
  { input, unsupportedValueBehavior, sourceType }: SerializeUnsupportedValueInput,
162
): SerializedValue => {
163
  if (unsupportedValueBehavior === 'throw') {
1!
164
    throw new TypeError(`Cannot serialize ${sourceType} values in @livon/sync.`);
1✔
165
  }
166

UNCOV
167
  return createMarker<SerializedStringifiedValue>({
×
168
    __livonType__: 'stringified',
169
    sourceType,
170
    value: String(input),
171
  });
172
};
173

174
const stableStringifySerializedValue = (input: SerializedValue): string => {
20✔
175
  if (
52✔
176
    input === null
195✔
177
    || typeof input === 'boolean'
178
    || typeof input === 'number'
179
    || typeof input === 'string'
180
  ) {
181
    return JSON.stringify(input);
36✔
182
  }
183

184
  if (Array.isArray(input)) {
16✔
185
    return `[${input.map((entry) => stableStringifySerializedValue(entry)).join(',')}]`;
8✔
186
  }
187

188
  const recordInput = input as SerializedRecord;
12✔
189
  const keys = Object.keys(recordInput).sort((left, right) => left.localeCompare(right));
16✔
190
  const serializedEntries = keys
12✔
191
    .map((key) => {
192
      const value = recordInput[key] as SerializedValue;
28✔
193

194
      return `${JSON.stringify(key)}:${stableStringifySerializedValue(value)}`;
28✔
195
    });
196

197
  return `{${serializedEntries.join(',')}}`;
12✔
198
};
199

200
const decodeSerializedRecord = (input: SerializedRecord): UnknownRecord => {
20✔
201
  const decodedRecord: UnknownRecord = {};
4✔
202

203
  Object.entries(input).forEach(([key, value]) => {
4✔
204
    decodedRecord[key] = decodeSerializedValue(value);
17✔
205
  });
206

207
  return decodedRecord;
4✔
208
};
209

210
const buildSerializedObject = ({
20✔
211
  input,
212
  unsupportedValueBehavior,
213
  sortCollections,
214
  parents,
215
}: BuildSerializedObjectInput): SerializedValue => {
216
  if (input instanceof Date) {
17✔
217
    return createMarker<SerializedDate>({
3✔
218
      __livonType__: 'date',
219
      value: String(input.getTime()),
220
    });
221
  }
222

223
  if (input instanceof RegExp) {
14✔
224
    return createMarker<SerializedRegExp>({
1✔
225
      __livonType__: 'regexp',
226
      source: input.source,
227
      flags: input.flags,
228
    });
229
  }
230

231
  if (input instanceof Map) {
13✔
232
    return withParentTracking({
3✔
233
      input,
234
      parents,
235
      run: () => {
236
      const entries = Array.from(input.entries()).map(([key, value]) => {
3✔
237
        return {
6✔
238
          key: buildSerializedValue({
239
            input: key,
240
            unsupportedValueBehavior,
241
            sortCollections,
242
            parents,
243
          }),
244
          value: buildSerializedValue({
245
            input: value,
246
            unsupportedValueBehavior,
247
            sortCollections,
248
            parents,
249
          }),
250
        } satisfies SerializedMapEntry;
251
      });
252

253
      const normalizedEntries = sortCollections
3✔
254
        ? [...entries].sort((left, right) => {
255
            const leftKey = stableStringifySerializedValue(left.key);
2✔
256
            const rightKey = stableStringifySerializedValue(right.key);
2✔
257
            if (leftKey !== rightKey) {
2!
258
              return leftKey.localeCompare(rightKey);
2✔
259
            }
260

UNCOV
261
            const leftValue = stableStringifySerializedValue(left.value);
×
UNCOV
262
            const rightValue = stableStringifySerializedValue(right.value);
×
263

UNCOV
264
            return leftValue.localeCompare(rightValue);
×
265
          })
266
        : entries;
267

268
      return createMarker<SerializedMap>({
3✔
269
        __livonType__: 'map',
270
        entries: normalizedEntries,
271
      });
272
      },
273
    });
274
  }
275

276
  if (input instanceof Set) {
10✔
277
    return withParentTracking({
3✔
278
      input,
279
      parents,
280
      run: () => {
281
      const values = Array.from(input.values()).map((value) => {
3✔
282
        return buildSerializedValue({
6✔
283
          input: value,
284
          unsupportedValueBehavior,
285
          sortCollections,
286
          parents,
287
        });
288
      });
289

290
      const normalizedValues = sortCollections
3✔
291
        ? [...values].sort((left, right) => {
292
            return stableStringifySerializedValue(left)
2✔
293
              .localeCompare(stableStringifySerializedValue(right));
294
          })
295
        : values;
296

297
      return createMarker<SerializedSet>({
3✔
298
        __livonType__: 'set',
299
        values: normalizedValues,
300
      });
301
      },
302
    });
303
  }
304

305
  const objectEntriesBase = Object.entries(input);
7✔
306
  const objectEntries = sortCollections
7✔
UNCOV
307
    ? [...objectEntriesBase].sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey))
×
308
    : objectEntriesBase;
309
  if (objectEntries.length === 0 && !isPlainObject(input)) {
17!
UNCOV
310
    return serializeUnsupportedValue({
×
311
      input,
312
      unsupportedValueBehavior,
313
      sourceType: 'unsupported-object',
314
    });
315
  }
316

317
  return withParentTracking({
7✔
318
    input,
319
    parents,
320
    run: () => {
321
    const serializedRecord: SerializedRecord = {};
7✔
322

323
    objectEntries.forEach(([key, value]) => {
7✔
324
      serializedRecord[key] = buildSerializedValue({
20✔
325
        input: value,
326
        unsupportedValueBehavior,
327
        sortCollections,
328
        parents,
329
      });
330
    });
331

332
    if (isSerializedMarker(serializedRecord)) {
7✔
333
      return createMarker<SerializedEscapedObject>({
1✔
334
        __livonType__: 'escaped-object',
335
        value: serializedRecord,
336
      });
337
    }
338

339
    return serializedRecord;
5✔
340
    },
341
  });
342
};
343

344
const buildSerializedNumber = (input: number): SerializedValue => {
20✔
345
  if (Number.isNaN(input)) {
13✔
346
    return createMarker<SerializedSpecialNumber>({
2✔
347
      __livonType__: 'number',
348
      value: 'NaN',
349
    });
350
  }
351

352
  if (input === Number.POSITIVE_INFINITY) {
11✔
353
    return createMarker<SerializedSpecialNumber>({
1✔
354
      __livonType__: 'number',
355
      value: 'Infinity',
356
    });
357
  }
358

359
  if (input === Number.NEGATIVE_INFINITY) {
10✔
360
    return createMarker<SerializedSpecialNumber>({
1✔
361
      __livonType__: 'number',
362
      value: '-Infinity',
363
    });
364
  }
365

366
  if (Object.is(input, -0)) {
9✔
367
    return createMarker<SerializedSpecialNumber>({
1✔
368
      __livonType__: 'number',
369
      value: '-0',
370
    });
371
  }
372

373
  return input;
8✔
374
};
375

376
const buildSerializedArray = ({
20✔
377
  input,
378
  unsupportedValueBehavior,
379
  sortCollections,
380
  parents,
381
}: BuildSerializedArrayInput): SerializedValue =>
382
  withParentTracking({
1✔
383
    input,
384
    parents,
385
    run: () => input.map((entry) => buildSerializedValue({
2✔
386
      input: entry,
387
      unsupportedValueBehavior,
388
      sortCollections,
389
      parents,
390
    })),
391
  });
392

393
const buildSerializedValue = ({
20✔
394
  input,
395
  unsupportedValueBehavior,
396
  sortCollections,
397
  parents,
398
}: BuildSerializedValueInput): SerializedValue => {
399
  if (input === null) {
51✔
400
    return null;
1✔
401
  }
402

403
  if (input === undefined) {
50✔
404
    return createMarker<SerializedUndefined>({
2✔
405
      __livonType__: 'undefined',
406
    });
407
  }
408

409
  if (typeof input === 'string' || typeof input === 'boolean') {
48✔
410
    return input;
14✔
411
  }
412

413
  if (typeof input === 'number') {
34✔
414
    return buildSerializedNumber(input);
13✔
415
  }
416

417
  if (typeof input === 'bigint') {
21✔
418
    return createMarker<SerializedBigInt>({
2✔
419
      __livonType__: 'bigint',
420
      value: input.toString(),
421
    });
422
  }
423

424
  if (typeof input === 'symbol') {
19!
UNCOV
425
    return serializeUnsupportedValue({
×
426
      input,
427
      unsupportedValueBehavior,
428
      sourceType: 'symbol',
429
    });
430
  }
431

432
  if (typeof input === 'function') {
19✔
433
    return serializeUnsupportedValue({
1✔
434
      input,
435
      unsupportedValueBehavior,
436
      sourceType: 'function',
437
    });
438
  }
439

440
  if (Array.isArray(input)) {
18✔
441
    return buildSerializedArray({
1✔
442
      input,
443
      unsupportedValueBehavior,
444
      sortCollections,
445
      parents,
446
    });
447
  }
448

449
  return buildSerializedObject({
17✔
450
    input,
451
    unsupportedValueBehavior,
452
    sortCollections,
453
    parents,
454
  });
455
};
456

457
const decodeSerializedValue = (input: SerializedValue): unknown => {
20✔
458
  if (
28✔
459
    input === null
108✔
460
    || typeof input === 'boolean'
461
    || typeof input === 'number'
462
    || typeof input === 'string'
463
  ) {
464
    return input;
9✔
465
  }
466

467
  if (Array.isArray(input)) {
19✔
468
    return input.map((entry) => decodeSerializedValue(entry));
2✔
469
  }
470

471
  if (!isSerializedMarker(input)) {
18✔
472
    return decodeSerializedRecord(input);
3✔
473
  }
474

475
  switch (input.__livonType__) {
15!
476
    case 'undefined':
477
      return undefined;
2✔
478
    case 'number':
479
      if (input.value === 'NaN') {
4✔
480
        return Number.NaN;
1✔
481
      }
482

483
      if (input.value === 'Infinity') {
3✔
484
        return Number.POSITIVE_INFINITY;
1✔
485
      }
486

487
      if (input.value === '-Infinity') {
2✔
488
        return Number.NEGATIVE_INFINITY;
1✔
489
      }
490

491
      return -0;
1✔
492
    case 'bigint':
493
      return BigInt(input.value);
1✔
494
    case 'date':
495
      return new Date(Number(input.value));
3✔
496
    case 'regexp':
497
      return new RegExp(input.source, input.flags);
1✔
498
    case 'map':
499
      return new Map(
1✔
500
        input.entries.map((entry) => {
501
          return [decodeSerializedValue(entry.key), decodeSerializedValue(entry.value)] as const;
2✔
502
        }),
503
      );
504
    case 'set':
505
      return new Set(
1✔
506
        input.values.map((value) => {
507
          return decodeSerializedValue(value);
2✔
508
        }),
509
      );
510
    case 'escaped-object':
511
      return decodeSerializedValue(input.value);
1✔
512
    case 'stringified':
UNCOV
513
      return input.value;
×
514
    default:
515
      return decodeSerializedRecord(input);
1✔
516
  }
517
};
518

519
export const serializeStructuredValue = ({
20✔
520
  input,
521
  unsupportedValueBehavior = 'throw',
3✔
522
}: SerializeStructuredValueInput): string => {
523
  const serializedValue = buildSerializedValue({
3✔
524
    input,
525
    unsupportedValueBehavior,
526
    sortCollections: false,
527
    parents: new WeakSet<object>(),
528
  });
529

530
  return JSON.stringify(serializedValue);
3✔
531
};
532

533
export const deserializeStructuredValue = <TResult>(input: string): TResult => {
20✔
534
  const parsed = JSON.parse(input) as SerializedValue;
2✔
535

536
  return decodeSerializedValue(parsed) as TResult;
2✔
537
};
538

539
const createStableSerializedValue = (input: unknown): SerializedValue => {
20✔
540
  return buildSerializedValue({
8✔
541
    input,
542
    unsupportedValueBehavior: 'stringify',
543
    sortCollections: true,
544
    parents: new WeakSet<object>(),
545
  });
546
};
547

548
export type StableStructuredValue = ReturnType<typeof createStableSerializedValue>;
549

550
export const createStableStructuredValue = (input: unknown): StableStructuredValue => {
20✔
UNCOV
551
  return createStableSerializedValue(input);
×
552
};
553

554
export const stableSerializeStructuredValue = (input: unknown): string => {
20✔
555
  const serializedValue = createStableSerializedValue(input);
8✔
556

557
  return stableStringifySerializedValue(serializedValue);
8✔
558
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc