@juzi/wechaty-puppet-service 1.0.124 → 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.
- package/dist/cjs/src/client/puppet-service.d.ts +0 -27
- package/dist/cjs/src/client/puppet-service.d.ts.map +1 -1
- package/dist/cjs/src/client/puppet-service.js +11 -108
- package/dist/cjs/src/client/puppet-service.js.map +1 -1
- package/dist/cjs/src/package-json.js +3 -3
- package/dist/esm/src/client/puppet-service.d.ts +0 -27
- package/dist/esm/src/client/puppet-service.d.ts.map +1 -1
- package/dist/esm/src/client/puppet-service.js +11 -108
- package/dist/esm/src/client/puppet-service.js.map +1 -1
- package/dist/esm/src/package-json.js +3 -3
- package/package.json +3 -3
- package/src/client/puppet-service.ts +14 -110
- package/src/package-json.ts +3 -3
- package/dist/cjs/tests/fast-dirty-completeness.spec.d.ts +0 -3
- package/dist/cjs/tests/fast-dirty-completeness.spec.d.ts.map +0 -1
- package/dist/cjs/tests/fast-dirty-completeness.spec.js +0 -59
- package/dist/cjs/tests/fast-dirty-completeness.spec.js.map +0 -1
- package/dist/cjs/tests/fast-dirty-room-member.spec.d.ts +0 -3
- package/dist/cjs/tests/fast-dirty-room-member.spec.d.ts.map +0 -1
- package/dist/cjs/tests/fast-dirty-room-member.spec.js +0 -179
- package/dist/cjs/tests/fast-dirty-room-member.spec.js.map +0 -1
- package/dist/cjs/tests/grpc-stream-dirty-order.spec.d.ts +0 -3
- package/dist/cjs/tests/grpc-stream-dirty-order.spec.d.ts.map +0 -1
- package/dist/cjs/tests/grpc-stream-dirty-order.spec.js +0 -135
- package/dist/cjs/tests/grpc-stream-dirty-order.spec.js.map +0 -1
- package/dist/cjs/tests/grpc-stream-dirty-parse.spec.d.ts +0 -3
- package/dist/cjs/tests/grpc-stream-dirty-parse.spec.d.ts.map +0 -1
- package/dist/cjs/tests/grpc-stream-dirty-parse.spec.js +0 -72
- package/dist/cjs/tests/grpc-stream-dirty-parse.spec.js.map +0 -1
- package/dist/esm/tests/fast-dirty-completeness.spec.d.ts +0 -3
- package/dist/esm/tests/fast-dirty-completeness.spec.d.ts.map +0 -1
- package/dist/esm/tests/fast-dirty-completeness.spec.js +0 -34
- package/dist/esm/tests/fast-dirty-completeness.spec.js.map +0 -1
- package/dist/esm/tests/fast-dirty-room-member.spec.d.ts +0 -3
- package/dist/esm/tests/fast-dirty-room-member.spec.d.ts.map +0 -1
- package/dist/esm/tests/fast-dirty-room-member.spec.js +0 -151
- package/dist/esm/tests/fast-dirty-room-member.spec.js.map +0 -1
- package/dist/esm/tests/grpc-stream-dirty-order.spec.d.ts +0 -3
- package/dist/esm/tests/grpc-stream-dirty-order.spec.d.ts.map +0 -1
- package/dist/esm/tests/grpc-stream-dirty-order.spec.js +0 -110
- package/dist/esm/tests/grpc-stream-dirty-order.spec.js.map +0 -1
- package/dist/esm/tests/grpc-stream-dirty-parse.spec.d.ts +0 -3
- package/dist/esm/tests/grpc-stream-dirty-parse.spec.d.ts.map +0 -1
- package/dist/esm/tests/grpc-stream-dirty-parse.spec.js +0 -47
- 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,12 +368,10 @@ class PuppetService extends PUPPET.Puppet {
|
|
|
383
368
|
)
|
|
384
369
|
}
|
|
385
370
|
break
|
|
386
|
-
case grpcPuppet.EventType.EVENT_TYPE_DIRTY:
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
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)
|
|
390
374
|
break
|
|
391
|
-
}
|
|
392
375
|
case grpcPuppet.EventType.EVENT_TYPE_MESSAGE:
|
|
393
376
|
this.emit('message', JSON.parse(payload) as PUPPET.payloads.EventMessage)
|
|
394
377
|
break
|
|
@@ -513,87 +496,6 @@ class PuppetService extends PUPPET.Puppet {
|
|
|
513
496
|
}
|
|
514
497
|
}
|
|
515
498
|
|
|
516
|
-
/**
|
|
517
|
-
* Dirty handler registry, keyed by every `PUPPET.types.Dirty` value.
|
|
518
|
-
*
|
|
519
|
-
* Using a full `Record<...>` (rather than `Partial<...>`) means
|
|
520
|
-
* TypeScript catches a missing entry at compile time -- so when a
|
|
521
|
-
* new DirtyType is added to wechaty-puppet, callers get a build
|
|
522
|
-
* error instead of a silent runtime no-op.
|
|
523
|
-
*
|
|
524
|
-
* Note: `Unspecified` is treated as an upstream contract violation,
|
|
525
|
-
* not a benign no-op -- it logs an error and emits 'error' so hosts
|
|
526
|
-
* notice.
|
|
527
|
-
*/
|
|
528
|
-
private get _dirtyHandlerMap (): Record<PUPPET.types.Dirty, (id: string) => Promise<unknown>> {
|
|
529
|
-
return {
|
|
530
|
-
[PUPPET.types.Dirty.Contact]: async (id: string) => this._payloadStore.contact?.delete(id),
|
|
531
|
-
[PUPPET.types.Dirty.Friendship]: async (_: string) => {},
|
|
532
|
-
[PUPPET.types.Dirty.Message]: async (_: string) => {},
|
|
533
|
-
[PUPPET.types.Dirty.Post]: async (_: string) => {},
|
|
534
|
-
[PUPPET.types.Dirty.Room]: async (id: string) => this._payloadStore.room?.delete(id),
|
|
535
|
-
[PUPPET.types.Dirty.RoomMember]: async (id: string) => {
|
|
536
|
-
const [ roomId, memberId ] = id.split(PUPPET.STRING_SPLITTER)
|
|
537
|
-
if (!roomId) {
|
|
538
|
-
return
|
|
539
|
-
}
|
|
540
|
-
const store = this._payloadStore.roomMember
|
|
541
|
-
if (!store) {
|
|
542
|
-
return
|
|
543
|
-
}
|
|
544
|
-
// Bare roomId: the whole member set is stale, drop the row.
|
|
545
|
-
// Row-level delete is idempotent so it does not need serialization.
|
|
546
|
-
if (memberId === undefined) {
|
|
547
|
-
await store.delete(roomId)
|
|
548
|
-
return
|
|
549
|
-
}
|
|
550
|
-
// Compound id: `get → mutate → set` is a read-modify-write across
|
|
551
|
-
// an async store. Serialize per roomId so two concurrent compound
|
|
552
|
-
// dirties don't each read the same snapshot and each drop only
|
|
553
|
-
// their own key -- which would lose one of the two deletes.
|
|
554
|
-
const previous = this._roomMemberDirtyChain.get(roomId) ?? Promise.resolve()
|
|
555
|
-
const next = (async () => {
|
|
556
|
-
// Swallow the previous link's rejection so a failure upstream
|
|
557
|
-
// does not silently skip our own mutation. The prior handler
|
|
558
|
-
// already reported its error via fastDirty()'s try/catch.
|
|
559
|
-
try {
|
|
560
|
-
await previous
|
|
561
|
-
} catch (_) { /* ignore, upstream already reported */ }
|
|
562
|
-
const current = await store.get(roomId)
|
|
563
|
-
if (!current || !Object.prototype.hasOwnProperty.call(current, memberId)) {
|
|
564
|
-
return
|
|
565
|
-
}
|
|
566
|
-
const { [memberId]: _drop, ...rest } = current
|
|
567
|
-
if (Object.keys(rest).length === 0) {
|
|
568
|
-
await store.delete(roomId)
|
|
569
|
-
} else {
|
|
570
|
-
await store.set(roomId, rest)
|
|
571
|
-
}
|
|
572
|
-
})()
|
|
573
|
-
this._roomMemberDirtyChain.set(roomId, next)
|
|
574
|
-
try {
|
|
575
|
-
await next
|
|
576
|
-
} finally {
|
|
577
|
-
// Only clear the slot if we are still the tail; otherwise a
|
|
578
|
-
// later handler has already extended the chain and owns cleanup.
|
|
579
|
-
if (this._roomMemberDirtyChain.get(roomId) === next) {
|
|
580
|
-
this._roomMemberDirtyChain.delete(roomId)
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
},
|
|
584
|
-
[PUPPET.types.Dirty.Tag]: async (id: string) => this._payloadStore.tag?.delete(id),
|
|
585
|
-
[PUPPET.types.Dirty.TagGroup]: async (id: string) => this._payloadStore.tagGroup?.delete(id),
|
|
586
|
-
[PUPPET.types.Dirty.WxxdProduct]: async (_: string) => {},
|
|
587
|
-
[PUPPET.types.Dirty.WxxdOrder]: async (_: string) => {},
|
|
588
|
-
[PUPPET.types.Dirty.Call]: async (_: string) => {},
|
|
589
|
-
[PUPPET.types.Dirty.Unspecified]: async (id: string) => {
|
|
590
|
-
const msg = `fastDirty() received Unspecified dirty type (id=${id}); upstream puppet is leaking protobuf default — this is a server-side contract bug`
|
|
591
|
-
this.log.error('PuppetService', msg)
|
|
592
|
-
this.emit('error', new Error(msg))
|
|
593
|
-
},
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
|
|
597
499
|
/**
|
|
598
500
|
* `onDirty()` is called when the puppet emit `dirty` event.
|
|
599
501
|
* the event listener will be registered in `start()` from the `PuppetAbstract` class
|
|
@@ -606,18 +508,20 @@ class PuppetService extends PUPPET.Puppet {
|
|
|
606
508
|
): Promise<void> {
|
|
607
509
|
this.log.verbose('PuppetService', 'fastDirty(%s<%s>, %s)', PUPPET.types.Dirty[payloadType], payloadType, payloadId)
|
|
608
510
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
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) },
|
|
617
521
|
}
|
|
618
522
|
|
|
619
523
|
try {
|
|
620
|
-
await
|
|
524
|
+
await dirtyMap[payloadType]?.(payloadId)
|
|
621
525
|
} catch (error) {
|
|
622
526
|
this.emit('error', error)
|
|
623
527
|
}
|
package/src/package-json.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
78
|
+
"@juzi/wechaty-puppet": "^1.0.151"
|
|
79
79
|
},
|
|
80
80
|
"dependencies": {
|
|
81
81
|
"@juzi/wechaty-grpc": "^1.0.106",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"fast-dirty-completeness.spec.d.ts","sourceRoot":"","sources":["../../../tests/fast-dirty-completeness.spec.ts"],"names":[],"mappings":""}
|
|
@@ -1,59 +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
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
-
/**
|
|
28
|
-
* Regression test for fastDirty dirtyMap coverage.
|
|
29
|
-
*
|
|
30
|
-
* The previous `fastDirty` built a `Partial<Record<...>>` map that
|
|
31
|
-
* omitted Call / WxxdProduct / WxxdOrder. Any dirty event for those
|
|
32
|
-
* types fell into the `dirtyMap[payloadType]?.()` no-op branch and
|
|
33
|
-
* was silently dropped. Today the persistent FlashStore does not yet
|
|
34
|
-
* cover those types, so the silent drop is "fine"; but as soon as
|
|
35
|
-
* a future patch persists any of them, the missing handler will
|
|
36
|
-
* leave stale rows on disk after a dirty.
|
|
37
|
-
*
|
|
38
|
-
* Enforce the contract at the structural level: the puppet must
|
|
39
|
-
* expose a complete dirty handler registry keyed by every
|
|
40
|
-
* PUPPET.types.Dirty value.
|
|
41
|
-
*/
|
|
42
|
-
const tstest_1 = require("tstest");
|
|
43
|
-
const PUPPET = __importStar(require("@juzi/wechaty-puppet"));
|
|
44
|
-
const mod_js_1 = require("../src/mod.js");
|
|
45
|
-
(0, tstest_1.test)('PuppetService exposes a complete dirty handler registry', async (t) => {
|
|
46
|
-
const token = `puppet_service_test_${Date.now()}_${Math.floor(Math.random() * 1e6)}`;
|
|
47
|
-
const puppet = new mod_js_1.PuppetService({ token });
|
|
48
|
-
const handlers = puppet._dirtyHandlerMap;
|
|
49
|
-
t.ok(handlers, 'PuppetService should expose `_dirtyHandlerMap` for completeness checks');
|
|
50
|
-
if (!handlers) {
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
for (const value of Object.values(PUPPET.types.Dirty)) {
|
|
54
|
-
if (typeof value !== 'number')
|
|
55
|
-
continue;
|
|
56
|
-
t.equal(typeof handlers[value], 'function', `dirty handler must exist for DirtyType.${PUPPET.types.Dirty[value]}`);
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
//# sourceMappingURL=fast-dirty-completeness.spec.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"fast-dirty-completeness.spec.js","sourceRoot":"","sources":["../../../tests/fast-dirty-completeness.spec.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AACA;;;;;;;;;;;;;;GAcG;AACH,mCAA6B;AAE7B,6DAA8C;AAE9C,0CAA6C;AAE7C,IAAA,aAAI,EAAC,yDAAyD,EAAE,KAAK,EAAC,CAAC,EAAC,EAAE;IACxE,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;IAElD,MAAM,QAAQ,GAAG,MAAM,CAAC,gBAAgB,CAAA;IACxC,CAAC,CAAC,EAAE,CAAC,QAAQ,EAAE,wEAAwE,CAAC,CAAA;IAExF,IAAI,CAAC,QAAQ,EAAE;QACb,OAAM;KACP;IAED,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE;QACrD,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,SAAQ;QACvC,CAAC,CAAC,KAAK,CACL,OAAO,QAAQ,CAAC,KAAK,CAAC,EACtB,UAAU,EACV,0CAA0C,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CACtE,CAAA;KACF;AACH,CAAC,CAAC,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"fast-dirty-room-member.spec.d.ts","sourceRoot":"","sources":["../../../tests/fast-dirty-room-member.spec.ts"],"names":[],"mappings":""}
|
|
@@ -1,179 +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 RoomMember persistent-store dirty semantics.
|
|
32
|
-
*
|
|
33
|
-
* The persistent `PayloadStore.roomMember` is a
|
|
34
|
-
* `FlashStore<roomId, {[memberId]: RoomMember}>` -- a per-room record of
|
|
35
|
-
* members. Dirty ids arrive in two shapes:
|
|
36
|
-
*
|
|
37
|
-
* 1. A compound `"<roomId><STRING_SPLITTER><memberId>"` -- a single
|
|
38
|
-
* member's payload went stale (e.g. one member renamed).
|
|
39
|
-
* 2. A bare `"<roomId>"` -- the whole room's member set went stale.
|
|
40
|
-
*
|
|
41
|
-
* The previous handler treated both shapes the same by calling
|
|
42
|
-
* `roomMember.delete(roomId)`, which threw away the entire room's
|
|
43
|
-
* cache even when only one member was dirty. That thrash forces the
|
|
44
|
-
* next N-1 members to re-fetch over gRPC.
|
|
45
|
-
*
|
|
46
|
-
* The new handler must:
|
|
47
|
-
* - On compound id: drop only the named member, keep the rest.
|
|
48
|
-
* - On compound id whose split leaves the record empty: delete the row.
|
|
49
|
-
* - On bare roomId: delete the entire row (as before).
|
|
50
|
-
*/
|
|
51
|
-
const tstest_1 = require("tstest");
|
|
52
|
-
const os_1 = __importDefault(require("os"));
|
|
53
|
-
const path_1 = __importDefault(require("path"));
|
|
54
|
-
const fs_1 = __importDefault(require("fs"));
|
|
55
|
-
const PUPPET = __importStar(require("@juzi/wechaty-puppet"));
|
|
56
|
-
const mod_js_1 = require("../src/mod.js");
|
|
57
|
-
const makePuppet = async () => {
|
|
58
|
-
const token = `puppet_service_test_${Date.now()}_${Math.floor(Math.random() * 1e6)}`;
|
|
59
|
-
const puppet = new mod_js_1.PuppetService({ token });
|
|
60
|
-
const accountId = 'acct-test';
|
|
61
|
-
await puppet._payloadStore.start(accountId);
|
|
62
|
-
return { puppet, token };
|
|
63
|
-
};
|
|
64
|
-
const cleanup = async (puppet, token) => {
|
|
65
|
-
await puppet._payloadStore.stop();
|
|
66
|
-
try {
|
|
67
|
-
await fs_1.default.promises.rm(path_1.default.join(os_1.default.homedir(), '.wechaty', 'wechaty-puppet-service', token), { recursive: true, force: true });
|
|
68
|
-
}
|
|
69
|
-
catch (_) { /* ignore */ }
|
|
70
|
-
};
|
|
71
|
-
(0, tstest_1.test)('fastDirty(RoomMember, "<roomId>\\u001F<memberId>") drops only the named member', async (t) => {
|
|
72
|
-
const { puppet, token } = await makePuppet();
|
|
73
|
-
const roomId = 'room-test-id';
|
|
74
|
-
await puppet._payloadStore.roomMember.set(roomId, {
|
|
75
|
-
'member-A': { id: 'member-A', name: 'A' },
|
|
76
|
-
'member-B': { id: 'member-B', name: 'B' },
|
|
77
|
-
});
|
|
78
|
-
await puppet.fastDirty({
|
|
79
|
-
payloadType: PUPPET.types.Dirty.RoomMember,
|
|
80
|
-
payloadId: `${roomId}${PUPPET.STRING_SPLITTER}member-A`,
|
|
81
|
-
});
|
|
82
|
-
const after = await puppet._payloadStore.roomMember.get(roomId);
|
|
83
|
-
t.ok(after, 'room entry must survive when only one member is dirtied');
|
|
84
|
-
t.notOk(after && after['member-A'], 'dirtied member-A must be gone');
|
|
85
|
-
t.ok(after && after['member-B'], 'unrelated member-B must be preserved');
|
|
86
|
-
await cleanup(puppet, token);
|
|
87
|
-
});
|
|
88
|
-
(0, tstest_1.test)('fastDirty(RoomMember, "<roomId>\\u001F<lastMember>") deletes the row when empty', async (t) => {
|
|
89
|
-
const { puppet, token } = await makePuppet();
|
|
90
|
-
const roomId = 'room-test-id-lone';
|
|
91
|
-
await puppet._payloadStore.roomMember.set(roomId, {
|
|
92
|
-
'only-member': { id: 'only-member', name: 'Solo' },
|
|
93
|
-
});
|
|
94
|
-
await puppet.fastDirty({
|
|
95
|
-
payloadType: PUPPET.types.Dirty.RoomMember,
|
|
96
|
-
payloadId: `${roomId}${PUPPET.STRING_SPLITTER}only-member`,
|
|
97
|
-
});
|
|
98
|
-
const after = await puppet._payloadStore.roomMember.get(roomId);
|
|
99
|
-
t.notOk(after, 'row must be deleted once the record has no members left');
|
|
100
|
-
await cleanup(puppet, token);
|
|
101
|
-
});
|
|
102
|
-
(0, tstest_1.test)('fastDirty(RoomMember, "<roomId>") clears the whole room entry', async (t) => {
|
|
103
|
-
const { puppet, token } = await makePuppet();
|
|
104
|
-
const roomId = 'room-test-id-full';
|
|
105
|
-
await puppet._payloadStore.roomMember.set(roomId, {
|
|
106
|
-
'member-A': { id: 'member-A', name: 'A' },
|
|
107
|
-
'member-B': { id: 'member-B', name: 'B' },
|
|
108
|
-
});
|
|
109
|
-
await puppet.fastDirty({
|
|
110
|
-
payloadType: PUPPET.types.Dirty.RoomMember,
|
|
111
|
-
payloadId: roomId,
|
|
112
|
-
});
|
|
113
|
-
const after = await puppet._payloadStore.roomMember.get(roomId);
|
|
114
|
-
t.notOk(after, 'bare roomId dirty must clear the whole row');
|
|
115
|
-
await cleanup(puppet, token);
|
|
116
|
-
});
|
|
117
|
-
(0, tstest_1.test)('fastDirty(RoomMember) on unknown member is a no-op', async (t) => {
|
|
118
|
-
const { puppet, token } = await makePuppet();
|
|
119
|
-
const roomId = 'room-test-id-noop';
|
|
120
|
-
const before = {
|
|
121
|
-
'member-A': { id: 'member-A', name: 'A' },
|
|
122
|
-
'member-B': { id: 'member-B', name: 'B' },
|
|
123
|
-
};
|
|
124
|
-
await puppet._payloadStore.roomMember.set(roomId, before);
|
|
125
|
-
await puppet.fastDirty({
|
|
126
|
-
payloadType: PUPPET.types.Dirty.RoomMember,
|
|
127
|
-
payloadId: `${roomId}${PUPPET.STRING_SPLITTER}ghost-member`,
|
|
128
|
-
});
|
|
129
|
-
const after = await puppet._payloadStore.roomMember.get(roomId);
|
|
130
|
-
t.same(after, before, 'unrelated dirty must not disturb the record');
|
|
131
|
-
await cleanup(puppet, token);
|
|
132
|
-
});
|
|
133
|
-
(0, tstest_1.test)('fastDirty(RoomMember) concurrent compound dirties on same roomId drop every named member', async (t) => {
|
|
134
|
-
const { puppet, token } = await makePuppet();
|
|
135
|
-
const roomId = 'room-test-id-race';
|
|
136
|
-
await puppet._payloadStore.roomMember.set(roomId, {
|
|
137
|
-
'member-A': { id: 'member-A', name: 'A' },
|
|
138
|
-
'member-B': { id: 'member-B', name: 'B' },
|
|
139
|
-
});
|
|
140
|
-
// Fire both compound dirties without awaiting between them so their
|
|
141
|
-
// `get → mutate → set` cycles interleave. Without per-roomId
|
|
142
|
-
// serialization, both reads would see {A, B}, both writes would keep
|
|
143
|
-
// one sibling, and one of the two deletes would be lost.
|
|
144
|
-
await Promise.all([
|
|
145
|
-
puppet.fastDirty({
|
|
146
|
-
payloadType: PUPPET.types.Dirty.RoomMember,
|
|
147
|
-
payloadId: `${roomId}${PUPPET.STRING_SPLITTER}member-A`,
|
|
148
|
-
}),
|
|
149
|
-
puppet.fastDirty({
|
|
150
|
-
payloadType: PUPPET.types.Dirty.RoomMember,
|
|
151
|
-
payloadId: `${roomId}${PUPPET.STRING_SPLITTER}member-B`,
|
|
152
|
-
}),
|
|
153
|
-
]);
|
|
154
|
-
const after = await puppet._payloadStore.roomMember.get(roomId);
|
|
155
|
-
// Either the whole row is gone (last shrink deleted it) or the record
|
|
156
|
-
// is empty. Both are acceptable "no members left" observations.
|
|
157
|
-
const noMembersLeft = !after || Object.keys(after).length === 0;
|
|
158
|
-
t.ok(noMembersLeft, `both dirtied members must be evicted, got: ${JSON.stringify(after)}`);
|
|
159
|
-
await cleanup(puppet, token);
|
|
160
|
-
});
|
|
161
|
-
(0, tstest_1.test)('fastDirty(RoomMember) prototype-key member id must not match a real member', async (t) => {
|
|
162
|
-
const { puppet, token } = await makePuppet();
|
|
163
|
-
const roomId = 'room-test-id-proto';
|
|
164
|
-
const before = {
|
|
165
|
-
'member-A': { id: 'member-A', name: 'A' },
|
|
166
|
-
};
|
|
167
|
-
await puppet._payloadStore.roomMember.set(roomId, before);
|
|
168
|
-
// `toString` exists on Object.prototype; a naive `memberId in current`
|
|
169
|
-
// check would match it and take the mutate branch. The correct guard
|
|
170
|
-
// uses hasOwnProperty and treats this as an unknown member.
|
|
171
|
-
await puppet.fastDirty({
|
|
172
|
-
payloadType: PUPPET.types.Dirty.RoomMember,
|
|
173
|
-
payloadId: `${roomId}${PUPPET.STRING_SPLITTER}toString`,
|
|
174
|
-
});
|
|
175
|
-
const after = await puppet._payloadStore.roomMember.get(roomId);
|
|
176
|
-
t.same(after, before, 'prototype-key dirty must be a no-op');
|
|
177
|
-
await cleanup(puppet, token);
|
|
178
|
-
});
|
|
179
|
-
//# sourceMappingURL=fast-dirty-room-member.spec.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"fast-dirty-room-member.spec.js","sourceRoot":"","sources":["../../../tests/fast-dirty-room-member.spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"grpc-stream-dirty-order.spec.d.ts","sourceRoot":"","sources":["../../../tests/grpc-stream-dirty-order.spec.ts"],"names":[],"mappings":""}
|
|
@@ -1,135 +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
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
-
/**
|
|
28
|
-
* Regression / hardening test for the EVENT_TYPE_DIRTY dispatch order.
|
|
29
|
-
*
|
|
30
|
-
* Client-side dirty handling touches two independent caches:
|
|
31
|
-
*
|
|
32
|
-
* - `fastDirty()` clears `_payloadStore` (FlashStore, on disk, async).
|
|
33
|
-
* - `emit('dirty')` triggers the puppet-side CacheMixin which clears
|
|
34
|
-
* the in-memory LRU synchronously in the same tick.
|
|
35
|
-
*
|
|
36
|
-
* If `emit('dirty')` runs before `fastDirty()` resolves, a listener that
|
|
37
|
-
* immediately reads back the payload -- as CacheMixin.onDirty does when
|
|
38
|
-
* a caller races with the dirty event -- can:
|
|
39
|
-
*
|
|
40
|
-
* 1. See an LRU miss (already cleared),
|
|
41
|
-
* 2. Refetch via `contactRawPayload` etc.,
|
|
42
|
-
* 3. Hit the still-un-cleared `_payloadStore` entry,
|
|
43
|
-
* 4. Repopulate the LRU with the stale row that fastDirty is about
|
|
44
|
-
* to delete a microtask later,
|
|
45
|
-
* 5. And the fresh fastDirty delete no-ops against a now-cold key.
|
|
46
|
-
*
|
|
47
|
-
* The current implementation `await`s fastDirty before emitting. This
|
|
48
|
-
* test pins that invariant: fastDirty must fully resolve before any
|
|
49
|
-
* `dirty` listener observes the event.
|
|
50
|
-
*/
|
|
51
|
-
const tstest_1 = require("tstest");
|
|
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
|
-
(0, tstest_1.test)('onGrpcStreamEvent awaits fastDirty before emitting dirty', async (t) => {
|
|
56
|
-
const token = `puppet_service_test_${Date.now()}_${Math.floor(Math.random() * 1e6)}`;
|
|
57
|
-
const puppet = new mod_js_1.PuppetService({ token });
|
|
58
|
-
let fastDirtyResolvedAt;
|
|
59
|
-
let dirtyEmittedAt;
|
|
60
|
-
const FASTDIRTY_DELAY_MS = 50;
|
|
61
|
-
puppet.fastDirty = async (_payload) => {
|
|
62
|
-
await new Promise(resolve => setTimeout(resolve, FASTDIRTY_DELAY_MS));
|
|
63
|
-
fastDirtyResolvedAt = Date.now();
|
|
64
|
-
};
|
|
65
|
-
puppet.on('dirty', () => {
|
|
66
|
-
dirtyEmittedAt = Date.now();
|
|
67
|
-
});
|
|
68
|
-
const dirtyJson = JSON.stringify({
|
|
69
|
-
payloadType: PUPPET.types.Dirty.Contact,
|
|
70
|
-
payloadId: 'contact-x',
|
|
71
|
-
});
|
|
72
|
-
const fakeEvent = {
|
|
73
|
-
getType: () => wechaty_grpc_1.puppet.EventType.EVENT_TYPE_DIRTY,
|
|
74
|
-
getPayload: () => dirtyJson,
|
|
75
|
-
getSeq: () => 0,
|
|
76
|
-
};
|
|
77
|
-
await puppet.onGrpcStreamEvent(fakeEvent);
|
|
78
|
-
t.ok(fastDirtyResolvedAt, 'fastDirty must have been invoked');
|
|
79
|
-
t.ok(dirtyEmittedAt, 'dirty listener must have been invoked');
|
|
80
|
-
t.ok(fastDirtyResolvedAt <= dirtyEmittedAt, `dirty emit (${dirtyEmittedAt}) must not precede fastDirty resolve (${fastDirtyResolvedAt})`);
|
|
81
|
-
});
|
|
82
|
-
(0, tstest_1.test)('onGrpcStreamEvent: dirty emit sequence-number is strictly after every fastDirty resolution', async (t) => {
|
|
83
|
-
// Wall-clock ordering (the baseline test above) is fine as a smoke
|
|
84
|
-
// signal but can collapse to a tie when both boundaries fall inside
|
|
85
|
-
// the same millisecond. The invariant we actually care about is
|
|
86
|
-
// *causal*: each fastDirty must have fully resolved before the
|
|
87
|
-
// corresponding dirty emit fires. Record call ordinals from a shared
|
|
88
|
-
// monotonic counter to pin that -- ordinals cannot tie.
|
|
89
|
-
const token = `puppet_service_test_${Date.now()}_${Math.floor(Math.random() * 1e6)}`;
|
|
90
|
-
const puppet = new mod_js_1.PuppetService({ token });
|
|
91
|
-
let sequence = 0;
|
|
92
|
-
const nextOrdinal = () => ++sequence;
|
|
93
|
-
// Order of interest per event: fastDirty enter -> fastDirty resolve -> dirty emit.
|
|
94
|
-
const fastDirtyEnterOrdinals = [];
|
|
95
|
-
const fastDirtyResolveOrdinals = [];
|
|
96
|
-
const dirtyEmitOrdinals = [];
|
|
97
|
-
const fastDirtyStub = tstest_1.sinon.stub().callsFake(async (_payload) => {
|
|
98
|
-
fastDirtyEnterOrdinals.push(nextOrdinal());
|
|
99
|
-
// Yield the microtask queue so a broken ordering would surface: if
|
|
100
|
-
// emit ran ahead of the await point below, its ordinal would slot
|
|
101
|
-
// between our enter and resolve records.
|
|
102
|
-
await new Promise(resolve => setImmediate(resolve));
|
|
103
|
-
fastDirtyResolveOrdinals.push(nextOrdinal());
|
|
104
|
-
});
|
|
105
|
-
puppet.fastDirty = fastDirtyStub;
|
|
106
|
-
const dirtySpy = tstest_1.sinon.spy(() => {
|
|
107
|
-
dirtyEmitOrdinals.push(nextOrdinal());
|
|
108
|
-
});
|
|
109
|
-
puppet.on('dirty', dirtySpy);
|
|
110
|
-
const makeEvent = (payloadId) => {
|
|
111
|
-
const dirtyJson = JSON.stringify({
|
|
112
|
-
payloadType: PUPPET.types.Dirty.Contact,
|
|
113
|
-
payloadId,
|
|
114
|
-
});
|
|
115
|
-
return {
|
|
116
|
-
getType: () => wechaty_grpc_1.puppet.EventType.EVENT_TYPE_DIRTY,
|
|
117
|
-
getPayload: () => dirtyJson,
|
|
118
|
-
getSeq: () => 0,
|
|
119
|
-
};
|
|
120
|
-
};
|
|
121
|
-
// Two back-to-back dirty events: this is the scenario Fix #6 pins,
|
|
122
|
-
// sequential await must hold across both.
|
|
123
|
-
await puppet.onGrpcStreamEvent(makeEvent('contact-1'));
|
|
124
|
-
await puppet.onGrpcStreamEvent(makeEvent('contact-2'));
|
|
125
|
-
t.equal(fastDirtyStub.callCount, 2, 'fastDirty invoked once per dirty event');
|
|
126
|
-
t.equal(dirtySpy.callCount, 2, 'dirty listener invoked once per dirty event');
|
|
127
|
-
t.equal(fastDirtyEnterOrdinals.length, 2, 'both fastDirty invocations recorded an entry ordinal');
|
|
128
|
-
t.equal(fastDirtyResolveOrdinals.length, 2, 'both fastDirty invocations recorded a resolve ordinal');
|
|
129
|
-
t.equal(dirtyEmitOrdinals.length, 2, 'both dirty emits recorded an ordinal');
|
|
130
|
-
for (let i = 0; i < 2; i++) {
|
|
131
|
-
t.ok(fastDirtyEnterOrdinals[i] < fastDirtyResolveOrdinals[i], `event #${i}: fastDirty must enter (ord=${fastDirtyEnterOrdinals[i]}) before it resolves (ord=${fastDirtyResolveOrdinals[i]})`);
|
|
132
|
-
t.ok(fastDirtyResolveOrdinals[i] < dirtyEmitOrdinals[i], `event #${i}: dirty emit ordinal (${dirtyEmitOrdinals[i]}) must strictly follow fastDirty resolve ordinal (${fastDirtyResolveOrdinals[i]})`);
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
//# sourceMappingURL=grpc-stream-dirty-order.spec.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"grpc-stream-dirty-order.spec.js","sourceRoot":"","sources":["../../../tests/grpc-stream-dirty-order.spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"grpc-stream-dirty-parse.spec.d.ts","sourceRoot":"","sources":["../../../tests/grpc-stream-dirty-parse.spec.ts"],"names":[],"mappings":""}
|