@wovin/core 0.3.4 → 0.3.5
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/applog.js +1 -1
- package/dist/{chunk-ERVSWD63.js → chunk-AFRRFDNL.js} +2 -2
- package/dist/{chunk-XEUQDEA4.js → chunk-B22AIVJH.js} +2 -2
- package/dist/{chunk-OB7M55WA.js → chunk-EZZVLYYC.js} +3 -3
- package/dist/{chunk-PTZJ7LVV.js → chunk-IC6WD7RV.js} +3 -3
- package/dist/{chunk-7VHIXHNF.js → chunk-OUQE6LG4.js} +42 -3
- package/dist/chunk-OUQE6LG4.js.map +1 -0
- package/dist/{chunk-GQDLPZEZ.js → chunk-PNNJMXZI.js} +2 -2
- package/dist/{chunk-523IW54O.js → chunk-PSIMJH6F.js} +3 -3
- package/dist/index.js +7 -7
- package/dist/ipfs.js +4 -4
- package/dist/pubsub.js +4 -4
- package/dist/query.js +3 -3
- package/dist/retrieve.js +4 -4
- package/dist/thread/basic.d.ts +18 -1
- package/dist/thread/basic.d.ts.map +1 -1
- package/dist/thread/mapped.d.ts +3 -1
- package/dist/thread/mapped.d.ts.map +1 -1
- package/dist/thread/writeable.d.ts.map +1 -1
- package/dist/thread.js +1 -1
- package/dist/viewmodel/index.js +2 -2
- package/package.json +1 -1
- package/src/thread/basic.ts +31 -3
- package/src/thread/insertMissing-perf.test.ts +95 -0
- package/src/thread/mapped.ts +13 -1
- package/src/thread/membership-cache.test.ts +133 -0
- package/src/thread/writeable.ts +2 -1
- package/dist/chunk-7VHIXHNF.js.map +0 -1
- /package/dist/{chunk-ERVSWD63.js.map → chunk-AFRRFDNL.js.map} +0 -0
- /package/dist/{chunk-XEUQDEA4.js.map → chunk-B22AIVJH.js.map} +0 -0
- /package/dist/{chunk-OB7M55WA.js.map → chunk-EZZVLYYC.js.map} +0 -0
- /package/dist/{chunk-PTZJ7LVV.js.map → chunk-IC6WD7RV.js.map} +0 -0
- /package/dist/{chunk-GQDLPZEZ.js.map → chunk-PNNJMXZI.js.map} +0 -0
- /package/dist/{chunk-523IW54O.js.map → chunk-PSIMJH6F.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -2,13 +2,13 @@ import "./chunk-E46VTKTZ.js";
|
|
|
2
2
|
import "./chunk-7Z5YDQKK.js";
|
|
3
3
|
import {
|
|
4
4
|
fetchSnapshotChainUntil
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-B22AIVJH.js";
|
|
6
6
|
import {
|
|
7
7
|
agentToShortHash,
|
|
8
8
|
integratePub,
|
|
9
9
|
isShare,
|
|
10
10
|
isSubscription
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-AFRRFDNL.js";
|
|
12
12
|
import {
|
|
13
13
|
createEpochSnapshot,
|
|
14
14
|
liveEntityCollection,
|
|
@@ -16,11 +16,11 @@ import {
|
|
|
16
16
|
queryDivergencesByPrev,
|
|
17
17
|
resolveTimeSpec,
|
|
18
18
|
serializeEpochSnapshot
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-PSIMJH6F.js";
|
|
20
20
|
import {
|
|
21
21
|
updateThreadFromSnapshot,
|
|
22
22
|
withBlockCache
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-EZZVLYYC.js";
|
|
24
24
|
import {
|
|
25
25
|
carFromBlob,
|
|
26
26
|
chunkApplogs,
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
prepareSnapshotForPush,
|
|
38
38
|
streamReaderToIterable,
|
|
39
39
|
unchunkApplogsBlock
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-IC6WD7RV.js";
|
|
41
41
|
import "./chunk-YDAKBU6Q.js";
|
|
42
42
|
import {
|
|
43
43
|
LiveQueryResult,
|
|
@@ -81,7 +81,7 @@ import {
|
|
|
81
81
|
throwOnTimeout,
|
|
82
82
|
withTimeout,
|
|
83
83
|
withoutDeleted
|
|
84
|
-
} from "./chunk-
|
|
84
|
+
} from "./chunk-PNNJMXZI.js";
|
|
85
85
|
import {
|
|
86
86
|
AppLogNoCidTB,
|
|
87
87
|
AppLogNoCidTBC,
|
|
@@ -189,7 +189,7 @@ import {
|
|
|
189
189
|
withAg,
|
|
190
190
|
withPvFrom,
|
|
191
191
|
withTs
|
|
192
|
-
} from "./chunk-
|
|
192
|
+
} from "./chunk-OUQE6LG4.js";
|
|
193
193
|
import {
|
|
194
194
|
BOOL,
|
|
195
195
|
Bool,
|
package/dist/ipfs.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
fetchSnapshotChainUntil
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-B22AIVJH.js";
|
|
4
4
|
import {
|
|
5
5
|
carFromBlob,
|
|
6
6
|
collectDagBlocks,
|
|
@@ -11,9 +11,9 @@ import {
|
|
|
11
11
|
makeCarBlob,
|
|
12
12
|
makeCarOut,
|
|
13
13
|
streamReaderToIterable
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-IC6WD7RV.js";
|
|
15
15
|
import "./chunk-YDAKBU6Q.js";
|
|
16
|
-
import "./chunk-
|
|
16
|
+
import "./chunk-PNNJMXZI.js";
|
|
17
17
|
import {
|
|
18
18
|
MULTICODEC_IPNS_KEY,
|
|
19
19
|
areCidsEqual,
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
prepareForPub,
|
|
30
30
|
toIpnsString,
|
|
31
31
|
tryParseCID
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-OUQE6LG4.js";
|
|
33
33
|
import "./chunk-ZAADLBSB.js";
|
|
34
34
|
export {
|
|
35
35
|
MULTICODEC_IPNS_KEY,
|
package/dist/pubsub.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
integratePub,
|
|
4
4
|
isShare,
|
|
5
5
|
isSubscription
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-AFRRFDNL.js";
|
|
7
7
|
import {
|
|
8
8
|
chunkApplogs,
|
|
9
9
|
encodeSnapshotApplogsAsCar,
|
|
@@ -11,10 +11,10 @@ import {
|
|
|
11
11
|
isSnapBlockChunks,
|
|
12
12
|
prepareSnapshotForPush,
|
|
13
13
|
unchunkApplogsBlock
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-IC6WD7RV.js";
|
|
15
15
|
import "./chunk-YDAKBU6Q.js";
|
|
16
|
-
import "./chunk-
|
|
17
|
-
import "./chunk-
|
|
16
|
+
import "./chunk-PNNJMXZI.js";
|
|
17
|
+
import "./chunk-OUQE6LG4.js";
|
|
18
18
|
import "./chunk-ZAADLBSB.js";
|
|
19
19
|
export {
|
|
20
20
|
agentToShortHash,
|
package/dist/query.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
queryDivergencesByPrev,
|
|
6
6
|
resolveTimeSpec,
|
|
7
7
|
serializeEpochSnapshot
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-PSIMJH6F.js";
|
|
9
9
|
import {
|
|
10
10
|
LiveQueryResult,
|
|
11
11
|
QueryNode,
|
|
@@ -48,7 +48,7 @@ import {
|
|
|
48
48
|
throwOnTimeout,
|
|
49
49
|
withTimeout,
|
|
50
50
|
withoutDeleted
|
|
51
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-PNNJMXZI.js";
|
|
52
52
|
import {
|
|
53
53
|
SubscribableArrayImpl,
|
|
54
54
|
SubscribableImpl,
|
|
@@ -61,7 +61,7 @@ import {
|
|
|
61
61
|
memoizedFn,
|
|
62
62
|
prettifyThreadName,
|
|
63
63
|
refCountedMemoizedFn
|
|
64
|
-
} from "./chunk-
|
|
64
|
+
} from "./chunk-OUQE6LG4.js";
|
|
65
65
|
import "./chunk-ZAADLBSB.js";
|
|
66
66
|
export {
|
|
67
67
|
LiveQueryResult,
|
package/dist/retrieve.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
updateThreadFromSnapshot,
|
|
3
3
|
withBlockCache
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-EZZVLYYC.js";
|
|
5
|
+
import "./chunk-IC6WD7RV.js";
|
|
6
6
|
import "./chunk-YDAKBU6Q.js";
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
7
|
+
import "./chunk-PNNJMXZI.js";
|
|
8
|
+
import "./chunk-OUQE6LG4.js";
|
|
9
9
|
import "./chunk-ZAADLBSB.js";
|
|
10
10
|
export {
|
|
11
11
|
updateThreadFromSnapshot,
|
package/dist/thread/basic.d.ts
CHANGED
|
@@ -16,11 +16,28 @@ export declare abstract class Thread implements SubscribableArray<Applog> {
|
|
|
16
16
|
protected _subscribers: ((event: ThreadEvent) => void)[];
|
|
17
17
|
/** Monotonic counter incremented on every mutation. Used by memoizedFn to invalidate caches. */
|
|
18
18
|
_version: number;
|
|
19
|
+
/**
|
|
20
|
+
* Memoized cid→membership lookups, rebuilt lazily on first read after a mutation.
|
|
21
|
+
* Previously these getters rebuilt a fresh Set/Map on EVERY access, which turned a
|
|
22
|
+
* bulk `insertMissing(N)` (N × hasApplog over a thread of M) into O(N·M) — pegging a
|
|
23
|
+
* core for tens of minutes on ~90k-applog threads. Now the collection is built once
|
|
24
|
+
* per mutation batch and reused across the whole filter loop. Invalidated in
|
|
25
|
+
* {@link invalidateApplogCaches}, called from every `_applogs` mutation path.
|
|
26
|
+
*/
|
|
27
|
+
protected _cidSet: Set<CidString> | null;
|
|
28
|
+
protected _byCid: Map<CidString, Applog> | null;
|
|
19
29
|
constructor(name: string, /* = null */ parents: ArrayOrSingle<Thread> | readonly Thread[] | null, filters: readonly string[], _applogs?: Applog[]);
|
|
20
30
|
get readOnly(): any;
|
|
21
31
|
insert(appLogsToInsert: ArrayOrSingle<ApplogForInsert>): any;
|
|
22
32
|
insertRaw(appLogsToInsert: readonly Applog[]): any;
|
|
23
33
|
subscribe(callback: (event: ThreadEvent) => void, type?: 'derived' | 'reaction'): () => void;
|
|
34
|
+
/**
|
|
35
|
+
* Drop memoized cid lookups. MUST be called from every path that mutates `_applogs`
|
|
36
|
+
* (or reassigns it). {@link notifySubscribers} covers the notify-emitting mutations
|
|
37
|
+
* (insertRaw, MappedThread remap/parent-update); non-notifying mutations (e.g.
|
|
38
|
+
* {@link WriteableThread.purge}) must call this explicitly.
|
|
39
|
+
*/
|
|
40
|
+
protected invalidateApplogCaches(): void;
|
|
24
41
|
protected notifySubscribers(event: ThreadEvent): void;
|
|
25
42
|
get items(): readonly Applog[];
|
|
26
43
|
dispose(): void;
|
|
@@ -40,7 +57,7 @@ export declare abstract class Thread implements SubscribableArray<Applog> {
|
|
|
40
57
|
get latestLog(): Applog;
|
|
41
58
|
hasApplog(applog: Applog, byRef: boolean): boolean;
|
|
42
59
|
hasApplogCid(cid: CID | CidString): boolean;
|
|
43
|
-
get applogsByCid():
|
|
60
|
+
get applogsByCid(): ReadonlyMap<CidString, Applog>;
|
|
44
61
|
getApplog(cid: CID | CidString): Applog;
|
|
45
62
|
hasApplogWithDiffTs(applog: ApplogForInsert): Applog;
|
|
46
63
|
get isEmpty(): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"basic.d.ts","sourceRoot":"","sources":["../../src/thread/basic.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAGlC,OAAO,EAAE,KAAK,MAAM,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AAClF,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAC7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAG3D,OAAO,EAAiB,aAAa,EAAE,MAAM,8BAA8B,CAAA;AAI3E,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC,MAAM,CAAC,CAAA;AAC5C,8DAA8D;AAC9D,eAAO,MAAM,WAAW,yBAAmB,CAAA;AAE3C,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,SAAS,MAAM,EAAE,CAAA;AAIxD,8BAAsB,MAAO,YAAW,iBAAiB,CAAC,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"basic.d.ts","sourceRoot":"","sources":["../../src/thread/basic.ts"],"names":[],"mappings":"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"}
|
package/dist/thread/mapped.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Applog, ApplogForInsert } from '../applog/datom-types.ts';
|
|
1
|
+
import { Applog, ApplogForInsert, CidString } from '../applog/datom-types.ts';
|
|
2
2
|
import { Thread, ThreadEvent } from './basic.ts';
|
|
3
3
|
/** Delta event — only incremental changes (no init) */
|
|
4
4
|
export type DeltaEvent = {
|
|
@@ -52,5 +52,7 @@ export declare class MappedThread extends Thread {
|
|
|
52
52
|
triggerRemap(): void;
|
|
53
53
|
protected onParentUpdate(sourceThread: Thread, event: ThreadEvent): void;
|
|
54
54
|
get readOnly(): boolean;
|
|
55
|
+
get applogsCidSet(): ReadonlySet<CidString>;
|
|
56
|
+
get applogsByCid(): ReadonlyMap<CidString, Applog>;
|
|
55
57
|
}
|
|
56
58
|
//# sourceMappingURL=mapped.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mapped.d.ts","sourceRoot":"","sources":["../../src/thread/mapped.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;
|
|
1
|
+
{"version":3,"file":"mapped.d.ts","sourceRoot":"","sources":["../../src/thread/mapped.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AAG7E,OAAO,EAAe,MAAM,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAY7D,uDAAuD;AACvD,MAAM,MAAM,UAAU,GAAG;IAAE,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAA;CAAE,CAAA;AAExF,mEAAmE;AACnE,MAAM,WAAW,YAAY;IAC5B,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAA;IACd,iCAAiC;IACjC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;IAC1B,8DAA8D;IAC9D,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;CACxB;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAChC,gGAAgG;IAChG,OAAO,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAAA;IAE7C;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,YAAY,KAAK,UAAU,GAAG,IAAI,CAAA;CACtE;AAKD,MAAM,MAAM,iBAAiB,GAAG,CAAC,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,eAAe,EAAE,KAAK,MAAM,EAAE,GAAG,eAAe,EAAE,GAAG,IAAI,CAAA;AAIlI,qBAAa,YAAa,SAAQ,MAAM;IAwBtC,QAAQ,CAAC,IAAI,EAAE,MAAM;IAGrB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,SAAS,CAAC;IA5BnB,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB;IASxE,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,MAAM;IAWhC,OAAO,CAAC,oBAAoB,CAAkC;gBAGpD,IAAI,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,EACnC,OAAO,EAAE,SAAS,MAAM,EAAE,EAClB,WAAW,EAAE,gBAAgB,GAAG,IAAI,EACpC,YAAY,GAAE,iBAAwB,EACtC,SAAS,CAAC,EAAE,OAAO;IAwBrB,MAAM,CAAC,eAAe,EAAE,eAAe,EAAE;IAMzC,SAAS,CAAC,eAAe,EAAE,MAAM,EAAE;IAO1C,OAAO,CAAC,kBAAkB;IAe1B,8DAA8D;IAC9D,OAAO;IASP,2FAA2F;IAC3F,UAAU,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE;IAOxC,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,EAAE,IAAI,CAAC,EAAE,SAAS,GAAG,UAAU;IAO/E,sDAAsD;IACtD,YAAY;IASZ,SAAS,CAAC,cAAc,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW;IA2DjE,IAAI,QAAQ,YAEX;IAOD,IAAI,aAAa,IAAI,WAAW,CAAC,SAAS,CAAC,CAE1C;IACD,IAAI,YAAY,IAAI,WAAW,CAAC,SAAS,EAAE,MAAM,CAAC,CAEjD;CACD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"writeable.d.ts","sourceRoot":"","sources":["../../src/thread/writeable.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,MAAM,EAAE,eAAe,EAAE,SAAS,EAAiB,MAAM,0BAA0B,CAAA;AACjG,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAInC,8BAAsB,eAAgB,SAAQ,MAAM;gBAElD,IAAI,EAAE,MAAM,EAEZ,OAAO,EAAE,MAAM,EAAO,EACtB,OAAO,EAAE,SAAS,MAAM,EAAE;IAKpB,KAAK,CAAC,WAAW,EAAE,SAAS,EAAE;
|
|
1
|
+
{"version":3,"file":"writeable.d.ts","sourceRoot":"","sources":["../../src/thread/writeable.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,MAAM,EAAE,eAAe,EAAE,SAAS,EAAiB,MAAM,0BAA0B,CAAA;AACjG,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAInC,8BAAsB,eAAgB,SAAQ,MAAM;gBAElD,IAAI,EAAE,MAAM,EAEZ,OAAO,EAAE,MAAM,EAAO,EACtB,OAAO,EAAE,SAAS,MAAM,EAAE;IAKpB,KAAK,CAAC,WAAW,EAAE,SAAS,EAAE;IAO9B,MAAM,CAAC,eAAe,EAAE,eAAe,EAAE;IAWhD;;;;OAIG;IACI,aAAa,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,UAAQ,GAAG,MAAM,EAAE;IASjF;;;;;;;;;;;;;;OAcG;IACI,SAAS,CAAC,eAAe,EAAE,MAAM,EAAE;IA6D1C,IAAI,QAAQ,YAEX;IAED,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CACzD;AACD,qBAAa,cAAe,SAAQ,eAAe;IAgBjD,QAAQ,CAAC,SAAS,EAAE,OAAO;IAf5B,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM;IAG1B,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,UAAQ;IAGnE,MAAM,CAAC,iBAAiB,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE,MAAM;gBAMjE,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,EAAE,SAAS,MAAM,EAAE,EACjB,SAAS,EAAE,OAAO;IAK5B,IAAI,QAAQ,YAEX;cAEe,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE;CAMtC"}
|
package/dist/thread.js
CHANGED
package/dist/viewmodel/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
import {
|
|
14
14
|
lastWriteWins,
|
|
15
15
|
liveEntityAt
|
|
16
|
-
} from "../chunk-
|
|
16
|
+
} from "../chunk-PNNJMXZI.js";
|
|
17
17
|
import {
|
|
18
18
|
EntityID_LENGTH,
|
|
19
19
|
SubscribableImpl,
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
isInitEvent,
|
|
23
23
|
mintNewEntity,
|
|
24
24
|
rollingFilter
|
|
25
|
-
} from "../chunk-
|
|
25
|
+
} from "../chunk-OUQE6LG4.js";
|
|
26
26
|
import "../chunk-ZAADLBSB.js";
|
|
27
27
|
|
|
28
28
|
// src/viewmodel/builder.ts
|
package/package.json
CHANGED
package/src/thread/basic.ts
CHANGED
|
@@ -28,6 +28,16 @@ export abstract class Thread implements SubscribableArray<Applog> {
|
|
|
28
28
|
protected _subscribers: ((event: ThreadEvent) => void)[] = []
|
|
29
29
|
/** Monotonic counter incremented on every mutation. Used by memoizedFn to invalidate caches. */
|
|
30
30
|
_version = 0
|
|
31
|
+
/**
|
|
32
|
+
* Memoized cid→membership lookups, rebuilt lazily on first read after a mutation.
|
|
33
|
+
* Previously these getters rebuilt a fresh Set/Map on EVERY access, which turned a
|
|
34
|
+
* bulk `insertMissing(N)` (N × hasApplog over a thread of M) into O(N·M) — pegging a
|
|
35
|
+
* core for tens of minutes on ~90k-applog threads. Now the collection is built once
|
|
36
|
+
* per mutation batch and reused across the whole filter loop. Invalidated in
|
|
37
|
+
* {@link invalidateApplogCaches}, called from every `_applogs` mutation path.
|
|
38
|
+
*/
|
|
39
|
+
protected _cidSet: Set<CidString> | null = null
|
|
40
|
+
protected _byCid: Map<CidString, Applog> | null = null
|
|
31
41
|
|
|
32
42
|
constructor(
|
|
33
43
|
readonly name: string, /* = null */
|
|
@@ -69,8 +79,20 @@ export abstract class Thread implements SubscribableArray<Applog> {
|
|
|
69
79
|
}
|
|
70
80
|
}
|
|
71
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Drop memoized cid lookups. MUST be called from every path that mutates `_applogs`
|
|
84
|
+
* (or reassigns it). {@link notifySubscribers} covers the notify-emitting mutations
|
|
85
|
+
* (insertRaw, MappedThread remap/parent-update); non-notifying mutations (e.g.
|
|
86
|
+
* {@link WriteableThread.purge}) must call this explicitly.
|
|
87
|
+
*/
|
|
88
|
+
protected invalidateApplogCaches() {
|
|
89
|
+
this._cidSet = null
|
|
90
|
+
this._byCid = null
|
|
91
|
+
}
|
|
92
|
+
|
|
72
93
|
protected notifySubscribers(event: ThreadEvent) {
|
|
73
94
|
this._version++
|
|
95
|
+
this.invalidateApplogCaches()
|
|
74
96
|
DEBUG(`[thread: ${this.name}] notifying`, this._derivedSubscribers.length, 'derived +', this._subscribers.length, 'subscribers of', {
|
|
75
97
|
...event,
|
|
76
98
|
subs: this._subscribers,
|
|
@@ -103,7 +125,10 @@ export abstract class Thread implements SubscribableArray<Applog> {
|
|
|
103
125
|
return this._applogs.map(l => l.cid)
|
|
104
126
|
}
|
|
105
127
|
get applogsCidSet(): ReadonlySet<CidString> {
|
|
106
|
-
|
|
128
|
+
if (this._cidSet === null) {
|
|
129
|
+
this._cidSet = new Set(this._applogs.map(l => l.cid))
|
|
130
|
+
}
|
|
131
|
+
return this._cidSet
|
|
107
132
|
}
|
|
108
133
|
|
|
109
134
|
public map<R>(fn: (applog: Applog) => R) {
|
|
@@ -142,8 +167,11 @@ export abstract class Thread implements SubscribableArray<Applog> {
|
|
|
142
167
|
public hasApplogCid(cid: CID | CidString) {
|
|
143
168
|
return this.applogsCidSet.has(cid.toString()) // O(1) via Set vs O(n) via includes
|
|
144
169
|
}
|
|
145
|
-
get applogsByCid() {
|
|
146
|
-
|
|
170
|
+
get applogsByCid(): ReadonlyMap<CidString, Applog> {
|
|
171
|
+
if (this._byCid === null) {
|
|
172
|
+
this._byCid = new Map(this._applogs.map(log => [log.cid, log]))
|
|
173
|
+
}
|
|
174
|
+
return this._byCid
|
|
147
175
|
}
|
|
148
176
|
public getApplog(cid: CID | CidString) {
|
|
149
177
|
return this.applogsByCid.get(cid.toString())
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Perf regression guard for the O(n²) hang in Thread membership checks.
|
|
3
|
+
*
|
|
4
|
+
* Before: `applogsCidSet` / `applogsByCid` rebuilt a fresh Set/Map on EVERY access.
|
|
5
|
+
* `insertMissing(N)` runs `hasApplog` (→ applogsCidSet) N times over a thread of M,
|
|
6
|
+
* and `insertRaw` filters existing again — so merging N applogs into a thread of M
|
|
7
|
+
* cost O(N·M) Set rebuilds. A daemon reloading ~89k local applogs into a thread that
|
|
8
|
+
* already held ~89k pegged a core at 98% for 45–70 min (~8 billion ops).
|
|
9
|
+
*
|
|
10
|
+
* After: the cid Set/Map is memoized and invalidated only on mutation, so the whole
|
|
11
|
+
* merge is O(N + M). This test inserts 50k applogs into a 50k thread and asserts it
|
|
12
|
+
* finishes in well under a second (was minutes).
|
|
13
|
+
*/
|
|
14
|
+
import { describe, expect, it } from 'vitest'
|
|
15
|
+
import { finalizeApplogForInsert } from '../applog/applog-helpers.ts'
|
|
16
|
+
import { sortApplogsByTs } from '../applog/applog-utils.ts'
|
|
17
|
+
import type { Applog, ApplogForInsert } from '../applog/datom-types.ts'
|
|
18
|
+
import { ThreadInMemory } from './writeable.ts'
|
|
19
|
+
|
|
20
|
+
/** Build N valid, unique applogs (real CIDs) as cheaply as we can. */
|
|
21
|
+
function makeLogs(count: number, startId = 0): Applog[] {
|
|
22
|
+
const logs: Applog[] = new Array(count)
|
|
23
|
+
for (let i = 0; i < count; i++) {
|
|
24
|
+
const id = startId + i
|
|
25
|
+
logs[i] = finalizeApplogForInsert(
|
|
26
|
+
{
|
|
27
|
+
en: `entity-${id}`,
|
|
28
|
+
at: 'value',
|
|
29
|
+
vl: id,
|
|
30
|
+
ts: new Date(1700000000000 + id * 1000).toISOString(),
|
|
31
|
+
pv: null,
|
|
32
|
+
ag: 'perfAgent',
|
|
33
|
+
} as ApplogForInsert,
|
|
34
|
+
{},
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
return sortApplogsByTs(logs)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
describe('insertMissing perf (O(n²) regression guard)', () => {
|
|
41
|
+
it('merges 50k applogs into a 50k thread in well under 1s', () => {
|
|
42
|
+
const N = 50_000
|
|
43
|
+
const logs = makeLogs(N)
|
|
44
|
+
|
|
45
|
+
// Thread already holds all N — the daemon's "reload local applogs already present" case.
|
|
46
|
+
const thread = ThreadInMemory.fromArray([...logs], 'perf-thread')
|
|
47
|
+
expect(thread.size).toBe(N)
|
|
48
|
+
|
|
49
|
+
const t0 = performance.now()
|
|
50
|
+
const inserted = thread.insertMissing(logs) // every log already present → nothing to insert
|
|
51
|
+
const ms = performance.now() - t0
|
|
52
|
+
|
|
53
|
+
expect(inserted).toEqual([]) // all filtered out as existing
|
|
54
|
+
expect(thread.size).toBe(N)
|
|
55
|
+
// eslint-disable-next-line no-console
|
|
56
|
+
console.log(`[insertMissing-perf] insertMissing(${N}) into ${N}-thread: ${ms.toFixed(1)}ms`)
|
|
57
|
+
expect(ms).toBeLessThan(1000)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('merges 25k new + 25k existing correctly and fast', () => {
|
|
61
|
+
const existing = makeLogs(25_000, 0)
|
|
62
|
+
const fresh = makeLogs(25_000, 1_000_000)
|
|
63
|
+
const thread = ThreadInMemory.fromArray([...existing], 'perf-thread-mixed')
|
|
64
|
+
|
|
65
|
+
const mixed = sortApplogsByTs([...existing, ...fresh])
|
|
66
|
+
const t0 = performance.now()
|
|
67
|
+
const inserted = thread.insertMissing(mixed)
|
|
68
|
+
const ms = performance.now() - t0
|
|
69
|
+
|
|
70
|
+
expect(inserted).toHaveLength(25_000) // only the fresh ones
|
|
71
|
+
expect(thread.size).toBe(50_000)
|
|
72
|
+
// Cache stayed correct across the mutation: freshly-inserted cids are now members.
|
|
73
|
+
expect(thread.hasApplogCid(fresh[0].cid)).toBe(true)
|
|
74
|
+
expect(thread.hasApplogCid(fresh[fresh.length - 1].cid)).toBe(true)
|
|
75
|
+
// eslint-disable-next-line no-console
|
|
76
|
+
console.log(`[insertMissing-perf] insertMissing(25k new + 25k dup): ${ms.toFixed(1)}ms`)
|
|
77
|
+
expect(ms).toBeLessThan(1000)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('membership cache invalidates on insert and purge', () => {
|
|
81
|
+
const logs = makeLogs(1_000)
|
|
82
|
+
const thread = ThreadInMemory.fromArray([...logs.slice(0, 500)], 'cache-correctness')
|
|
83
|
+
|
|
84
|
+
// Not present yet → build cache, miss.
|
|
85
|
+
expect(thread.hasApplogCid(logs[600].cid)).toBe(false)
|
|
86
|
+
// Insert → cache must invalidate so the new cid is visible.
|
|
87
|
+
thread.insertRaw([logs[600]])
|
|
88
|
+
expect(thread.hasApplogCid(logs[600].cid)).toBe(true)
|
|
89
|
+
expect(thread.getApplog(logs[600].cid)?.en).toBe(logs[600].en)
|
|
90
|
+
// Purge → cache must invalidate so the removed cid disappears.
|
|
91
|
+
thread.purge([logs[600].cid])
|
|
92
|
+
expect(thread.hasApplogCid(logs[600].cid)).toBe(false)
|
|
93
|
+
expect(thread.getApplog(logs[600].cid)).toBeUndefined()
|
|
94
|
+
})
|
|
95
|
+
})
|
package/src/thread/mapped.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Logger } from 'besonders-logger'
|
|
2
2
|
import { DebouncedFunc, pull, sortedIndexBy } from 'lodash-es'
|
|
3
|
-
import { Applog, ApplogForInsert } from '../applog/datom-types.ts'
|
|
3
|
+
import { Applog, ApplogForInsert, CidString } from '../applog/datom-types.ts'
|
|
4
4
|
import { applogsSummary } from '../applog/applog-utils.ts'
|
|
5
5
|
import { arrayIfSingle } from '../types/typescript-utils.ts'
|
|
6
6
|
import { isInitEvent, Thread, ThreadEvent } from './basic.ts'
|
|
@@ -231,4 +231,16 @@ export class MappedThread extends Thread {
|
|
|
231
231
|
get readOnly() {
|
|
232
232
|
return this._readOnly ?? false
|
|
233
233
|
}
|
|
234
|
+
|
|
235
|
+
// A no-derivation MappedThread SHARES its parent's `_applogs` array (see constructor) and never
|
|
236
|
+
// mutates it itself — the parent does, and the parent invalidates only its own cid caches. So a
|
|
237
|
+
// local cache here would go stale after a parent insert. Delegate membership lookups to the parent
|
|
238
|
+
// (chaining up to the array's real owner), whose cache is always fresh. Derived threads own their
|
|
239
|
+
// array and use the inherited, self-invalidating cache.
|
|
240
|
+
get applogsCidSet(): ReadonlySet<CidString> {
|
|
241
|
+
return this._derivation ? super.applogsCidSet : this.parents[0].applogsCidSet
|
|
242
|
+
}
|
|
243
|
+
get applogsByCid(): ReadonlyMap<CidString, Applog> {
|
|
244
|
+
return this._derivation ? super.applogsByCid : this.parents[0].applogsByCid
|
|
245
|
+
}
|
|
234
246
|
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guards for the memoized cid-membership caches (`_cidSet` / `_byCid`) that back
|
|
3
|
+
* `hasApplogCid` / `getApplog` (and thus `hasApplog`, `insertMissing`).
|
|
4
|
+
*
|
|
5
|
+
* These caches are built lazily on first read and MUST be invalidated on every
|
|
6
|
+
* `_applogs` mutation. This suite pins that invariant on each mutation/composition
|
|
7
|
+
* path so a future refactor (e.g. re-adding a naive local cache to a shared-array
|
|
8
|
+
* wrapper) fails loudly instead of silently serving stale membership.
|
|
9
|
+
*
|
|
10
|
+
* The pattern in each test is deliberately: read (build cache) → mutate → read again.
|
|
11
|
+
* A missing invalidation only surfaces when the cache was populated BEFORE the mutation.
|
|
12
|
+
*/
|
|
13
|
+
import { describe, expect, it } from 'vitest'
|
|
14
|
+
import { finalizeApplogForInsert, joinThreads } from '../applog/applog-helpers.ts'
|
|
15
|
+
import type { Applog, ApplogForInsert } from '../applog/datom-types.ts'
|
|
16
|
+
import { MappedThread } from './mapped.ts'
|
|
17
|
+
import { ThreadInMemory } from './writeable.ts'
|
|
18
|
+
|
|
19
|
+
let idSeq = 0
|
|
20
|
+
/** A valid, unique Applog with a real CID. */
|
|
21
|
+
function makeLog(en = `e-${idSeq}`): Applog {
|
|
22
|
+
const id = idSeq++
|
|
23
|
+
return finalizeApplogForInsert(
|
|
24
|
+
{ en, at: 'v', vl: id, ts: new Date(1700000000000 + id * 1000).toISOString(), pv: null, ag: 'a' } as ApplogForInsert,
|
|
25
|
+
{},
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe('membership cache invalidation', () => {
|
|
30
|
+
it('WriteableThread.insertRaw makes new cids visible (was cache built before insert)', () => {
|
|
31
|
+
const [a, b] = [makeLog(), makeLog()]
|
|
32
|
+
const t = ThreadInMemory.fromArray([a], 'insertRaw')
|
|
33
|
+
|
|
34
|
+
expect(t.hasApplogCid(b.cid)).toBe(false) // build cache while b absent
|
|
35
|
+
expect(t.getApplog(b.cid)).toBeUndefined()
|
|
36
|
+
|
|
37
|
+
t.insertRaw([b])
|
|
38
|
+
|
|
39
|
+
expect(t.hasApplogCid(b.cid)).toBe(true)
|
|
40
|
+
expect(t.getApplog(b.cid)).toBe(b)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('WriteableThread.insert (finalize path) invalidates too', () => {
|
|
44
|
+
const t = ThreadInMemory.fromArray([makeLog()], 'insert')
|
|
45
|
+
expect(t.hasApplogCid('bogus-cid')).toBe(false) // build cache
|
|
46
|
+
const before = t.size
|
|
47
|
+
t.insert([{ en: 'new-entity', at: 'v', vl: 1, ag: 'a' } as ApplogForInsert])
|
|
48
|
+
expect(t.size).toBe(before + 1)
|
|
49
|
+
const inserted = t.applogs[t.applogs.length - 1]
|
|
50
|
+
expect(t.hasApplogCid(inserted.cid)).toBe(true)
|
|
51
|
+
expect(t.getApplog(inserted.cid)).toBe(inserted)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('WriteableThread.purge removes cids from the cache (purge does not notify)', () => {
|
|
55
|
+
const [a, b] = [makeLog(), makeLog()]
|
|
56
|
+
const t = ThreadInMemory.fromArray([a, b], 'purge')
|
|
57
|
+
|
|
58
|
+
expect(t.hasApplogCid(b.cid)).toBe(true) // build cache while b present
|
|
59
|
+
expect(t.getApplog(b.cid)).toBe(b)
|
|
60
|
+
|
|
61
|
+
t.purge([b.cid])
|
|
62
|
+
|
|
63
|
+
expect(t.hasApplogCid(b.cid)).toBe(false)
|
|
64
|
+
expect(t.getApplog(b.cid)).toBeUndefined()
|
|
65
|
+
expect(t.hasApplogCid(a.cid)).toBe(true) // untouched cid still present
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('insertMissing leaves the cache consistent', () => {
|
|
69
|
+
const [a, b, c] = [makeLog(), makeLog(), makeLog()]
|
|
70
|
+
const t = ThreadInMemory.fromArray([a], 'insertMissing')
|
|
71
|
+
expect(t.hasApplogCid(b.cid)).toBe(false) // build cache
|
|
72
|
+
const inserted = t.insertMissing([a, b, c]) // a is dup, b & c are new
|
|
73
|
+
expect(inserted).toHaveLength(2)
|
|
74
|
+
expect(t.hasApplogCid(b.cid)).toBe(true)
|
|
75
|
+
expect(t.hasApplogCid(c.cid)).toBe(true)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
describe('derived MappedThread (owns its array, self-invalidating cache)', () => {
|
|
79
|
+
it('reflects inserts routed through a parent (joinThreads)', () => {
|
|
80
|
+
const [a, b, fresh] = [makeLog(), makeLog(), makeLog()]
|
|
81
|
+
const pa = ThreadInMemory.fromArray([a], 'A')
|
|
82
|
+
const pb = ThreadInMemory.fromArray([b], 'B')
|
|
83
|
+
const joined = joinThreads([pa, pb])
|
|
84
|
+
|
|
85
|
+
expect(joined.hasApplogCid(fresh.cid)).toBe(false) // build joined cache
|
|
86
|
+
expect(joined.hasApplogCid(a.cid)).toBe(true)
|
|
87
|
+
|
|
88
|
+
pa.insertRaw([fresh]) // parent delta → joined.onParentUpdate → must invalidate joined cache
|
|
89
|
+
|
|
90
|
+
expect(joined.hasApplogCid(fresh.cid)).toBe(true)
|
|
91
|
+
expect(joined.getApplog(fresh.cid)).toBe(fresh)
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
describe('shared-array MappedThread (delegates to parent, no local cache)', () => {
|
|
96
|
+
it('asReadOnly wrapper sees parent inserts through delegation', () => {
|
|
97
|
+
const [a, fresh] = [makeLog(), makeLog()]
|
|
98
|
+
const parent = ThreadInMemory.fromArray([a], 'parent')
|
|
99
|
+
const ro = MappedThread.asReadOnly(parent) as MappedThread
|
|
100
|
+
|
|
101
|
+
expect(ro.hasApplogCid(fresh.cid)).toBe(false) // populate whatever cache the wrapper would keep
|
|
102
|
+
expect(ro.getApplog(fresh.cid)).toBeUndefined()
|
|
103
|
+
|
|
104
|
+
parent.insertRaw([fresh]) // mutates the SHARED array; wrapper is never notified
|
|
105
|
+
|
|
106
|
+
expect(ro.hasApplogCid(fresh.cid)).toBe(true) // only correct via delegation to parent
|
|
107
|
+
expect(ro.getApplog(fresh.cid)).toBe(fresh)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('nested wrapper chain still delegates to the true owner', () => {
|
|
111
|
+
const [a, fresh] = [makeLog(), makeLog()]
|
|
112
|
+
const parent = ThreadInMemory.fromArray([a], 'owner')
|
|
113
|
+
const ro1 = MappedThread.asReadOnly(parent) as MappedThread
|
|
114
|
+
const ro2 = MappedThread.asReadOnly(ro1) as MappedThread
|
|
115
|
+
|
|
116
|
+
expect(ro2.hasApplogCid(fresh.cid)).toBe(false)
|
|
117
|
+
parent.insertRaw([fresh])
|
|
118
|
+
expect(ro2.hasApplogCid(fresh.cid)).toBe(true)
|
|
119
|
+
expect(ro2.getApplog(fresh.cid)).toBe(fresh)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('mapWrites wrapper reflects parent state after purge', () => {
|
|
123
|
+
const [a, b] = [makeLog(), makeLog()]
|
|
124
|
+
const parent = ThreadInMemory.fromArray([a, b], 'parent')
|
|
125
|
+
const wrapped = MappedThread.mapWrites(parent, 'Pass', (logs) => logs) as MappedThread
|
|
126
|
+
|
|
127
|
+
expect(wrapped.hasApplogCid(b.cid)).toBe(true) // build via delegation
|
|
128
|
+
parent.purge([b.cid])
|
|
129
|
+
expect(wrapped.hasApplogCid(b.cid)).toBe(false)
|
|
130
|
+
expect(wrapped.getApplog(b.cid)).toBeUndefined()
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
})
|
package/src/thread/writeable.ts
CHANGED
|
@@ -20,6 +20,7 @@ export abstract class WriteableThread extends Thread {
|
|
|
20
20
|
const beforeCount = this.applogs.length
|
|
21
21
|
// HACK setting readonly
|
|
22
22
|
this._applogs = this.applogs.filter(log => !(cidsToPurge.includes(log.cid)))
|
|
23
|
+
this.invalidateApplogCaches() // purge doesn't notifySubscribers, so invalidate the cid caches directly
|
|
23
24
|
return beforeCount - this.applogs.length
|
|
24
25
|
}
|
|
25
26
|
public insert(appLogsToInsert: ApplogForInsert[]) {
|
|
@@ -138,7 +139,7 @@ export class ThreadInMemory extends WriteableThread {
|
|
|
138
139
|
}
|
|
139
140
|
static fromReadOnlyArray(applogs: readonly Applog[], name?: string) {
|
|
140
141
|
// @ts-expect-error readonly conditional drama
|
|
141
|
-
return new ThreadInMemory(name ?? 'in-memory', applogs, [], true)
|
|
142
|
+
return new ThreadInMemory(name ?? 'in-memory', applogs, [], true)
|
|
142
143
|
}
|
|
143
144
|
|
|
144
145
|
constructor(
|