@dan-uni/dan-any 0.5.0 → 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') {
@@ -60,23 +74,24 @@ const toBits = (number: number) => {
60
74
  return bits.reverse()
61
75
  }
62
76
 
63
- export type DMAttr =
64
- | 'Protect'
65
- | 'FromLive'
66
- | 'HighLike'
67
- | 'Compatible' // 由dan-any进行过兼容处理的弹幕,可能丢失部分信息
68
- | 'Reported' // 在DanUni上被多人举报过的弹幕
69
- | 'Unchecked' // 在DanUni上未被审核过的弹幕
70
- | 'HasEvent' // 该弹幕当前在DanUni上存在事件(如点赞/举报等)
71
- | '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
+ }
72
87
  const DMAttrUtils = {
73
88
  fromBin(bin: number = 0, format?: PlatformDanmakuSource) {
74
89
  const array = toBits(bin),
75
90
  attr: DMAttr[] = []
76
91
  if (format === 'bili') {
77
- if (array[0]) attr.push('Protect')
78
- if (array[1]) attr.push('FromLive')
79
- 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)
80
95
  }
81
96
  return attr
82
97
  },
@@ -91,9 +106,9 @@ const DMAttrUtils = {
91
106
  ) {
92
107
  const bin = new SetBin(0)
93
108
  if (format === 'bili') {
94
- if (attr.includes('Protect')) bin.set1(0)
95
- if (attr.includes('FromLive')) bin.set1(1)
96
- 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)
97
112
  }
98
113
  return bin.bin
99
114
  },
@@ -319,133 +334,194 @@ export interface UniDMObj {
319
334
  DMID: string
320
335
  }
321
336
 
322
- /**
323
- * 所有 number/bigint 值设为0自动转换为默认
324
- */
325
337
  export class UniDM {
326
338
  /**
327
- * 同步时确认位置的参数
339
+ * 资源ID
340
+ * @description 由某一danuni服务确定的某一剧集下不同资源(不同视频站/字幕组具有细节差异)的ID
328
341
  */
329
- // syncAnchor = BigInt(Date.now())
330
- constructor(
331
- /**
332
- * 资源ID
333
- * @description 由某一danuni服务确定的某一剧集下不同资源(不同视频站/字幕组具有细节差异)的ID
334
- */
335
- public SOID: string,
336
- /**
337
- * 弹幕出现位置(单位s;精度为ms,即保留三位小数)
338
- */
339
- public progress: number = 0,
340
- /**
341
- * 弹幕类型
342
- */
343
- public mode: Modes = Modes.Normal,
344
- /**
345
- * 字号
346
- * @default 25
347
- * - 12
348
- * - 16
349
- * - 18:小
350
- * - 25:标准
351
- * - 36:大
352
- * - 45
353
- * - 64
354
- */
355
- public fontsize: number = 25,
356
- /**
357
- * 颜色
358
- * @description 为DEC值(十进制RGB888值),默认白色
359
- * @default 16777215
360
- */
361
- public color: number = 16777215,
362
- /**
363
- * 发送者 senderID
364
- */
365
- public senderID: string = ID.fromNull().toString(),
366
- /**
367
- * 正文
368
- */
369
- public content: string = '',
370
- /**
371
- * 发送时间
372
- */
373
- // public ctime: bigint = BigInt(Date.now()),
374
- public ctime: Date = new Date(),
375
- /**
376
- * 权重 用于屏蔽等级 区间:[1,10]
377
- * @description 参考B站,源弹幕有该参数则直接利用,
378
- * 本实现默认取5,再经过ruleset匹配加减分数
379
- * @description 为0时表示暂时未计算权重
380
- */
381
- public weight: number = 0,
382
- /**
383
- * 弹幕池 0:普通池 1:字幕池 2:特殊池(代码/BAS弹幕) 3:互动池(互动弹幕中选择投票快速发送的弹幕)
384
- */
385
- public pool: Pools = Pools.Def,
386
- /**
387
- * 弹幕属性位(bin求AND)
388
- * bit0:保护 bit1:直播 bit2:高赞
389
- */
390
- public attr: DMAttr[] = [],
391
- /**
392
- * 初始来源平台
393
- * `danuni`与任意空值(可隐式转换为false的值)等价
394
- */
395
- public platform?: PlatformDanmakuSource | string,
396
- /**
397
- * 弹幕原始数据(不推荐使用)
398
- * @description 适用于无法解析的B站代码弹幕、Artplayer弹幕样式等
399
- * @description 初步约定:
400
- * - Artplayer: style不为空时,将其JSON.stringify()存入
401
- */
402
- public extraStr?: string,
403
- public DMID?: string,
404
- ) {
405
- //TODO 引入class-validator
406
- if (progress < 0) this.progress = 0
407
- if (mode < Modes.Normal || mode > Modes.Ext) this.mode = Modes.Normal
408
- if (fontsize < 10 || fontsize > 127) this.fontsize = 25
409
- if (color <= 0) this.color = 16777215 //虽然不知道为0是否为可用值,但过为少见,利用其作为默认位
410
- // if (ctime <= 0n) this.ctime = BigInt(Date.now())
411
- if (weight < 0 || weight > 11) this.weight = 5
412
- if (pool < Pools.Def || pool > Pools.Ix) this.pool = Pools.Def
413
- // if (attr < 0 || attr > 0b111) this.attr = 0
414
- 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
415
473
 
416
- this.progress = Number.parseFloat(progress.toFixed(3))
417
- if (extraStr)
418
- this.extraStr = JSON.stringify(cleanEmptyObjects(JSON.parse(extraStr)))
419
- 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
420
496
  }
421
- static create(args?: Partial<UniDMObj>) {
422
- return args
423
- ? new UniDM(
424
- args.SOID || ID.fromNull().toString(),
425
- args.progress,
426
- args.mode,
427
- args.fontsize,
428
- args.color,
429
- args.senderID,
430
- args.content,
431
- args.ctime,
432
- args.weight,
433
- args.pool,
434
- args.attr,
435
- args.platform,
436
- typeof args.extra === 'object'
437
- ? JSON.stringify(args.extra)
438
- : args.extra || args.extraStr,
439
- args.DMID,
440
- )
441
- : new UniDM(ID.fromNull().toString())
497
+ @Expose()
498
+ async validate() {
499
+ return validateOrReject(this)
442
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()
443
518
  get extra(): Extra {
444
519
  const extra = JSON.parse(this.extraStr || '{}')
445
520
  // this.extraStr = JSON.stringify(cleanEmptyObjects(extra))
446
521
  return extra
447
522
  // return cleanEmptyObjects(extra) as Extra
448
523
  }
524
+ @Expose()
449
525
  get isFrom3rdPlatform() {
450
526
  if (
451
527
  this.platform &&
@@ -459,10 +535,16 @@ export class UniDM {
459
535
  * @description sha3-256(content+senderID+ctime)截取前8位
460
536
  * @description 同一SOID下唯一
461
537
  */
538
+ @Expose()
462
539
  toDMID() {
463
- return createDMID(this.content, this.senderID, this.ctime)
540
+ return createDMID(this.content, this.senderID, this.ctime, this.extraStr)
464
541
  }
465
- 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
466
548
  const isSame = (k: keyof UniDMObj) => this[k] === dan[k],
467
549
  checks = (
468
550
  [
@@ -473,39 +555,10 @@ export class UniDM {
473
555
  'pool',
474
556
  ] satisfies (keyof UniDMObj)[]
475
557
  ).every((k) => isSame(k))
476
- // 如果两个对象的extra都是空对象,只检查基本字段
477
- if (
478
- JSON.stringify(this.extra) === '{}' &&
479
- JSON.stringify(dan.extra) === '{}'
480
- ) {
481
- return checks
482
- }
483
- // 特殊情况:只包含danuni.merge的情况
484
- const thisHasOnlyMerge =
485
- this.extra.danuni?.merge &&
486
- !this.extra.artplayer &&
487
- !this.extra.bili &&
488
- !this.extra.danuni.chapter
489
- const danHasOnlyMerge =
490
- dan.extra.danuni?.merge &&
491
- !dan.extra.artplayer &&
492
- !dan.extra.bili &&
493
- !dan.extra.danuni.chapter
494
- if (thisHasOnlyMerge && danHasOnlyMerge) {
495
- return checks
496
- }
497
- if (_check2) {
498
- return isSame('extraStr') && checks
499
- }
500
- const a = { ...this.extra }
501
- const b = { ...dan.extra }
502
- if (a.danuni) delete a.danuni.merge
503
- if (b.danuni) delete b.danuni.merge
504
- return UniDM.create({ ...a, extraStr: JSON.stringify(a) }).isSameAs(
505
- UniDM.create({ ...b, extraStr: JSON.stringify(b) }),
506
- true,
507
- )
558
+ // 忽略使用了extra字段却不在mode里标记的弹幕
559
+ return checks
508
560
  }
561
+ @Expose()
509
562
  minify() {
510
563
  type UObj = Partial<UniDMObj> & Pick<UniDMObj, 'SOID'>
511
564
  const def: UObj = UniDM.create(),
@@ -525,6 +578,7 @@ export class UniDM {
525
578
  }
526
579
  return JSON.parse(JSON.stringify(dan)) as UObj
527
580
  }
581
+ @Expose()
528
582
  downgradeAdvcancedDan(
529
583
  {
530
584
  include,
@@ -570,7 +624,7 @@ export class UniDM {
570
624
  }
571
625
  }
572
626
  clone.senderID = 'compat[bot]@dan-any'
573
- clone.attr.push('Compatible')
627
+ clone.attr.push(DMAttr.Compatible)
574
628
  if (cleanExtra) clone.extraStr = undefined
575
629
  return clone
576
630
  }
@@ -664,7 +718,7 @@ export class UniDM {
664
718
  bili: ExtraBili
665
719
  }
666
720
  if (args.oid && !cid) cid = args.oid
667
- const SOID = `def::${ID.fromBili({ cid })}`,
721
+ const SOID = `def_${PlatformVideoSource.Bilibili}+${ID.fromBili({ cid })}`,
668
722
  senderID = ID.fromBili({ midHash: args.midHash })
669
723
  let mode = Modes.Normal
670
724
  const pool = args.pool, //暂时不做处理,兼容bili的pool格式
@@ -716,11 +770,14 @@ export class UniDM {
716
770
  // 需改进,7=>advanced 8=>code 9=>bas 互动=>command
717
771
  // 同时塞进无法/无需直接解析的数据
718
772
  // 另开一个解析器,为大部分播放器(无法解析该类dm)做文本类型降级处理
719
- extra:
720
- args.mode >= 7 ? JSON.stringify(extra, BigIntSerializer) : undefined,
773
+ extra,
721
774
  })
722
775
  }
723
- toBiliXML() {
776
+ @Expose()
777
+ toBiliXML(options?: { skipBiliCommand: boolean }) {
778
+ if (options?.skipBiliCommand && this.extra.bili?.command) {
779
+ return null
780
+ }
724
781
  const recMode = (mode: Modes, extra?: ExtraBili) => {
725
782
  switch (mode) {
726
783
  case Modes.Normal:
@@ -788,16 +845,13 @@ export class UniDM {
788
845
  ctime: new Date(`${args.ctime} GMT+0800`), // 无视本地时区,按照B站的东8区计算时间
789
846
  weight: 10,
790
847
  pool: Pools.Adv,
791
- attr: ['Protect'],
848
+ attr: [DMAttr.Protect],
792
849
  platform: PlatformVideoSource.Bilibili,
793
- extra: JSON.stringify(
794
- {
795
- bili: {
796
- command: args,
797
- },
850
+ extra: {
851
+ bili: {
852
+ command: args,
798
853
  },
799
- BigIntSerializer,
800
- ),
854
+ },
801
855
  })
802
856
  }
803
857
  static fromDplayer(args: DMDplayer, playerID: string, domain: string) {
@@ -815,6 +869,7 @@ export class UniDM {
815
869
  platform: domain,
816
870
  })
817
871
  }
872
+ @Expose()
818
873
  toDplayer(): DMDplayer {
819
874
  let mode = 0
820
875
  if (this.mode === Modes.Top) mode = 1
@@ -851,9 +906,10 @@ export class UniDM {
851
906
  senderID: senderID.toString(),
852
907
  // content: args.content,
853
908
  platform: domain,
854
- extra: JSON.stringify(extra, BigIntSerializer), //optional BigINt parser
909
+ extra, //optional BigINt parser
855
910
  })
856
911
  }
912
+ @Expose()
857
913
  toArtplayer(): DMArtplayer {
858
914
  let mode = 0
859
915
  if (this.mode === Modes.Top) mode = 1
@@ -885,6 +941,7 @@ export class UniDM {
885
941
  DMID: args.cid.toString(), //无需 new ID() 获取带suffix的ID
886
942
  })
887
943
  }
944
+ @Expose()
888
945
  toDDplay(): DMDDplay {
889
946
  let mode = 1
890
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"}