@juzi/wechaty-puppet-service 1.0.119 → 1.0.120

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 (37) hide show
  1. package/dist/cjs/src/client/puppet-service.d.ts +13 -0
  2. package/dist/cjs/src/client/puppet-service.d.ts.map +1 -1
  3. package/dist/cjs/src/client/puppet-service.js +48 -11
  4. package/dist/cjs/src/client/puppet-service.js.map +1 -1
  5. package/dist/cjs/src/package-json.js +1 -1
  6. package/dist/cjs/tests/fast-dirty-completeness.spec.d.ts +3 -0
  7. package/dist/cjs/tests/fast-dirty-completeness.spec.d.ts.map +1 -0
  8. package/dist/cjs/tests/fast-dirty-completeness.spec.js +59 -0
  9. package/dist/cjs/tests/fast-dirty-completeness.spec.js.map +1 -0
  10. package/dist/cjs/tests/fast-dirty-room-member.spec.d.ts +3 -0
  11. package/dist/cjs/tests/fast-dirty-room-member.spec.d.ts.map +1 -0
  12. package/dist/cjs/tests/fast-dirty-room-member.spec.js +77 -0
  13. package/dist/cjs/tests/fast-dirty-room-member.spec.js.map +1 -0
  14. package/dist/cjs/tests/grpc-stream-dirty-parse.spec.d.ts +3 -0
  15. package/dist/cjs/tests/grpc-stream-dirty-parse.spec.d.ts.map +1 -0
  16. package/dist/cjs/tests/grpc-stream-dirty-parse.spec.js +72 -0
  17. package/dist/cjs/tests/grpc-stream-dirty-parse.spec.js.map +1 -0
  18. package/dist/esm/src/client/puppet-service.d.ts +13 -0
  19. package/dist/esm/src/client/puppet-service.d.ts.map +1 -1
  20. package/dist/esm/src/client/puppet-service.js +48 -11
  21. package/dist/esm/src/client/puppet-service.js.map +1 -1
  22. package/dist/esm/src/package-json.js +1 -1
  23. package/dist/esm/tests/fast-dirty-completeness.spec.d.ts +3 -0
  24. package/dist/esm/tests/fast-dirty-completeness.spec.d.ts.map +1 -0
  25. package/dist/esm/tests/fast-dirty-completeness.spec.js +34 -0
  26. package/dist/esm/tests/fast-dirty-completeness.spec.js.map +1 -0
  27. package/dist/esm/tests/fast-dirty-room-member.spec.d.ts +3 -0
  28. package/dist/esm/tests/fast-dirty-room-member.spec.d.ts.map +1 -0
  29. package/dist/esm/tests/fast-dirty-room-member.spec.js +49 -0
  30. package/dist/esm/tests/fast-dirty-room-member.spec.js.map +1 -0
  31. package/dist/esm/tests/grpc-stream-dirty-parse.spec.d.ts +3 -0
  32. package/dist/esm/tests/grpc-stream-dirty-parse.spec.d.ts.map +1 -0
  33. package/dist/esm/tests/grpc-stream-dirty-parse.spec.js +47 -0
  34. package/dist/esm/tests/grpc-stream-dirty-parse.spec.js.map +1 -0
  35. package/package.json +1 -1
  36. package/src/client/puppet-service.ts +52 -14
  37. package/src/package-json.ts +1 -1
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env -S node --no-warnings --loader ts-node/esm
2
+ /**
3
+ * Regression test for the double JSON.parse on every EVENT_TYPE_DIRTY
4
+ * stream event.
5
+ *
6
+ * The previous implementation was:
7
+ *
8
+ * case grpcPuppet.EventType.EVENT_TYPE_DIRTY:
9
+ * await this.fastDirty(JSON.parse(payload))
10
+ * this.emit('dirty', JSON.parse(payload) as PUPPET.payloads.EventDirty)
11
+ *
12
+ * which parses the same JSON string twice for every dirty event on the
13
+ * hot stream path. Parse the payload once and reuse the resulting object.
14
+ */
15
+ import { test, sinon } from 'tstest';
16
+ import * as PUPPET from '@juzi/wechaty-puppet';
17
+ import { puppet as grpcPuppet } from '@juzi/wechaty-grpc';
18
+ import { PuppetService } from '../src/mod.js';
19
+ test('onGrpcStreamEvent must parse a DIRTY payload only once', async (t) => {
20
+ const token = `puppet_service_test_${Date.now()}_${Math.floor(Math.random() * 1e6)}`;
21
+ const puppet = new PuppetService({ token });
22
+ // Force the fastDirty branch to be a no-op so we measure only the
23
+ // parsing cost in the dispatch case.
24
+ puppet.fastDirty = async (_) => { };
25
+ const dirtyPayload = JSON.stringify({
26
+ payloadType: PUPPET.types.Dirty.Contact,
27
+ payloadId: 'x',
28
+ });
29
+ // Build a minimal grpcPuppet.EventResponse shape -- only the getters
30
+ // touched by `onGrpcStreamEvent` are needed.
31
+ const fakeEvent = {
32
+ getType: () => grpcPuppet.EventType.EVENT_TYPE_DIRTY,
33
+ getPayload: () => dirtyPayload,
34
+ getSeq: () => 0,
35
+ };
36
+ const parseSpy = sinon.spy(JSON, 'parse');
37
+ try {
38
+ const baseline = parseSpy.callCount;
39
+ await puppet.onGrpcStreamEvent(fakeEvent);
40
+ const delta = parseSpy.callCount - baseline;
41
+ t.equal(delta, 1, `EVENT_TYPE_DIRTY should JSON.parse(payload) only once, observed ${delta}`);
42
+ }
43
+ finally {
44
+ parseSpy.restore();
45
+ }
46
+ });
47
+ //# sourceMappingURL=grpc-stream-dirty-parse.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grpc-stream-dirty-parse.spec.js","sourceRoot":"","sources":["../../../tests/grpc-stream-dirty-parse.spec.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AAEpC,OAAO,KAAK,MAAM,MAAM,sBAAsB,CAAA;AAC9C,OAAO,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAEzD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAE7C,IAAI,CAAC,wDAAwD,EAAE,KAAK,EAAC,CAAC,EAAC,EAAE;IACvE,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,aAAa,CAAC,EAAE,KAAK,EAAE,CAAQ,CAAA;IAElD,kEAAkE;IAClE,qCAAqC;IACrC,MAAM,CAAC,SAAS,GAAG,KAAK,EAAE,CAAM,EAAE,EAAE,GAAE,CAAC,CAAA;IAEvC,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,CAAC;QAClC,WAAW,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO;QACvC,SAAS,EAAI,GAAG;KACjB,CAAC,CAAA;IAEF,qEAAqE;IACrE,6CAA6C;IAC7C,MAAM,SAAS,GAAG;QAChB,OAAO,EAAK,GAAG,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,gBAAgB;QACvD,UAAU,EAAE,GAAG,EAAE,CAAC,YAAY;QAC9B,MAAM,EAAM,GAAG,EAAE,CAAC,CAAC;KACpB,CAAA;IAED,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IACzC,IAAI;QACF,MAAM,QAAQ,GAAG,QAAQ,CAAC,SAAS,CAAA;QACnC,MAAM,MAAM,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAA;QACzC,MAAM,KAAK,GAAG,QAAQ,CAAC,SAAS,GAAG,QAAQ,CAAA;QAC3C,CAAC,CAAC,KAAK,CACL,KAAK,EACL,CAAC,EACD,mEAAmE,KAAK,EAAE,CAC3E,CAAA;KACF;YAAS;QACR,QAAQ,CAAC,OAAO,EAAE,CAAA;KACnB;AACH,CAAC,CAAC,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juzi/wechaty-puppet-service",
3
- "version": "1.0.119",
3
+ "version": "1.0.120",
4
4
  "description": "Puppet Service for Wechaty",
5
5
  "type": "module",
6
6
  "exports": {
@@ -367,10 +367,12 @@ class PuppetService extends PUPPET.Puppet {
367
367
  )
368
368
  }
369
369
  break
370
- case grpcPuppet.EventType.EVENT_TYPE_DIRTY:
371
- await this.fastDirty(JSON.parse(payload))
372
- this.emit('dirty', JSON.parse(payload) as PUPPET.payloads.EventDirty)
370
+ case grpcPuppet.EventType.EVENT_TYPE_DIRTY: {
371
+ const dirtyPayload = JSON.parse(payload) as PUPPET.payloads.EventDirty
372
+ await this.fastDirty(dirtyPayload)
373
+ this.emit('dirty', dirtyPayload)
373
374
  break
375
+ }
374
376
  case grpcPuppet.EventType.EVENT_TYPE_MESSAGE:
375
377
  this.emit('message', JSON.parse(payload) as PUPPET.payloads.EventMessage)
376
378
  break
@@ -495,6 +497,44 @@ class PuppetService extends PUPPET.Puppet {
495
497
  }
496
498
  }
497
499
 
500
+ /**
501
+ * Dirty handler registry, keyed by every `PUPPET.types.Dirty` value.
502
+ *
503
+ * Using a full `Record<...>` (rather than `Partial<...>`) means
504
+ * TypeScript catches a missing entry at compile time -- so when a
505
+ * new DirtyType is added to wechaty-puppet, callers get a build
506
+ * error instead of a silent runtime no-op.
507
+ *
508
+ * Note: `Unspecified` is treated as an upstream contract violation,
509
+ * not a benign no-op -- it logs an error and emits 'error' so hosts
510
+ * notice.
511
+ */
512
+ private get _dirtyHandlerMap (): Record<PUPPET.types.Dirty, (id: string) => Promise<unknown>> {
513
+ return {
514
+ [PUPPET.types.Dirty.Contact]: async (id: string) => this._payloadStore.contact?.delete(id),
515
+ [PUPPET.types.Dirty.Friendship]: async (_: string) => {},
516
+ [PUPPET.types.Dirty.Message]: async (_: string) => {},
517
+ [PUPPET.types.Dirty.Post]: async (_: string) => {},
518
+ [PUPPET.types.Dirty.Room]: async (id: string) => this._payloadStore.room?.delete(id),
519
+ [PUPPET.types.Dirty.RoomMember]: async (id: string) => {
520
+ const [ roomId ] = id.split(PUPPET.STRING_SPLITTER)
521
+ if (roomId) {
522
+ await this._payloadStore.roomMember?.delete(roomId)
523
+ }
524
+ },
525
+ [PUPPET.types.Dirty.Tag]: async (id: string) => this._payloadStore.tag?.delete(id),
526
+ [PUPPET.types.Dirty.TagGroup]: async (id: string) => this._payloadStore.tagGroup?.delete(id),
527
+ [PUPPET.types.Dirty.WxxdProduct]: async (_: string) => {},
528
+ [PUPPET.types.Dirty.WxxdOrder]: async (_: string) => {},
529
+ [PUPPET.types.Dirty.Call]: async (_: string) => {},
530
+ [PUPPET.types.Dirty.Unspecified]: async (id: string) => {
531
+ const msg = `fastDirty() received Unspecified dirty type (id=${id}); upstream puppet is leaking protobuf default — this is a server-side contract bug`
532
+ log.error('PuppetService', msg)
533
+ this.emit('error', new Error(msg))
534
+ },
535
+ }
536
+ }
537
+
498
538
  /**
499
539
  * `onDirty()` is called when the puppet emit `dirty` event.
500
540
  * the event listener will be registered in `start()` from the `PuppetAbstract` class
@@ -507,20 +547,18 @@ class PuppetService extends PUPPET.Puppet {
507
547
  ): Promise<void> {
508
548
  log.verbose('PuppetService', 'fastDirty(%s<%s>, %s)', PUPPET.types.Dirty[payloadType], payloadType, payloadId)
509
549
 
510
- const dirtyMap: Partial<Record<PUPPET.types.Dirty, (id: string) => Promise<unknown>>> = {
511
- [PUPPET.types.Dirty.Contact]: async (id: string) => this._payloadStore.contact?.delete(id),
512
- [PUPPET.types.Dirty.Friendship]: async (_: string) => {},
513
- [PUPPET.types.Dirty.Message]: async (_: string) => {},
514
- [PUPPET.types.Dirty.Post]: async (_: string) => {},
515
- [PUPPET.types.Dirty.Room]: async (id: string) => this._payloadStore.room?.delete(id),
516
- [PUPPET.types.Dirty.RoomMember]: async (id: string) => this._payloadStore.roomMember?.delete(id),
517
- [PUPPET.types.Dirty.Tag]: async (id: string) => this._payloadStore.tag?.delete(id),
518
- [PUPPET.types.Dirty.TagGroup]: async (id: string) => this._payloadStore.tagGroup?.delete(id),
519
- [PUPPET.types.Dirty.Unspecified]: async (id: string) => { throw new Error('Unspecified type with id: ' + id) },
550
+ // payloadType is typed as the enum, but at runtime the server may emit a
551
+ // value outside our enum (forward-compat). Look it up via a
552
+ // possibly-undefined view so the runtime guard stays meaningful.
553
+ const lookup = this._dirtyHandlerMap as Partial<Record<PUPPET.types.Dirty, (id: string) => Promise<unknown>>>
554
+ const handler = lookup[payloadType]
555
+ if (!handler) {
556
+ log.warn('PuppetService', 'fastDirty() no handler for payloadType=%s', payloadType)
557
+ return
520
558
  }
521
559
 
522
560
  try {
523
- await dirtyMap[payloadType]?.(payloadId)
561
+ await handler(payloadId)
524
562
  } catch (error) {
525
563
  this.emit('error', error)
526
564
  }
@@ -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.119",
7
+ "version": "1.0.120",
8
8
  "description": "Puppet Service for Wechaty",
9
9
  "type": "module",
10
10
  "exports": {