@dan-uni/dan-any 0.4.9 → 0.6.6

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,4 +1,4 @@
1
- // @generated by protoc-gen-es v2.6.0 with parameter "target=ts"
1
+ // @generated by protoc-gen-es v2.6.2 with parameter "target=ts"
2
2
  // @generated from file danuni.proto (package danuni.danmaku.v1, syntax proto3)
3
3
  /* eslint-disable */
4
4
 
@@ -114,13 +114,16 @@ describe('其它', () => {
114
114
  UniDM.create({ ...commonSample }),
115
115
  UniDM.create({ ...commonSample, extra: { artplayer: { border: true } } }),
116
116
  ]
117
+ let counter = 0
117
118
  for (const pool of pool2) {
118
119
  console.info(pool.extraStr)
119
120
  console.info(pool2[0].isSameAs(pool))
121
+ if (counter <= 2) expect(pool2[0].extraStr).toBe(undefined)
122
+ counter++
120
123
  }
121
124
  expect(pool2[0].isSameAs(pool2[1])).toBe(true)
122
125
  expect(pool2[0].isSameAs(pool2[2])).toBe(true)
123
- expect(pool2[0].isSameAs(pool2[3])).toBe(true)
126
+ expect(pool2[0].isSameAs(pool2[3])).toBe(false)
124
127
  expect(pool2[0].isSameAs(pool2[4])).toBe(false)
125
128
  })
126
129
  })
@@ -1,4 +1,19 @@
1
+ import { Expose, plainToInstance } from 'class-transformer'
2
+ import {
3
+ IsDate,
4
+ IsEmail,
5
+ IsEnum,
6
+ IsInt,
7
+ IsNotEmpty,
8
+ IsNumber,
9
+ IsOptional,
10
+ IsString,
11
+ Max,
12
+ Min,
13
+ validateOrReject,
14
+ } from 'class-validator'
1
15
  import TimeFormat from 'hh-mm-ss'
16
+ import JSONbig from 'json-bigint'
2
17
  import type { DM_JSON_BiliCommandGrpc } from '..'
3
18
  import type { PlatformDanmakuSource } from './platform'
4
19
 
@@ -9,8 +24,7 @@ import {
9
24
  PlatformVideoSource,
10
25
  } from './platform'
11
26
 
12
- const BigIntSerializer = (k: string, v: any) =>
13
- typeof v === 'bigint' ? v.toString() : v
27
+ const JSON = JSONbig({ useNativeBigInt: true })
14
28
 
15
29
  function cleanEmptyObjects(obj: object): object {
16
30
  if (obj === null || typeof obj !== 'object') {
@@ -42,6 +56,11 @@ class SetBin {
42
56
  this.bin &= ~(1 << bit)
43
57
  }
44
58
  }
59
+
60
+ /**
61
+ * 获得数字的二进制,每位以boolean(true/false)表示1/0,从低位向高位
62
+ * @param number 任意进制数字
63
+ */
45
64
  const toBits = (number: number) => {
46
65
  // 低速方案
47
66
  // return [...number.toString(2)].map(Number)
@@ -52,26 +71,27 @@ const toBits = (number: number) => {
52
71
  // bits.unshift(number & 1) // (0|1)[]
53
72
  number >>= 1
54
73
  } while (number)
55
- return bits
74
+ return bits.reverse()
56
75
  }
57
76
 
58
- export type DMAttr =
59
- | 'Protect'
60
- | 'FromLive'
61
- | 'HighLike'
62
- | 'Compatible' // 由dan-any进行过兼容处理的弹幕,可能丢失部分信息
63
- | 'Reported' // 在DanUni上被多人举报过的弹幕
64
- | 'Unchecked' // 在DanUni上未被审核过的弹幕
65
- | 'HasEvent' // 该弹幕当前在DanUni上存在事件(如点赞/举报等)
66
- | 'Hide' // 由于其它原因需要隐藏的弹幕(建议在server端不返回该类弹幕)
77
+ export enum DMAttr {
78
+ Protect = 'Protect',
79
+ FromLive = 'FromLive',
80
+ HighLike = 'HighLike',
81
+ Compatible = 'Compatible', // 由dan-any进行过兼容处理的弹幕,可能丢失部分信息
82
+ Reported = 'Reported', // 在DanUni上被多人举报过的弹幕
83
+ Unchecked = 'Unchecked', // 在DanUni上未被审核过的弹幕
84
+ HasEvent = 'HasEvent', // 该弹幕当前在DanUni上存在事件(如点赞/举报等)
85
+ Hide = 'Hide', // 由于其它原因需要隐藏的弹幕(建议在server端不返回该类弹幕)
86
+ }
67
87
  const DMAttrUtils = {
68
88
  fromBin(bin: number = 0, format?: PlatformDanmakuSource) {
69
89
  const array = toBits(bin),
70
90
  attr: DMAttr[] = []
71
91
  if (format === 'bili') {
72
- if (array[0]) attr.push('Protect')
73
- if (array[1]) attr.push('FromLive')
74
- if (array[2]) attr.push('HighLike')
92
+ if (array[0]) attr.push(DMAttr.Protect)
93
+ if (array[1]) attr.push(DMAttr.FromLive)
94
+ if (array[2]) attr.push(DMAttr.HighLike)
75
95
  }
76
96
  return attr
77
97
  },
@@ -86,9 +106,9 @@ const DMAttrUtils = {
86
106
  ) {
87
107
  const bin = new SetBin(0)
88
108
  if (format === 'bili') {
89
- if (attr.includes('Protect')) bin.set1(0)
90
- if (attr.includes('FromLive')) bin.set1(1)
91
- if (attr.includes('HighLike')) bin.set1(2)
109
+ if (attr.includes(DMAttr.Protect)) bin.set1(0)
110
+ if (attr.includes(DMAttr.FromLive)) bin.set1(1)
111
+ if (attr.includes(DMAttr.HighLike)) bin.set1(2)
92
112
  }
93
113
  return bin.bin
94
114
  },
@@ -314,129 +334,194 @@ export interface UniDMObj {
314
334
  DMID: string
315
335
  }
316
336
 
317
- /**
318
- * 所有 number/bigint 值设为0自动转换为默认
319
- */
320
337
  export class UniDM {
321
338
  /**
322
- * 同步时确认位置的参数
339
+ * 资源ID
340
+ * @description 由某一danuni服务确定的某一剧集下不同资源(不同视频站/字幕组具有细节差异)的ID
323
341
  */
324
- // syncAnchor = BigInt(Date.now())
325
- constructor(
326
- /**
327
- * 资源ID
328
- * @description 由某一danuni服务确定的某一剧集下不同资源(不同视频站/字幕组具有细节差异)的ID
329
- */
330
- public SOID: string,
331
- /**
332
- * 弹幕出现位置(单位s;精度为ms,即保留三位小数)
333
- */
334
- public progress: number = 0,
335
- /**
336
- * 弹幕类型
337
- */
338
- public mode: Modes = Modes.Normal,
339
- /**
340
- * 字号
341
- * @default 25
342
- * - 18:小
343
- * - 25:标准
344
- * - 36:大
345
- */
346
- public fontsize: number = 25,
347
- /**
348
- * 颜色
349
- * @description 为DEC值(十进制RGB888值),默认白色
350
- * @default 16777215
351
- */
352
- public color: number = 16777215,
353
- /**
354
- * 发送者 senderID
355
- */
356
- public senderID: string = ID.fromNull().toString(),
357
- /**
358
- * 正文
359
- */
360
- public content: string = '',
361
- /**
362
- * 发送时间
363
- */
364
- // public ctime: bigint = BigInt(Date.now()),
365
- public ctime: Date = new Date(),
366
- /**
367
- * 权重 用于屏蔽等级 区间:[1,10]
368
- * @description 参考B站,源弹幕有该参数则直接利用,
369
- * 本实现默认取5,再经过ruleset匹配加减分数
370
- * @description 为0时表示暂时未计算权重
371
- */
372
- public weight: number = 0,
373
- /**
374
- * 弹幕池 0:普通池 1:字幕池 2:特殊池(代码/BAS弹幕) 3:互动池(互动弹幕中选择投票快速发送的弹幕)
375
- */
376
- public pool: Pools = Pools.Def,
377
- /**
378
- * 弹幕属性位(bin求AND)
379
- * bit0:保护 bit1:直播 bit2:高赞
380
- */
381
- public attr: DMAttr[] = [],
382
- /**
383
- * 初始来源平台
384
- * `danuni`与任意空值(可隐式转换为false的值)等价
385
- */
386
- public platform?: PlatformDanmakuSource | string,
387
- /**
388
- * 弹幕原始数据(不推荐使用)
389
- * @description 适用于无法解析的B站代码弹幕、Artplayer弹幕样式等
390
- * @description 初步约定:
391
- * - Artplayer: style不为空时,将其JSON.stringify()存入
392
- */
393
- public extraStr?: string,
394
- public DMID?: string,
395
- ) {
396
- //TODO 引入class-validator
397
- if (progress < 0) this.progress = 0
398
- if (mode < Modes.Normal || mode > Modes.Ext) this.mode = Modes.Normal
399
- if (fontsize <= 0) this.fontsize = 25
400
- if (color <= 0) this.color = 16777215 //虽然不知道为0是否为可用值,但过为少见,利用其作为默认位
401
- // if (ctime <= 0n) this.ctime = BigInt(Date.now())
402
- if (weight < 0 || weight > 10) this.weight = 5
403
- if (pool < Pools.Def || pool > Pools.Ix) this.pool = Pools.Def
404
- // if (attr < 0 || attr > 0b111) this.attr = 0
405
- if (!DMID) DMID = this.toDMID()
342
+ @IsEmail({ require_tld: false })
343
+ @IsString()
344
+ @IsNotEmpty()
345
+ @Expose()
346
+ public SOID: string = ID.fromNull().toString()
347
+ /**
348
+ * 弹幕出现位置(单位s;精度为ms,即保留三位小数)
349
+ */
350
+ @Min(0)
351
+ @IsNumber()
352
+ @IsNotEmpty()
353
+ @Expose()
354
+ public progress: number = 0
355
+ /**
356
+ * 弹幕类型
357
+ */
358
+ @IsEnum(Modes)
359
+ @IsNotEmpty()
360
+ @Expose()
361
+ public mode: Modes = Modes.Normal
362
+ /**
363
+ * 字号
364
+ * @default 25
365
+ * - 12
366
+ * - 16
367
+ * - 18:小
368
+ * - 25:标准
369
+ * - 36:大
370
+ * - 45
371
+ * - 64
372
+ */
373
+ @Max(64)
374
+ @Min(12)
375
+ @IsNumber()
376
+ @IsNotEmpty()
377
+ @Expose()
378
+ public fontsize: number = 25
379
+ /**
380
+ * 颜色
381
+ * @description 为DEC值(十进制RGB888值),默认白色
382
+ * @default 16777215
383
+ */
384
+ @IsNumber()
385
+ @IsNotEmpty()
386
+ @Expose()
387
+ public color: number = 16777215
388
+ /**
389
+ * 发送者 senderID
390
+ */
391
+ @IsEmail({ require_tld: false })
392
+ @IsString()
393
+ @IsNotEmpty()
394
+ @Expose()
395
+ public senderID: string = ID.fromNull().toString()
396
+ /**
397
+ * 正文
398
+ */
399
+ @IsString()
400
+ @IsNotEmpty()
401
+ @Expose()
402
+ public content: string = ''
403
+ /**
404
+ * 发送时间
405
+ */
406
+ @IsDate()
407
+ @IsNotEmpty()
408
+ @Expose()
409
+ public ctime: Date = new Date()
410
+ /**
411
+ * 权重 用于屏蔽等级 区间:[0,11]
412
+ * @description 参考B站,源弹幕有该参数则直接利用,
413
+ * 本实现默认取5,再经过ruleset匹配加减分数
414
+ * @description 为0时表示暂时未计算权重
415
+ */
416
+ @Max(11)
417
+ @Min(0)
418
+ @IsInt()
419
+ @IsNotEmpty()
420
+ @Expose()
421
+ public weight: number = 0
422
+ /**
423
+ * 弹幕池 0:普通池 1:字幕池 2:特殊池(代码/BAS弹幕) 3:互动池(互动弹幕中选择投票快速发送的弹幕)
424
+ */
425
+ @IsEnum(Pools)
426
+ @IsNotEmpty()
427
+ @Expose()
428
+ public pool: Pools = Pools.Def
429
+ /**
430
+ * 弹幕属性位(bin求AND)
431
+ * bit0:保护 bit1:直播 bit2:高赞
432
+ */
433
+ @IsEnum(DMAttr, { each: true })
434
+ @IsNotEmpty()
435
+ @Expose()
436
+ public attr: DMAttr[] = []
437
+ /**
438
+ * 初始来源平台
439
+ * `danuni`与任意空值(可隐式转换为false的值)等价
440
+ */
441
+ @IsString()
442
+ @IsOptional()
443
+ @Expose()
444
+ public platform?: PlatformDanmakuSource | string
445
+ /**
446
+ * 弹幕原始数据(不推荐使用)
447
+ * @description 适用于无法解析的B站代码弹幕、Artplayer弹幕样式等
448
+ * @description 初步约定:
449
+ * - Artplayer: style不为空时,将其JSON.stringify()存入
450
+ */
451
+ @IsString()
452
+ @IsOptional()
453
+ @Expose()
454
+ public extraStr?: string
455
+ @IsString()
456
+ @IsOptional()
457
+ @Expose()
458
+ public DMID?: string
459
+ @Expose()
460
+ init() {
461
+ const def = new UniDM()
462
+ if (!this.SOID) this.SOID = def.SOID
463
+ if (!this.progress) this.progress = def.progress
464
+ if (!this.mode) this.mode = def.mode
465
+ if (!this.fontsize) this.fontsize = def.mode
466
+ if (!this.color) this.color = def.color
467
+ if (!this.senderID) this.senderID = def.senderID
468
+ if (!this.content) this.content = def.content
469
+ if (!this.ctime) this.ctime = def.ctime
470
+ if (!this.weight) this.weight = def.weight
471
+ if (!this.pool) this.pool = def.pool
472
+ if (!this.attr) this.attr = def.attr
406
473
 
407
- this.progress = Number.parseFloat(progress.toFixed(3))
408
- if (extraStr)
409
- this.extraStr = JSON.stringify(cleanEmptyObjects(JSON.parse(extraStr)))
410
- if (extraStr === '{}') this.extraStr = undefined
474
+ if (!this.DMID) this.DMID = this.toDMID()
475
+ this.progress = Number.parseFloat(this.progress.toFixed(3))
476
+ if (this.extraStr)
477
+ this.extraStr = JSON.stringify(
478
+ cleanEmptyObjects(JSON.parse(this.extraStr)),
479
+ )
480
+ if (this.extraStr === '{}') this.extraStr = undefined
481
+ else if (this.mode !== Modes.Ext) {
482
+ const checkExtraBili = (obj?: ExtraBili) =>
483
+ obj
484
+ ? (['adv', 'bas', 'code', 'command'] as (keyof ExtraBili)[]).some(
485
+ (k) => obj[k],
486
+ )
487
+ : false
488
+ if (
489
+ this.extra.artplayer ||
490
+ this.extra.danuni?.chapter ||
491
+ checkExtraBili(this.extra.bili)
492
+ )
493
+ this.mode = Modes.Ext
494
+ }
495
+ return this
411
496
  }
412
- static create(args?: Partial<UniDMObj>) {
413
- return args
414
- ? new UniDM(
415
- args.SOID || ID.fromNull().toString(),
416
- args.progress,
417
- args.mode,
418
- args.fontsize,
419
- args.color,
420
- args.senderID,
421
- args.content,
422
- args.ctime,
423
- args.weight,
424
- args.pool,
425
- args.attr,
426
- args.platform,
427
- typeof args.extra === 'object'
428
- ? JSON.stringify(args.extra)
429
- : args.extra || args.extraStr,
430
- args.DMID,
431
- )
432
- : new UniDM(ID.fromNull().toString())
497
+ @Expose()
498
+ async validate() {
499
+ return validateOrReject(this)
433
500
  }
501
+ static create(pjson?: Partial<UniDMObj>) {
502
+ return pjson
503
+ ? plainToInstance(
504
+ UniDM,
505
+ pjson.extra
506
+ ? {
507
+ ...pjson,
508
+ extraStr: pjson.extra
509
+ ? JSON.stringify(pjson.extra)
510
+ : pjson.extraStr,
511
+ }
512
+ : pjson,
513
+ { excludeExtraneousValues: true },
514
+ ).init()
515
+ : new UniDM()
516
+ }
517
+ @Expose()
434
518
  get extra(): Extra {
435
519
  const extra = JSON.parse(this.extraStr || '{}')
436
520
  // this.extraStr = JSON.stringify(cleanEmptyObjects(extra))
437
521
  return extra
438
522
  // return cleanEmptyObjects(extra) as Extra
439
523
  }
524
+ @Expose()
440
525
  get isFrom3rdPlatform() {
441
526
  if (
442
527
  this.platform &&
@@ -450,10 +535,16 @@ export class UniDM {
450
535
  * @description sha3-256(content+senderID+ctime)截取前8位
451
536
  * @description 同一SOID下唯一
452
537
  */
538
+ @Expose()
453
539
  toDMID() {
454
- return createDMID(this.content, this.senderID, this.ctime)
540
+ return createDMID(this.content, this.senderID, this.ctime, this.extraStr)
455
541
  }
456
- isSameAs(dan: UniDM, _check2 = false): boolean {
542
+ @Expose()
543
+ isSameAs(dan: UniDM): boolean {
544
+ // 不支持比较高级弹幕
545
+ if (this.mode === Modes.Ext || dan.mode === Modes.Ext) return false
546
+ // 合并过视为不同,防止存在合并完成弹幕后再次合并造成计数错误
547
+ if (this.extra.danuni?.merge || dan.extra.danuni?.merge) return false
457
548
  const isSame = (k: keyof UniDMObj) => this[k] === dan[k],
458
549
  checks = (
459
550
  [
@@ -464,39 +555,10 @@ export class UniDM {
464
555
  'pool',
465
556
  ] satisfies (keyof UniDMObj)[]
466
557
  ).every((k) => isSame(k))
467
- // 如果两个对象的extra都是空对象,只检查基本字段
468
- if (
469
- JSON.stringify(this.extra) === '{}' &&
470
- JSON.stringify(dan.extra) === '{}'
471
- ) {
472
- return checks
473
- }
474
- // 特殊情况:只包含danuni.merge的情况
475
- const thisHasOnlyMerge =
476
- this.extra.danuni?.merge &&
477
- !this.extra.artplayer &&
478
- !this.extra.bili &&
479
- !this.extra.danuni.chapter
480
- const danHasOnlyMerge =
481
- dan.extra.danuni?.merge &&
482
- !dan.extra.artplayer &&
483
- !dan.extra.bili &&
484
- !dan.extra.danuni.chapter
485
- if (thisHasOnlyMerge && danHasOnlyMerge) {
486
- return checks
487
- }
488
- if (_check2) {
489
- return isSame('extraStr') && checks
490
- }
491
- const a = { ...this.extra }
492
- const b = { ...dan.extra }
493
- if (a.danuni) delete a.danuni.merge
494
- if (b.danuni) delete b.danuni.merge
495
- return UniDM.create({ ...a, extraStr: JSON.stringify(a) }).isSameAs(
496
- UniDM.create({ ...b, extraStr: JSON.stringify(b) }),
497
- true,
498
- )
558
+ // 忽略使用了extra字段却不在mode里标记的弹幕
559
+ return checks
499
560
  }
561
+ @Expose()
500
562
  minify() {
501
563
  type UObj = Partial<UniDMObj> & Pick<UniDMObj, 'SOID'>
502
564
  const def: UObj = UniDM.create(),
@@ -516,6 +578,7 @@ export class UniDM {
516
578
  }
517
579
  return JSON.parse(JSON.stringify(dan)) as UObj
518
580
  }
581
+ @Expose()
519
582
  downgradeAdvcancedDan(
520
583
  {
521
584
  include,
@@ -561,7 +624,7 @@ export class UniDM {
561
624
  }
562
625
  }
563
626
  clone.senderID = 'compat[bot]@dan-any'
564
- clone.attr.push('Compatible')
627
+ clone.attr.push(DMAttr.Compatible)
565
628
  if (cleanExtra) clone.extraStr = undefined
566
629
  return clone
567
630
  }
@@ -655,7 +718,7 @@ export class UniDM {
655
718
  bili: ExtraBili
656
719
  }
657
720
  if (args.oid && !cid) cid = args.oid
658
- const SOID = `def::${ID.fromBili({ cid })}`,
721
+ const SOID = `def_${PlatformVideoSource.Bilibili}+${ID.fromBili({ cid })}`,
659
722
  senderID = ID.fromBili({ midHash: args.midHash })
660
723
  let mode = Modes.Normal
661
724
  const pool = args.pool, //暂时不做处理,兼容bili的pool格式
@@ -707,11 +770,14 @@ export class UniDM {
707
770
  // 需改进,7=>advanced 8=>code 9=>bas 互动=>command
708
771
  // 同时塞进无法/无需直接解析的数据
709
772
  // 另开一个解析器,为大部分播放器(无法解析该类dm)做文本类型降级处理
710
- extra:
711
- args.mode >= 7 ? JSON.stringify(extra, BigIntSerializer) : undefined,
773
+ extra,
712
774
  })
713
775
  }
714
- toBiliXML() {
776
+ @Expose()
777
+ toBiliXML(options?: { skipBiliCommand: boolean }) {
778
+ if (options?.skipBiliCommand && this.extra.bili?.command) {
779
+ return null
780
+ }
715
781
  const recMode = (mode: Modes, extra?: ExtraBili) => {
716
782
  switch (mode) {
717
783
  case Modes.Normal:
@@ -779,16 +845,13 @@ export class UniDM {
779
845
  ctime: new Date(`${args.ctime} GMT+0800`), // 无视本地时区,按照B站的东8区计算时间
780
846
  weight: 10,
781
847
  pool: Pools.Adv,
782
- attr: ['Protect'],
848
+ attr: [DMAttr.Protect],
783
849
  platform: PlatformVideoSource.Bilibili,
784
- extra: JSON.stringify(
785
- {
786
- bili: {
787
- command: args,
788
- },
850
+ extra: {
851
+ bili: {
852
+ command: args,
789
853
  },
790
- BigIntSerializer,
791
- ),
854
+ },
792
855
  })
793
856
  }
794
857
  static fromDplayer(args: DMDplayer, playerID: string, domain: string) {
@@ -806,6 +869,7 @@ export class UniDM {
806
869
  platform: domain,
807
870
  })
808
871
  }
872
+ @Expose()
809
873
  toDplayer(): DMDplayer {
810
874
  let mode = 0
811
875
  if (this.mode === Modes.Top) mode = 1
@@ -842,9 +906,10 @@ export class UniDM {
842
906
  senderID: senderID.toString(),
843
907
  // content: args.content,
844
908
  platform: domain,
845
- extra: JSON.stringify(extra, BigIntSerializer), //optional BigINt parser
909
+ extra, //optional BigINt parser
846
910
  })
847
911
  }
912
+ @Expose()
848
913
  toArtplayer(): DMArtplayer {
849
914
  let mode = 0
850
915
  if (this.mode === Modes.Top) mode = 1
@@ -876,6 +941,7 @@ export class UniDM {
876
941
  DMID: args.cid.toString(), //无需 new ID() 获取带suffix的ID
877
942
  })
878
943
  }
944
+ @Expose()
879
945
  toDDplay(): DMDDplay {
880
946
  let mode = 1
881
947
  if (this.mode === Modes.Top) mode = 5
@@ -66,10 +66,13 @@ export function createDMID(
66
66
  content: string = '',
67
67
  senderID: string,
68
68
  ctime: ctime,
69
+ extraStr?: string,
69
70
  slice = 8,
70
71
  ) {
71
72
  return new jsSHA('SHA3-256', 'TEXT')
72
- .update(content + senderID + UniDM.transCtime(ctime).toISOString())
73
+ .update(
74
+ `${content}|${senderID}|${UniDM.transCtime(ctime).toISOString()}|${extraStr}`,
75
+ )
73
76
  .getHash('HEX')
74
77
  .slice(0, slice)
75
78
  }
package/tsconfig.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "compilerOptions": {
3
3
  /* Visit https://aka.ms/tsconfig to read more about this file */
4
4
  /* Projects */
5
- // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
5
+ "incremental": true /* Save .tsbuildinfo files to allow for incremental compilation of projects. */,
6
6
  // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
7
7
  // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
8
8
  // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
@@ -10,10 +10,10 @@
10
10
  // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
11
11
  /* Language and Environment */
12
12
  "target": "ES2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
13
+ "emitDecoratorMetadata": true /* Emit design-type metadata for decorated declarations in source files. */,
13
14
  // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
14
15
  // "jsx": "preserve", /* Specify what JSX code is generated. */
15
- // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
16
- // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
16
+ "experimentalDecorators": true /* Enable experimental support for legacy experimental decorators. */,
17
17
  // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
18
18
  // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
19
19
  // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
@@ -0,0 +1 @@
1
+ {"version":"5.8.3"}