@juzi/wechaty-puppet-service 1.0.125 → 1.0.126

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.
Files changed (53) hide show
  1. package/dist/cjs/src/client/puppet-service.d.ts +0 -27
  2. package/dist/cjs/src/client/puppet-service.d.ts.map +1 -1
  3. package/dist/cjs/src/client/puppet-service.js +27 -228
  4. package/dist/cjs/src/client/puppet-service.js.map +1 -1
  5. package/dist/cjs/src/package-json.js +3 -3
  6. package/dist/esm/src/client/puppet-service.d.ts +0 -27
  7. package/dist/esm/src/client/puppet-service.d.ts.map +1 -1
  8. package/dist/esm/src/client/puppet-service.js +27 -228
  9. package/dist/esm/src/client/puppet-service.js.map +1 -1
  10. package/dist/esm/src/package-json.js +3 -3
  11. package/package.json +3 -3
  12. package/src/client/puppet-service.ts +30 -230
  13. package/src/package-json.ts +3 -3
  14. package/dist/cjs/tests/dirty-gen-guard.spec.d.ts +0 -3
  15. package/dist/cjs/tests/dirty-gen-guard.spec.d.ts.map +0 -1
  16. package/dist/cjs/tests/dirty-gen-guard.spec.js +0 -206
  17. package/dist/cjs/tests/dirty-gen-guard.spec.js.map +0 -1
  18. package/dist/cjs/tests/fast-dirty-completeness.spec.d.ts +0 -3
  19. package/dist/cjs/tests/fast-dirty-completeness.spec.d.ts.map +0 -1
  20. package/dist/cjs/tests/fast-dirty-completeness.spec.js +0 -59
  21. package/dist/cjs/tests/fast-dirty-completeness.spec.js.map +0 -1
  22. package/dist/cjs/tests/fast-dirty-room-member.spec.d.ts +0 -3
  23. package/dist/cjs/tests/fast-dirty-room-member.spec.d.ts.map +0 -1
  24. package/dist/cjs/tests/fast-dirty-room-member.spec.js +0 -179
  25. package/dist/cjs/tests/fast-dirty-room-member.spec.js.map +0 -1
  26. package/dist/cjs/tests/grpc-stream-dirty-order.spec.d.ts +0 -3
  27. package/dist/cjs/tests/grpc-stream-dirty-order.spec.d.ts.map +0 -1
  28. package/dist/cjs/tests/grpc-stream-dirty-order.spec.js +0 -135
  29. package/dist/cjs/tests/grpc-stream-dirty-order.spec.js.map +0 -1
  30. package/dist/cjs/tests/grpc-stream-dirty-parse.spec.d.ts +0 -3
  31. package/dist/cjs/tests/grpc-stream-dirty-parse.spec.d.ts.map +0 -1
  32. package/dist/cjs/tests/grpc-stream-dirty-parse.spec.js +0 -72
  33. package/dist/cjs/tests/grpc-stream-dirty-parse.spec.js.map +0 -1
  34. package/dist/esm/tests/dirty-gen-guard.spec.d.ts +0 -3
  35. package/dist/esm/tests/dirty-gen-guard.spec.d.ts.map +0 -1
  36. package/dist/esm/tests/dirty-gen-guard.spec.js +0 -178
  37. package/dist/esm/tests/dirty-gen-guard.spec.js.map +0 -1
  38. package/dist/esm/tests/fast-dirty-completeness.spec.d.ts +0 -3
  39. package/dist/esm/tests/fast-dirty-completeness.spec.d.ts.map +0 -1
  40. package/dist/esm/tests/fast-dirty-completeness.spec.js +0 -34
  41. package/dist/esm/tests/fast-dirty-completeness.spec.js.map +0 -1
  42. package/dist/esm/tests/fast-dirty-room-member.spec.d.ts +0 -3
  43. package/dist/esm/tests/fast-dirty-room-member.spec.d.ts.map +0 -1
  44. package/dist/esm/tests/fast-dirty-room-member.spec.js +0 -151
  45. package/dist/esm/tests/fast-dirty-room-member.spec.js.map +0 -1
  46. package/dist/esm/tests/grpc-stream-dirty-order.spec.d.ts +0 -3
  47. package/dist/esm/tests/grpc-stream-dirty-order.spec.d.ts.map +0 -1
  48. package/dist/esm/tests/grpc-stream-dirty-order.spec.js +0 -110
  49. package/dist/esm/tests/grpc-stream-dirty-order.spec.js.map +0 -1
  50. package/dist/esm/tests/grpc-stream-dirty-parse.spec.d.ts +0 -3
  51. package/dist/esm/tests/grpc-stream-dirty-parse.spec.d.ts.map +0 -1
  52. package/dist/esm/tests/grpc-stream-dirty-parse.spec.js +0 -47
  53. package/dist/esm/tests/grpc-stream-dirty-parse.spec.js.map +0 -1
@@ -110,21 +110,6 @@ class PuppetService extends PUPPET.Puppet {
110
110
 
111
111
  protected _payloadStore: PayloadStore
112
112
 
113
- /**
114
- * Per-roomId serialization chain for RoomMember compound dirty events.
115
- *
116
- * The compound RoomMember handler is a `get → mutate → set` sequence
117
- * across an async FlashStore. Two dirty events for the same roomId
118
- * (e.g. member-A and member-B, arriving back-to-back) would otherwise
119
- * both read the same snapshot and each write back with only their own
120
- * key removed -- losing one of the deletes.
121
- *
122
- * Keyed by roomId, each entry chains the pending handler run so a
123
- * follow-up dirty for the same room waits for the in-flight one to
124
- * finish. Entries self-clean after their tail resolves.
125
- */
126
- private _roomMemberDirtyChain: Map<string, Promise<void>> = new Map()
127
-
128
113
  private timeoutMilliseconds: number
129
114
 
130
115
  protected _grpcManager?: GrpcManager
@@ -383,41 +368,10 @@ class PuppetService extends PUPPET.Puppet {
383
368
  )
384
369
  }
385
370
  break
386
- case grpcPuppet.EventType.EVENT_TYPE_DIRTY: {
387
- const dirtyPayload = JSON.parse(payload) as PUPPET.payloads.EventDirty
388
- /**
389
- * Bump the generation counter *before* awaiting fastDirty so the
390
- * whole fastDirty window is covered: any raw fetch that started
391
- * before this dirty and resolves while fastDirty is deleting the
392
- * FlashStore row must be judged stale by `isFreshWrite` and skip
393
- * its write-back -- otherwise it re-poisons the row we just
394
- * cleared and the value only refreshes after a *second* dirty.
395
- *
396
- * Upstream cache-mixin.onDirty bumps the same (type, id) again
397
- * after `emit('dirty')` below; double-bumping is harmless because
398
- * the counter only moves forward and `isFreshWrite` compares by
399
- * equality.
400
- */
401
- this.cache.bumpGen(dirtyPayload.payloadType, dirtyPayload.payloadId)
402
- /**
403
- * A compound RoomMember dirty (`${roomId}${SEP}${memberId}`) must
404
- * also bump the bare roomId key: roomMember write-backs are
405
- * row-level read-modify-writes keyed by roomId, so a single-member
406
- * dirty has to move that key too or an in-flight roomMember fetch
407
- * could still write the whole row back stale.
408
- */
409
- if (dirtyPayload.payloadType === PUPPET.types.Dirty.RoomMember
410
- && dirtyPayload.payloadId.includes(PUPPET.STRING_SPLITTER)
411
- ) {
412
- const [ roomId ] = dirtyPayload.payloadId.split(PUPPET.STRING_SPLITTER)
413
- if (roomId) {
414
- this.cache.bumpGen(PUPPET.types.Dirty.RoomMember, roomId)
415
- }
416
- }
417
- await this.fastDirty(dirtyPayload)
418
- this.emit('dirty', dirtyPayload)
371
+ case grpcPuppet.EventType.EVENT_TYPE_DIRTY:
372
+ await this.fastDirty(JSON.parse(payload))
373
+ this.emit('dirty', JSON.parse(payload) as PUPPET.payloads.EventDirty)
419
374
  break
420
- }
421
375
  case grpcPuppet.EventType.EVENT_TYPE_MESSAGE:
422
376
  this.emit('message', JSON.parse(payload) as PUPPET.payloads.EventMessage)
423
377
  break
@@ -542,87 +496,6 @@ class PuppetService extends PUPPET.Puppet {
542
496
  }
543
497
  }
544
498
 
545
- /**
546
- * Dirty handler registry, keyed by every `PUPPET.types.Dirty` value.
547
- *
548
- * Using a full `Record<...>` (rather than `Partial<...>`) means
549
- * TypeScript catches a missing entry at compile time -- so when a
550
- * new DirtyType is added to wechaty-puppet, callers get a build
551
- * error instead of a silent runtime no-op.
552
- *
553
- * Note: `Unspecified` is treated as an upstream contract violation,
554
- * not a benign no-op -- it logs an error and emits 'error' so hosts
555
- * notice.
556
- */
557
- private get _dirtyHandlerMap (): Record<PUPPET.types.Dirty, (id: string) => Promise<unknown>> {
558
- return {
559
- [PUPPET.types.Dirty.Contact]: async (id: string) => this._payloadStore.contact?.delete(id),
560
- [PUPPET.types.Dirty.Friendship]: async (_: string) => {},
561
- [PUPPET.types.Dirty.Message]: async (_: string) => {},
562
- [PUPPET.types.Dirty.Post]: async (_: string) => {},
563
- [PUPPET.types.Dirty.Room]: async (id: string) => this._payloadStore.room?.delete(id),
564
- [PUPPET.types.Dirty.RoomMember]: async (id: string) => {
565
- const [ roomId, memberId ] = id.split(PUPPET.STRING_SPLITTER)
566
- if (!roomId) {
567
- return
568
- }
569
- const store = this._payloadStore.roomMember
570
- if (!store) {
571
- return
572
- }
573
- // Bare roomId: the whole member set is stale, drop the row.
574
- // Row-level delete is idempotent so it does not need serialization.
575
- if (memberId === undefined) {
576
- await store.delete(roomId)
577
- return
578
- }
579
- // Compound id: `get → mutate → set` is a read-modify-write across
580
- // an async store. Serialize per roomId so two concurrent compound
581
- // dirties don't each read the same snapshot and each drop only
582
- // their own key -- which would lose one of the two deletes.
583
- const previous = this._roomMemberDirtyChain.get(roomId) ?? Promise.resolve()
584
- const next = (async () => {
585
- // Swallow the previous link's rejection so a failure upstream
586
- // does not silently skip our own mutation. The prior handler
587
- // already reported its error via fastDirty()'s try/catch.
588
- try {
589
- await previous
590
- } catch (_) { /* ignore, upstream already reported */ }
591
- const current = await store.get(roomId)
592
- if (!current || !Object.prototype.hasOwnProperty.call(current, memberId)) {
593
- return
594
- }
595
- const { [memberId]: _drop, ...rest } = current
596
- if (Object.keys(rest).length === 0) {
597
- await store.delete(roomId)
598
- } else {
599
- await store.set(roomId, rest)
600
- }
601
- })()
602
- this._roomMemberDirtyChain.set(roomId, next)
603
- try {
604
- await next
605
- } finally {
606
- // Only clear the slot if we are still the tail; otherwise a
607
- // later handler has already extended the chain and owns cleanup.
608
- if (this._roomMemberDirtyChain.get(roomId) === next) {
609
- this._roomMemberDirtyChain.delete(roomId)
610
- }
611
- }
612
- },
613
- [PUPPET.types.Dirty.Tag]: async (id: string) => this._payloadStore.tag?.delete(id),
614
- [PUPPET.types.Dirty.TagGroup]: async (id: string) => this._payloadStore.tagGroup?.delete(id),
615
- [PUPPET.types.Dirty.WxxdProduct]: async (_: string) => {},
616
- [PUPPET.types.Dirty.WxxdOrder]: async (_: string) => {},
617
- [PUPPET.types.Dirty.Call]: async (_: string) => {},
618
- [PUPPET.types.Dirty.Unspecified]: async (id: string) => {
619
- const msg = `fastDirty() received Unspecified dirty type (id=${id}); upstream puppet is leaking protobuf default — this is a server-side contract bug`
620
- this.log.error('PuppetService', msg)
621
- this.emit('error', new Error(msg))
622
- },
623
- }
624
- }
625
-
626
499
  /**
627
500
  * `onDirty()` is called when the puppet emit `dirty` event.
628
501
  * the event listener will be registered in `start()` from the `PuppetAbstract` class
@@ -635,18 +508,20 @@ class PuppetService extends PUPPET.Puppet {
635
508
  ): Promise<void> {
636
509
  this.log.verbose('PuppetService', 'fastDirty(%s<%s>, %s)', PUPPET.types.Dirty[payloadType], payloadType, payloadId)
637
510
 
638
- // payloadType is typed as the enum, but at runtime the server may emit a
639
- // value outside our enum (forward-compat). Look it up via a
640
- // possibly-undefined view so the runtime guard stays meaningful.
641
- const lookup = this._dirtyHandlerMap as Partial<Record<PUPPET.types.Dirty, (id: string) => Promise<unknown>>>
642
- const handler = lookup[payloadType]
643
- if (!handler) {
644
- this.log.warn('PuppetService', 'fastDirty() no handler for payloadType=%s', payloadType)
645
- return
511
+ const dirtyMap: Partial<Record<PUPPET.types.Dirty, (id: string) => Promise<unknown>>> = {
512
+ [PUPPET.types.Dirty.Contact]: async (id: string) => this._payloadStore.contact?.delete(id),
513
+ [PUPPET.types.Dirty.Friendship]: async (_: string) => {},
514
+ [PUPPET.types.Dirty.Message]: async (_: string) => {},
515
+ [PUPPET.types.Dirty.Post]: async (_: string) => {},
516
+ [PUPPET.types.Dirty.Room]: async (id: string) => this._payloadStore.room?.delete(id),
517
+ [PUPPET.types.Dirty.RoomMember]: async (id: string) => this._payloadStore.roomMember?.delete(id),
518
+ [PUPPET.types.Dirty.Tag]: async (id: string) => this._payloadStore.tag?.delete(id),
519
+ [PUPPET.types.Dirty.TagGroup]: async (id: string) => this._payloadStore.tagGroup?.delete(id),
520
+ [PUPPET.types.Dirty.Unspecified]: async (id: string) => { throw new Error('Unspecified type with id: ' + id) },
646
521
  }
647
522
 
648
523
  try {
649
- await handler(payloadId)
524
+ await dirtyMap[payloadType]?.(payloadId)
650
525
  } catch (error) {
651
526
  this.emit('error', error)
652
527
  }
@@ -884,10 +759,6 @@ class PuppetService extends PUPPET.Puppet {
884
759
  return cachedPayload
885
760
  }
886
761
 
887
- // Snapshot the gen before the raw fetch so a dirty that lands while
888
- // the gRPC round-trip is in flight makes the write-back below stale.
889
- const gen = this.cache.snapshotGen(PUPPET.types.Dirty.Contact, id)
890
-
891
762
  const request = new grpcPuppet.ContactPayloadRequest()
892
763
  request.setId(id)
893
764
 
@@ -930,12 +801,8 @@ class PuppetService extends PUPPET.Puppet {
930
801
  aka : response.getAka(),
931
802
  }
932
803
 
933
- if (this.cache.isFreshWrite(PUPPET.types.Dirty.Contact, id, gen)) {
934
- await this._payloadStore.contact?.set(id, payload)
935
- this.log.silly('PuppetService', 'contactRawPayload(%s) cache SET', id)
936
- } else {
937
- this.log.silly('PuppetService', 'contactRawPayload(%s) cache SET skipped: dirty landed during raw fetch', id)
938
- }
804
+ await this._payloadStore.contact?.set(id, payload)
805
+ this.log.silly('PuppetService', 'contactRawPayload(%s) cache SET', id)
939
806
 
940
807
  return payload
941
808
  }
@@ -963,14 +830,6 @@ class PuppetService extends PUPPET.Puppet {
963
830
 
964
831
  if (needGetSet.size > 0) {
965
832
  try {
966
- // Snapshot each id's gen before the batch fetch; write-backs below
967
- // are validated per id so a dirty on one contact mid-flight only
968
- // skips that contact's stale write, not the whole batch.
969
- const genSnap = new Map<string, number>()
970
- for (const contactId of needGetSet) {
971
- genSnap.set(contactId, this.cache.snapshotGen(PUPPET.types.Dirty.Contact, contactId))
972
- }
973
-
974
833
  const request = new grpcPuppet.BatchContactPayloadRequest()
975
834
  request.setIdsList(Array.from(needGetSet))
976
835
 
@@ -984,11 +843,7 @@ class PuppetService extends PUPPET.Puppet {
984
843
  const contactId = payload.getId()
985
844
  const puppetPayload = contactPbToPayload(payload)
986
845
  result.set(contactId, puppetPayload)
987
- if (this.cache.isFreshWrite(PUPPET.types.Dirty.Contact, contactId, genSnap.get(contactId) ?? 0)) {
988
- await this._payloadStore.contact?.set(contactId, puppetPayload)
989
- } else {
990
- this.log.silly('PuppetService', 'batchContactRawPayload(%s) cache SET skipped: dirty landed during raw fetch', contactId)
991
- }
846
+ await this._payloadStore.contact?.set(contactId, puppetPayload)
992
847
  }
993
848
  } catch (e) {
994
849
  this.log.error('PuppetService', 'batchContactRawPayload(%s, %s) error: %s, use one by one method', contactIdList, needGetSet, e)
@@ -2352,10 +2207,6 @@ class PuppetService extends PUPPET.Puppet {
2352
2207
  return cachedPayload
2353
2208
  }
2354
2209
 
2355
- // Snapshot the gen before the raw fetch so a dirty that lands while
2356
- // the gRPC round-trip is in flight makes the write-back below stale.
2357
- const gen = this.cache.snapshotGen(PUPPET.types.Dirty.Room, id)
2358
-
2359
2210
  const request = new grpcPuppet.RoomPayloadRequest()
2360
2211
  request.setId(id)
2361
2212
 
@@ -2382,12 +2233,8 @@ class PuppetService extends PUPPET.Puppet {
2382
2233
  payload.createTime = millisecondsFromTimestamp(createTime)
2383
2234
  }
2384
2235
 
2385
- if (this.cache.isFreshWrite(PUPPET.types.Dirty.Room, id, gen)) {
2386
- await this._payloadStore.room?.set(id, payload)
2387
- this.log.silly('PuppetService', 'roomRawPayload(%s) cache SET', id)
2388
- } else {
2389
- this.log.silly('PuppetService', 'roomRawPayload(%s) cache SET skipped: dirty landed during raw fetch', id)
2390
- }
2236
+ await this._payloadStore.room?.set(id, payload)
2237
+ this.log.silly('PuppetService', 'roomRawPayload(%s) cache SET', id)
2391
2238
 
2392
2239
  return payload
2393
2240
  }
@@ -2415,14 +2262,6 @@ class PuppetService extends PUPPET.Puppet {
2415
2262
 
2416
2263
  if (needGetSet.size > 0) {
2417
2264
  try {
2418
- // Snapshot each id's gen before the batch fetch; write-backs below
2419
- // are validated per id so a dirty on one room mid-flight only skips
2420
- // that room's stale write, not the whole batch.
2421
- const genSnap = new Map<string, number>()
2422
- for (const roomId of needGetSet) {
2423
- genSnap.set(roomId, this.cache.snapshotGen(PUPPET.types.Dirty.Room, roomId))
2424
- }
2425
-
2426
2265
  const request = new grpcPuppet.BatchRoomPayloadRequest()
2427
2266
  request.setIdsList(Array.from(needGetSet))
2428
2267
 
@@ -2451,11 +2290,7 @@ class PuppetService extends PUPPET.Puppet {
2451
2290
  puppetPayload.createTime = millisecondsFromTimestamp(createTime)
2452
2291
  }
2453
2292
  result.set(roomId, puppetPayload)
2454
- if (this.cache.isFreshWrite(PUPPET.types.Dirty.Room, roomId, genSnap.get(roomId) ?? 0)) {
2455
- await this._payloadStore.room?.set(roomId, puppetPayload)
2456
- } else {
2457
- this.log.silly('PuppetService', 'batchRoomRawPayload(%s) cache SET skipped: dirty landed during raw fetch', roomId)
2458
- }
2293
+ await this._payloadStore.room?.set(roomId, puppetPayload)
2459
2294
  }
2460
2295
  } catch (e) {
2461
2296
  this.log.error('PuppetService', 'batchRoomRawPayload(%s, %s) error: %s, use one by one method', roomIdList, needGetSet, e)
@@ -2735,13 +2570,6 @@ class PuppetService extends PUPPET.Puppet {
2735
2570
  override async roomMemberRawPayload (roomId: string, contactId: string): Promise<PUPPET.payloads.RoomMember> {
2736
2571
  this.log.verbose('PuppetService', 'roomMemberRawPayload(%s, %s)', roomId, contactId)
2737
2572
 
2738
- // Row-level read-modify-write: the write-back merges the fetched
2739
- // member into the existing row, so snapshot the room-level gen before
2740
- // even reading the row. A room-level dirty (bare roomId, or the
2741
- // room-level bump a compound RoomMember dirty triggers) that lands
2742
- // anywhere in this window then skips the merge write-back.
2743
- const roomGen = this.cache.snapshotGen(PUPPET.types.Dirty.RoomMember, roomId)
2744
-
2745
2573
  const cachedPayload = await this._payloadStore.roomMember?.get(roomId)
2746
2574
  const cachedRoomMemberPayload = cachedPayload && cachedPayload[contactId]
2747
2575
 
@@ -2770,15 +2598,11 @@ class PuppetService extends PUPPET.Puppet {
2770
2598
  joinTime : response.getJoinTime(),
2771
2599
  }
2772
2600
 
2773
- if (this.cache.isFreshWrite(PUPPET.types.Dirty.RoomMember, roomId, roomGen)) {
2774
- await this._payloadStore.roomMember?.set(roomId, {
2775
- ...cachedPayload,
2776
- [contactId]: payload,
2777
- })
2778
- this.log.silly('PuppetService', 'roomMemberRawPayload(%s, %s) cache SET', roomId, contactId)
2779
- } else {
2780
- this.log.silly('PuppetService', 'roomMemberRawPayload(%s, %s) cache SET skipped: dirty landed during raw fetch', roomId, contactId)
2781
- }
2601
+ await this._payloadStore.roomMember?.set(roomId, {
2602
+ ...cachedPayload,
2603
+ [contactId]: payload,
2604
+ })
2605
+ this.log.silly('PuppetService', 'roomMemberRawPayload(%s, %s) cache SET', roomId, contactId)
2782
2606
 
2783
2607
  return payload
2784
2608
  }
@@ -2795,10 +2619,6 @@ class PuppetService extends PUPPET.Puppet {
2795
2619
  const result = new Map<string, PUPPET.payloads.RoomMember>()
2796
2620
  const contactIdSet = new Set<string>(contactIdList)
2797
2621
  let needGetSet = new Set<string>()
2798
- // Row-level read-modify-write keyed by roomId: snapshot the room-level
2799
- // gen before reading the row so a dirty landing anywhere in the window
2800
- // skips the merged write-back.
2801
- const roomGen = this.cache.snapshotGen(PUPPET.types.Dirty.RoomMember, roomId)
2802
2622
  const cachedPayload = await this._payloadStore.roomMember?.get(roomId) || {}
2803
2623
  if (Object.keys(cachedPayload).length > 0) {
2804
2624
  for (const contactId of contactIdSet) {
@@ -2831,11 +2651,7 @@ class PuppetService extends PUPPET.Puppet {
2831
2651
  result.set(contactId, puppetPayload)
2832
2652
  cachedPayload[contactId] = puppetPayload
2833
2653
  }
2834
- if (this.cache.isFreshWrite(PUPPET.types.Dirty.RoomMember, roomId, roomGen)) {
2835
- await this._payloadStore.roomMember?.set(roomId, cachedPayload)
2836
- } else {
2837
- this.log.silly('PuppetService', 'batchRoomMemberRawPayload(%s) cache SET skipped: dirty landed during raw fetch', roomId)
2838
- }
2654
+ await this._payloadStore.roomMember?.set(roomId, cachedPayload)
2839
2655
  } catch (e) {
2840
2656
  this.log.error('PuppetService', 'batchRoomMemberRawPayload(%s, %s) error: %s, use one by one method', roomId, needGetSet, e)
2841
2657
  for (const contactId of needGetSet) {
@@ -3481,10 +3297,6 @@ class PuppetService extends PUPPET.Puppet {
3481
3297
  return cachedPayload
3482
3298
  }
3483
3299
 
3484
- // Snapshot the gen before the raw fetch so a dirty that lands while
3485
- // the gRPC round-trip is in flight makes the write-back below stale.
3486
- const gen = this.cache.snapshotGen(PUPPET.types.Dirty.TagGroup, id)
3487
-
3488
3300
  const request = new grpcPuppet.TagGroupPayloadRequest()
3489
3301
  request.setGroupId(id)
3490
3302
 
@@ -3504,12 +3316,8 @@ class PuppetService extends PUPPET.Puppet {
3504
3316
  type: grpcPayload.getType(),
3505
3317
  }
3506
3318
 
3507
- if (this.cache.isFreshWrite(PUPPET.types.Dirty.TagGroup, id, gen)) {
3508
- await this._payloadStore.tagGroup?.set(id, payload)
3509
- this.log.silly('PuppetService', 'tagGroupPayloadPuppet(%s) cache SET', id)
3510
- } else {
3511
- this.log.silly('PuppetService', 'tagGroupPayloadPuppet(%s) cache SET skipped: dirty landed during raw fetch', id)
3512
- }
3319
+ await this._payloadStore.tagGroup?.set(id, payload)
3320
+ this.log.silly('PuppetService', 'tagGroupPayloadPuppet(%s) cache SET', id)
3513
3321
 
3514
3322
  return payload
3515
3323
  }
@@ -3523,10 +3331,6 @@ class PuppetService extends PUPPET.Puppet {
3523
3331
  return cachedPayload
3524
3332
  }
3525
3333
 
3526
- // Snapshot the gen before the raw fetch so a dirty that lands while
3527
- // the gRPC round-trip is in flight makes the write-back below stale.
3528
- const gen = this.cache.snapshotGen(PUPPET.types.Dirty.Tag, tagId)
3529
-
3530
3334
  const request = new grpcPuppet.TagPayloadRequest()
3531
3335
  request.setTagId(tagId)
3532
3336
 
@@ -3547,12 +3351,8 @@ class PuppetService extends PUPPET.Puppet {
3547
3351
  type: grpcPayload.getType(),
3548
3352
  }
3549
3353
 
3550
- if (this.cache.isFreshWrite(PUPPET.types.Dirty.Tag, tagId, gen)) {
3551
- await this._payloadStore.tag?.set(tagId, payload)
3552
- this.log.silly('PuppetService', 'tagPayloadPuppet(%s) cache SET', tagId)
3553
- } else {
3554
- this.log.silly('PuppetService', 'tagPayloadPuppet(%s) cache SET skipped: dirty landed during raw fetch', tagId)
3555
- }
3354
+ await this._payloadStore.tag?.set(tagId, payload)
3355
+ this.log.silly('PuppetService', 'tagPayloadPuppet(%s) cache SET', tagId)
3556
3356
 
3557
3357
  return payload
3558
3358
  }
@@ -4,7 +4,7 @@
4
4
  import type { PackageJson } from 'type-fest'
5
5
  export const packageJson: PackageJson = {
6
6
  "name": "@juzi/wechaty-puppet-service",
7
- "version": "1.0.125",
7
+ "version": "1.0.126",
8
8
  "description": "Puppet Service for Wechaty",
9
9
  "type": "module",
10
10
  "exports": {
@@ -55,7 +55,7 @@ export const packageJson: PackageJson = {
55
55
  "@chatie/eslint-config": "^1.0.4",
56
56
  "@chatie/semver": "^0.4.7",
57
57
  "@chatie/tsconfig": "^4.6.3",
58
- "@juzi/wechaty-puppet": "^1.0.150",
58
+ "@juzi/wechaty-puppet": "^1.0.151",
59
59
  "@juzi/wechaty-puppet-mock": "^1.0.1",
60
60
  "@swc/core": "1.3.39",
61
61
  "@types/google-protobuf": "^3.15.5",
@@ -75,7 +75,7 @@ export const packageJson: PackageJson = {
75
75
  "why-is-node-running": "^2.2.1"
76
76
  },
77
77
  "peerDependencies": {
78
- "@juzi/wechaty-puppet": "^1.0.150"
78
+ "@juzi/wechaty-puppet": "^1.0.151"
79
79
  },
80
80
  "dependencies": {
81
81
  "@juzi/wechaty-grpc": "^1.0.106",
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env -S node --no-warnings --loader ts-node/esm
2
- export {};
3
- //# sourceMappingURL=dirty-gen-guard.spec.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"dirty-gen-guard.spec.d.ts","sourceRoot":"","sources":["../../../tests/dirty-gen-guard.spec.ts"],"names":[],"mappings":""}
@@ -1,206 +0,0 @@
1
- #!/usr/bin/env -S node --no-warnings --loader ts-node/esm
2
- "use strict";
3
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
- if (k2 === undefined) k2 = k;
5
- var desc = Object.getOwnPropertyDescriptor(m, k);
6
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
- desc = { enumerable: true, get: function() { return m[k]; } };
8
- }
9
- Object.defineProperty(o, k2, desc);
10
- }) : (function(o, m, k, k2) {
11
- if (k2 === undefined) k2 = k;
12
- o[k2] = m[k];
13
- }));
14
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
- Object.defineProperty(o, "default", { enumerable: true, value: v });
16
- }) : function(o, v) {
17
- o["default"] = v;
18
- });
19
- var __importStar = (this && this.__importStar) || function (mod) {
20
- if (mod && mod.__esModule) return mod;
21
- var result = {};
22
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
23
- __setModuleDefault(result, mod);
24
- return result;
25
- };
26
- var __importDefault = (this && this.__importDefault) || function (mod) {
27
- return (mod && mod.__esModule) ? mod : { "default": mod };
28
- };
29
- Object.defineProperty(exports, "__esModule", { value: true });
30
- /**
31
- * Regression tests for the FlashStore write-back generation guard.
32
- *
33
- * `_payloadStore` (FlashStore, on disk) is a cache layer independent of
34
- * the puppet-side LRU. Before this fix it had no protection against the
35
- * "dirty lands during a raw fetch" race:
36
- *
37
- * 1. A raw fetch (e.g. `contactRawPayload`) starts a gRPC round-trip.
38
- * 2. An EVENT_TYPE_DIRTY arrives; `fastDirty` deletes the FlashStore row.
39
- * 3. The in-flight fetch resolves and writes the *pre-dirty* payload
40
- * back into the FlashStore -- re-poisoning the row that was just
41
- * cleared. The value then only refreshes after a *second* dirty.
42
- *
43
- * The fix snapshots a per-(type, id) generation counter before the raw
44
- * fetch and re-checks it with `isFreshWrite` before every FlashStore
45
- * write-back; the EVENT_TYPE_DIRTY handler bumps that counter before it
46
- * even awaits `fastDirty`, so the whole delete window is covered.
47
- */
48
- const tstest_1 = require("tstest");
49
- const os_1 = __importDefault(require("os"));
50
- const path_1 = __importDefault(require("path"));
51
- const fs_1 = __importDefault(require("fs"));
52
- const PUPPET = __importStar(require("@juzi/wechaty-puppet"));
53
- const wechaty_grpc_1 = require("@juzi/wechaty-grpc");
54
- const mod_js_1 = require("../src/mod.js");
55
- const makePuppet = async () => {
56
- const token = `puppet_service_test_${Date.now()}_${Math.floor(Math.random() * 1e6)}`;
57
- const puppet = new mod_js_1.PuppetService({ token });
58
- const accountId = 'acct-test';
59
- await puppet._payloadStore.start(accountId);
60
- return { puppet, token };
61
- };
62
- const cleanup = async (puppet, token) => {
63
- await puppet._payloadStore.stop();
64
- try {
65
- await fs_1.default.promises.rm(path_1.default.join(os_1.default.homedir(), '.wechaty', 'wechaty-puppet-service', token), { recursive: true, force: true });
66
- }
67
- catch (_) { /* ignore */ }
68
- };
69
- /**
70
- * A one-shot gate for a fake gRPC method. The method resolves `entered`
71
- * as soon as it is invoked (by which point the caller's gen snapshot has
72
- * already been taken, since the snapshot precedes the fetch call), then
73
- * holds its callback until `release()` is called. This lets the test slot
74
- * a dirty deterministically between the fetch start (gen snapshot) and
75
- * the fetch resolution (write-back) -- no timers, no flakiness.
76
- */
77
- const makeGate = () => {
78
- let release = () => { };
79
- let markEntered = () => { };
80
- const gate = new Promise(resolve => { release = resolve; });
81
- const entered = new Promise(resolve => { markEntered = resolve; });
82
- return { gate, entered, enter: () => markEntered(), release: () => release() };
83
- };
84
- (0, tstest_1.test)('contactRawPayload: a dirty mid-fetch skips the stale FlashStore write-back', async (t) => {
85
- const { puppet, token } = await makePuppet();
86
- const contactId = 'contact-race';
87
- const response = new wechaty_grpc_1.puppet.ContactPayloadResponse();
88
- response.setId(contactId);
89
- response.setName('stale-name-from-inflight-fetch');
90
- const { gate, entered, enter, release } = makeGate();
91
- puppet._grpcManager = {
92
- client: {
93
- contactPayload: (_req, cb) => {
94
- enter();
95
- void (async () => {
96
- await gate;
97
- cb(null, response);
98
- })();
99
- },
100
- },
101
- };
102
- // Start the fetch: it snapshots the gen, then blocks on the gate.
103
- const fetchPromise = puppet.contactRawPayload(contactId);
104
- // Deterministic: once the fake method is entered, the snapshot is taken.
105
- await entered;
106
- // A dirty arrives while the fetch is in flight: bump gen + clear store,
107
- // exactly as the EVENT_TYPE_DIRTY handler does.
108
- puppet.cache.bumpGen(PUPPET.types.Dirty.Contact, contactId);
109
- await puppet.fastDirty({ payloadType: PUPPET.types.Dirty.Contact, payloadId: contactId });
110
- // Now let the stale fetch resolve into the write-back path.
111
- release();
112
- const returned = await fetchPromise;
113
- t.equal(returned.id, contactId, 'raw fetch still returns its payload to the caller');
114
- const stored = await puppet._payloadStore.contact.get(contactId);
115
- t.notOk(stored, 'stale in-flight fetch must NOT re-poison the FlashStore after the dirty');
116
- await cleanup(puppet, token);
117
- });
118
- (0, tstest_1.test)('contactRawPayload: no dirty means the write-back proceeds as usual', async (t) => {
119
- const { puppet, token } = await makePuppet();
120
- const contactId = 'contact-happy';
121
- const response = new wechaty_grpc_1.puppet.ContactPayloadResponse();
122
- response.setId(contactId);
123
- response.setName('fresh-name');
124
- puppet._grpcManager = {
125
- client: {
126
- contactPayload: (_req, cb) => cb(null, response),
127
- },
128
- };
129
- const returned = await puppet.contactRawPayload(contactId);
130
- t.equal(returned.id, contactId, 'raw fetch returns its payload');
131
- const stored = await puppet._payloadStore.contact.get(contactId);
132
- t.ok(stored, 'without a racing dirty the payload is written to the FlashStore');
133
- t.equal(stored && stored.id, contactId, 'stored payload matches the fetched id');
134
- await cleanup(puppet, token);
135
- });
136
- (0, tstest_1.test)('EVENT_TYPE_DIRTY bumps gen before awaiting fastDirty so pre-event snapshots are stale', async (t) => {
137
- const { puppet, token } = await makePuppet();
138
- const contactId = 'contact-bump';
139
- // A getter that snapshotted just before the event arrived.
140
- const snapshot = puppet.cache.snapshotGen(PUPPET.types.Dirty.Contact, contactId);
141
- const dirtyJson = JSON.stringify({
142
- payloadType: PUPPET.types.Dirty.Contact,
143
- payloadId: contactId,
144
- });
145
- await puppet.onGrpcStreamEvent({
146
- getType: () => wechaty_grpc_1.puppet.EventType.EVENT_TYPE_DIRTY,
147
- getPayload: () => dirtyJson,
148
- getSeq: () => 0,
149
- });
150
- t.notOk(puppet.cache.isFreshWrite(PUPPET.types.Dirty.Contact, contactId, snapshot), 'a snapshot taken before the dirty event must be judged stale afterwards');
151
- await cleanup(puppet, token);
152
- });
153
- (0, tstest_1.test)('EVENT_TYPE_DIRTY compound RoomMember bumps both the compound and the room-level gen', async (t) => {
154
- const { puppet, token } = await makePuppet();
155
- const roomId = 'room-bump';
156
- const memberId = 'member-bump';
157
- const compoundId = `${roomId}${PUPPET.STRING_SPLITTER}${memberId}`;
158
- const compoundSnap = puppet.cache.snapshotGen(PUPPET.types.Dirty.RoomMember, compoundId);
159
- const roomSnap = puppet.cache.snapshotGen(PUPPET.types.Dirty.RoomMember, roomId);
160
- const dirtyJson = JSON.stringify({
161
- payloadType: PUPPET.types.Dirty.RoomMember,
162
- payloadId: compoundId,
163
- });
164
- await puppet.onGrpcStreamEvent({
165
- getType: () => wechaty_grpc_1.puppet.EventType.EVENT_TYPE_DIRTY,
166
- getPayload: () => dirtyJson,
167
- getSeq: () => 0,
168
- });
169
- t.notOk(puppet.cache.isFreshWrite(PUPPET.types.Dirty.RoomMember, compoundId, compoundSnap), 'the compound (roomId+SEP+memberId) key must be bumped');
170
- t.notOk(puppet.cache.isFreshWrite(PUPPET.types.Dirty.RoomMember, roomId, roomSnap), 'the bare room-level key must also be bumped so row-level write-backs are guarded');
171
- await cleanup(puppet, token);
172
- });
173
- (0, tstest_1.test)('roomMemberRawPayload: a room-level dirty mid-fetch skips the row write-back', async (t) => {
174
- const { puppet, token } = await makePuppet();
175
- const roomId = 'room-member-race';
176
- const contactId = 'member-race';
177
- const response = new wechaty_grpc_1.puppet.RoomMemberPayloadResponse();
178
- response.setId(contactId);
179
- response.setName('stale-member-from-inflight-fetch');
180
- const { gate, entered, enter, release } = makeGate();
181
- puppet._grpcManager = {
182
- client: {
183
- roomMemberPayload: (_req, cb) => {
184
- enter();
185
- void (async () => {
186
- await gate;
187
- cb(null, response);
188
- })();
189
- },
190
- },
191
- };
192
- // Start the fetch: it snapshots the room-level gen before reading the
193
- // row, then blocks on the gate.
194
- const fetchPromise = puppet.roomMemberRawPayload(roomId, contactId);
195
- await entered;
196
- // A bare-roomId dirty lands mid-fetch: the whole member set is stale.
197
- puppet.cache.bumpGen(PUPPET.types.Dirty.RoomMember, roomId);
198
- await puppet.fastDirty({ payloadType: PUPPET.types.Dirty.RoomMember, payloadId: roomId });
199
- release();
200
- const returned = await fetchPromise;
201
- t.equal(returned.id, contactId, 'raw fetch still returns its member payload to the caller');
202
- const stored = await puppet._payloadStore.roomMember.get(roomId);
203
- t.notOk(stored, 'stale in-flight member fetch must NOT re-create the row after the dirty');
204
- await cleanup(puppet, token);
205
- });
206
- //# sourceMappingURL=dirty-gen-guard.spec.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"dirty-gen-guard.spec.js","sourceRoot":"","sources":["../../../tests/dirty-gen-guard.spec.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA;;;;;;;;;;;;;;;;;GAiBG;AACH,mCAA6B;AAC7B,4CAAmB;AACnB,gDAAuB;AACvB,4CAAmB;AAEnB,6DAA8C;AAC9C,qDAAyD;AAEzD,0CAA6C;AAE7C,MAAM,UAAU,GAAG,KAAK,IAAI,EAAE;IAC5B,MAAM,KAAK,GAAG,uBAAuB,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,EAAE,CAAA;IACpF,MAAM,MAAM,GAAG,IAAI,sBAAa,CAAC,EAAE,KAAK,EAAE,CAAQ,CAAA;IAClD,MAAM,SAAS,GAAG,WAAW,CAAA;IAC7B,MAAM,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;IAC3C,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;AAC1B,CAAC,CAAA;AAED,MAAM,OAAO,GAAG,KAAK,EAAE,MAAW,EAAE,KAAa,EAAE,EAAE;IACnD,MAAM,MAAM,CAAC,aAAa,CAAC,IAAI,EAAE,CAAA;IACjC,IAAI;QACF,MAAM,YAAE,CAAC,QAAQ,CAAC,EAAE,CAClB,cAAI,CAAC,IAAI,CAAC,YAAE,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,wBAAwB,EAAE,KAAK,CAAC,EACpE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CACjC,CAAA;KACF;IAAC,OAAO,CAAC,EAAE,EAAE,YAAY,EAAE;AAC9B,CAAC,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,QAAQ,GAAG,GAAG,EAAE;IACpB,IAAI,OAAO,GAAe,GAAG,EAAE,GAAE,CAAC,CAAA;IAClC,IAAI,WAAW,GAAe,GAAG,EAAE,GAAE,CAAC,CAAA;IACtC,MAAM,IAAI,GAAM,IAAI,OAAO,CAAO,OAAO,CAAC,EAAE,GAAG,OAAO,GAAG,OAAO,CAAA,CAAC,CAAC,CAAC,CAAA;IACnE,MAAM,OAAO,GAAG,IAAI,OAAO,CAAO,OAAO,CAAC,EAAE,GAAG,WAAW,GAAG,OAAO,CAAA,CAAC,CAAC,CAAC,CAAA;IACvE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,WAAW,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,EAAE,CAAA;AAChF,CAAC,CAAA;AAED,IAAA,aAAI,EAAC,4EAA4E,EAAE,KAAK,EAAC,CAAC,EAAC,EAAE;IAC3F,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,UAAU,EAAE,CAAA;IAE5C,MAAM,SAAS,GAAG,cAAc,CAAA;IAChC,MAAM,QAAQ,GAAG,IAAI,qBAAU,CAAC,sBAAsB,EAAE,CAAA;IACxD,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;IACzB,QAAQ,CAAC,OAAO,CAAC,gCAAgC,CAAC,CAAA;IAElD,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,QAAQ,EAAE,CAAA;IACpD,MAAM,CAAC,YAAY,GAAG;QACpB,MAAM,EAAE;YACN,cAAc,EAAE,CAAC,IAAS,EAAE,EAA8B,EAAE,EAAE;gBAC5D,KAAK,EAAE,CAAA;gBACP,KAAK,CAAC,KAAK,IAAI,EAAE;oBACf,MAAM,IAAI,CAAA;oBACV,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;gBACpB,CAAC,CAAC,EAAE,CAAA;YACN,CAAC;SACF;KACF,CAAA;IAED,kEAAkE;IAClE,MAAM,YAAY,GAAG,MAAM,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAA;IACxD,yEAAyE;IACzE,MAAM,OAAO,CAAA;IAEb,wEAAwE;IACxE,gDAAgD;IAChD,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;IAC3D,MAAM,MAAM,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,CAAA;IAEzF,4DAA4D;IAC5D,OAAO,EAAE,CAAA;IACT,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAA;IAEnC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,mDAAmD,CAAC,CAAA;IACpF,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;IAChE,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,yEAAyE,CAAC,CAAA;IAE1F,MAAM,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;AAC9B,CAAC,CAAC,CAAA;AAEF,IAAA,aAAI,EAAC,oEAAoE,EAAE,KAAK,EAAC,CAAC,EAAC,EAAE;IACnF,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,UAAU,EAAE,CAAA;IAE5C,MAAM,SAAS,GAAG,eAAe,CAAA;IACjC,MAAM,QAAQ,GAAG,IAAI,qBAAU,CAAC,sBAAsB,EAAE,CAAA;IACxD,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;IACzB,QAAQ,CAAC,OAAO,CAAC,YAAY,CAAC,CAAA;IAE9B,MAAM,CAAC,YAAY,GAAG;QACpB,MAAM,EAAE;YACN,cAAc,EAAE,CAAC,IAAS,EAAE,EAA8B,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC;SAClF;KACF,CAAA;IAED,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAA;IAC1D,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,+BAA+B,CAAC,CAAA;IAEhE,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;IAChE,CAAC,CAAC,EAAE,CAAC,MAAM,EAAE,iEAAiE,CAAC,CAAA;IAC/E,CAAC,CAAC,KAAK,CAAC,MAAM,IAAI,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,uCAAuC,CAAC,CAAA;IAEhF,MAAM,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;AAC9B,CAAC,CAAC,CAAA;AAEF,IAAA,aAAI,EAAC,uFAAuF,EAAE,KAAK,EAAC,CAAC,EAAC,EAAE;IACtG,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,UAAU,EAAE,CAAA;IAE5C,MAAM,SAAS,GAAG,cAAc,CAAA;IAChC,2DAA2D;IAC3D,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;IAEhF,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;QAC/B,WAAW,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO;QACvC,SAAS,EAAI,SAAS;KACvB,CAAC,CAAA;IACF,MAAM,MAAM,CAAC,iBAAiB,CAAC;QAC7B,OAAO,EAAK,GAAG,EAAE,CAAC,qBAAU,CAAC,SAAS,CAAC,gBAAgB;QACvD,UAAU,EAAE,GAAG,EAAE,CAAC,SAAS;QAC3B,MAAM,EAAM,GAAG,EAAE,CAAC,CAAC;KACpB,CAAC,CAAA;IAEF,CAAC,CAAC,KAAK,CACL,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC,EAC1E,yEAAyE,CAC1E,CAAA;IAED,MAAM,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;AAC9B,CAAC,CAAC,CAAA;AAEF,IAAA,aAAI,EAAC,qFAAqF,EAAE,KAAK,EAAC,CAAC,EAAC,EAAE;IACpG,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,UAAU,EAAE,CAAA;IAE5C,MAAM,MAAM,GAAO,WAAW,CAAA;IAC9B,MAAM,QAAQ,GAAK,aAAa,CAAA;IAChC,MAAM,UAAU,GAAG,GAAG,MAAM,GAAG,MAAM,CAAC,eAAe,GAAG,QAAQ,EAAE,CAAA;IAElE,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,EAAE,UAAU,CAAC,CAAA;IACxF,MAAM,QAAQ,GAAO,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,EAAE,MAAM,CAAC,CAAA;IAEpF,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;QAC/B,WAAW,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU;QAC1C,SAAS,EAAI,UAAU;KACxB,CAAC,CAAA;IACF,MAAM,MAAM,CAAC,iBAAiB,CAAC;QAC7B,OAAO,EAAK,GAAG,EAAE,CAAC,qBAAU,CAAC,SAAS,CAAC,gBAAgB;QACvD,UAAU,EAAE,GAAG,EAAE,CAAC,SAAS;QAC3B,MAAM,EAAM,GAAG,EAAE,CAAC,CAAC;KACpB,CAAC,CAAA;IAEF,CAAC,CAAC,KAAK,CACL,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,EAAE,UAAU,EAAE,YAAY,CAAC,EAClF,uDAAuD,CACxD,CAAA;IACD,CAAC,CAAC,KAAK,CACL,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,EAC1E,kFAAkF,CACnF,CAAA;IAED,MAAM,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;AAC9B,CAAC,CAAC,CAAA;AAEF,IAAA,aAAI,EAAC,6EAA6E,EAAE,KAAK,EAAC,CAAC,EAAC,EAAE;IAC5F,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,UAAU,EAAE,CAAA;IAE5C,MAAM,MAAM,GAAM,kBAAkB,CAAA;IACpC,MAAM,SAAS,GAAG,aAAa,CAAA;IAE/B,MAAM,QAAQ,GAAG,IAAI,qBAAU,CAAC,yBAAyB,EAAE,CAAA;IAC3D,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;IACzB,QAAQ,CAAC,OAAO,CAAC,kCAAkC,CAAC,CAAA;IAEpD,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,QAAQ,EAAE,CAAA;IACpD,MAAM,CAAC,YAAY,GAAG;QACpB,MAAM,EAAE;YACN,iBAAiB,EAAE,CAAC,IAAS,EAAE,EAA8B,EAAE,EAAE;gBAC/D,KAAK,EAAE,CAAA;gBACP,KAAK,CAAC,KAAK,IAAI,EAAE;oBACf,MAAM,IAAI,CAAA;oBACV,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;gBACpB,CAAC,CAAC,EAAE,CAAA;YACN,CAAC;SACF;KACF,CAAA;IAED,sEAAsE;IACtE,gCAAgC;IAChC,MAAM,YAAY,GAAG,MAAM,CAAC,oBAAoB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;IACnE,MAAM,OAAO,CAAA;IAEb,sEAAsE;IACtE,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,EAAE,MAAM,CAAC,CAAA;IAC3D,MAAM,MAAM,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAA;IAEzF,OAAO,EAAE,CAAA;IACT,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAA;IAEnC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,0DAA0D,CAAC,CAAA;IAC3F,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IAChE,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,yEAAyE,CAAC,CAAA;IAE1F,MAAM,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;AAC9B,CAAC,CAAC,CAAA"}
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env -S node --no-warnings --loader ts-node/esm
2
- export {};
3
- //# sourceMappingURL=fast-dirty-completeness.spec.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"fast-dirty-completeness.spec.d.ts","sourceRoot":"","sources":["../../../tests/fast-dirty-completeness.spec.ts"],"names":[],"mappings":""}