@wovin/core 0.1.36 → 0.2.0

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 (212) hide show
  1. package/README.md +0 -12
  2. package/dist/applog/applog-helpers.d.ts +12 -12
  3. package/dist/applog/applog-helpers.d.ts.map +1 -1
  4. package/dist/applog/applog-utils.d.ts +25 -6
  5. package/dist/applog/applog-utils.d.ts.map +1 -1
  6. package/dist/applog/datom-types.d.ts +4 -5
  7. package/dist/applog/datom-types.d.ts.map +1 -1
  8. package/dist/applog.d.ts +3 -3
  9. package/dist/applog.d.ts.map +1 -1
  10. package/dist/{applog.min.js → applog.js} +6 -7
  11. package/dist/blockstore.d.ts +1 -1
  12. package/dist/blockstore.d.ts.map +1 -1
  13. package/dist/{blockstore.min.js → blockstore.js} +1 -3
  14. package/dist/{blockstore.min.js.map → blockstore.js.map} +1 -1
  15. package/dist/{chunk-KXMTKPF4.min.js → chunk-3JZMOEOD.js} +8 -8
  16. package/dist/chunk-3JZMOEOD.js.map +1 -0
  17. package/dist/chunk-3WZVG277.js +434 -0
  18. package/dist/chunk-3WZVG277.js.map +1 -0
  19. package/dist/chunk-7Z5YDQKK.js +1 -0
  20. package/dist/chunk-CPSDKFBG.js +147 -0
  21. package/dist/chunk-CPSDKFBG.js.map +1 -0
  22. package/dist/chunk-E46VTKTZ.js +1 -0
  23. package/dist/{chunk-H3VQJP56.min.js → chunk-J2FDHGOZ.js} +9 -9
  24. package/dist/chunk-J2FDHGOZ.js.map +1 -0
  25. package/dist/chunk-L5EEEGE6.js +1862 -0
  26. package/dist/chunk-L5EEEGE6.js.map +1 -0
  27. package/dist/{chunk-BRC7LSM6.min.js → chunk-PD3C7XUM.js} +5 -5
  28. package/dist/chunk-PD3C7XUM.js.map +1 -0
  29. package/dist/chunk-QZXKQCAY.js +1026 -0
  30. package/dist/chunk-QZXKQCAY.js.map +1 -0
  31. package/dist/{chunk-QPGEBDMJ.min.js → chunk-YDAKBU6Q.js} +1 -1
  32. package/dist/chunk-YDAKBU6Q.js.map +1 -0
  33. package/dist/chunk-ZAADLBSB.js +36 -0
  34. package/dist/chunk-ZAADLBSB.js.map +1 -0
  35. package/dist/index.d.ts +7 -7
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/{index.min.js → index.js} +73 -46
  38. package/dist/ipfs/car.d.ts +11 -11
  39. package/dist/ipfs/car.d.ts.map +1 -1
  40. package/dist/ipfs/ipfs-utils.d.ts +2 -2
  41. package/dist/ipfs/ipfs-utils.d.ts.map +1 -1
  42. package/dist/ipfs.d.ts +3 -3
  43. package/dist/ipfs.d.ts.map +1 -1
  44. package/dist/{ipfs.min.js → ipfs.js} +7 -10
  45. package/dist/ipns.d.ts +1 -1
  46. package/dist/ipns.d.ts.map +1 -1
  47. package/dist/ipns.js +64 -0
  48. package/dist/ipns.js.map +1 -0
  49. package/dist/pubsub/pub-pull.d.ts +3 -3
  50. package/dist/pubsub/pub-pull.d.ts.map +1 -1
  51. package/dist/pubsub/pubsub-types.d.ts +3 -3
  52. package/dist/pubsub/pubsub-types.d.ts.map +1 -1
  53. package/dist/pubsub/snap-push.d.ts +4 -4
  54. package/dist/pubsub/snap-push.d.ts.map +1 -1
  55. package/dist/pubsub/ucan.d.ts +1 -1
  56. package/dist/pubsub/ucan.d.ts.map +1 -1
  57. package/dist/pubsub.d.ts +4 -4
  58. package/dist/pubsub.d.ts.map +1 -1
  59. package/dist/{pubsub.min.js → pubsub.js} +7 -10
  60. package/dist/query/attr-helpers.d.ts +5 -0
  61. package/dist/query/attr-helpers.d.ts.map +1 -0
  62. package/dist/query/basic.d.ts +85 -21
  63. package/dist/query/basic.d.ts.map +1 -1
  64. package/dist/query/divergences.d.ts +5 -5
  65. package/dist/query/divergences.d.ts.map +1 -1
  66. package/dist/query/entity-collection.d.ts +19 -0
  67. package/dist/query/entity-collection.d.ts.map +1 -0
  68. package/dist/query/matchers.d.ts +1 -1
  69. package/dist/query/matchers.d.ts.map +1 -1
  70. package/dist/query/memoized.d.ts +66 -0
  71. package/dist/query/memoized.d.ts.map +1 -0
  72. package/dist/query/situations.d.ts +2 -1
  73. package/dist/query/situations.d.ts.map +1 -1
  74. package/dist/query/subscribable.d.ts +111 -0
  75. package/dist/query/subscribable.d.ts.map +1 -0
  76. package/dist/query/types.d.ts +54 -14
  77. package/dist/query/types.d.ts.map +1 -1
  78. package/dist/query.d.ts +9 -5
  79. package/dist/query.d.ts.map +1 -1
  80. package/dist/{query.min.js → query.js} +51 -32
  81. package/dist/retrieve/index.d.ts +1 -1
  82. package/dist/retrieve/index.d.ts.map +1 -1
  83. package/dist/retrieve/update-thread.d.ts +3 -3
  84. package/dist/retrieve/update-thread.d.ts.map +1 -1
  85. package/dist/retrieve.d.ts +1 -1
  86. package/dist/retrieve.d.ts.map +1 -1
  87. package/dist/retrieve.js +14 -0
  88. package/dist/thread/basic.d.ts +15 -19
  89. package/dist/thread/basic.d.ts.map +1 -1
  90. package/dist/thread/filters.d.ts +8 -10
  91. package/dist/thread/filters.d.ts.map +1 -1
  92. package/dist/thread/indexes.d.ts +56 -0
  93. package/dist/thread/indexes.d.ts.map +1 -0
  94. package/dist/thread/mapped.d.ts +40 -11
  95. package/dist/thread/mapped.d.ts.map +1 -1
  96. package/dist/thread/utils.d.ts +5 -5
  97. package/dist/thread/utils.d.ts.map +1 -1
  98. package/dist/thread/writeable.d.ts +2 -2
  99. package/dist/thread/writeable.d.ts.map +1 -1
  100. package/dist/thread.d.ts +6 -5
  101. package/dist/thread.d.ts.map +1 -1
  102. package/dist/{thread.min.js → thread.js} +9 -6
  103. package/dist/types/typescript-utils.d.ts +6 -5
  104. package/dist/types/typescript-utils.d.ts.map +1 -1
  105. package/dist/types.d.ts +1 -1
  106. package/dist/types.d.ts.map +1 -1
  107. package/dist/{types.min.js → types.js} +3 -4
  108. package/dist/utils/debug-name.d.ts +13 -0
  109. package/dist/utils/debug-name.d.ts.map +1 -0
  110. package/dist/utils.d.ts +1 -1
  111. package/dist/utils.d.ts.map +1 -1
  112. package/dist/utils.js +9 -0
  113. package/package.json +32 -23
  114. package/src/applog/applog-helpers.ts +155 -0
  115. package/src/applog/applog-utils.test.ts +108 -0
  116. package/src/applog/applog-utils.ts +507 -0
  117. package/src/applog/datom-types.ts +148 -0
  118. package/src/applog.ts +3 -0
  119. package/src/blockstore/index.ts +36 -0
  120. package/src/blockstore.ts +1 -0
  121. package/src/index.ts +8 -0
  122. package/src/ipfs/car.ts +291 -0
  123. package/src/ipfs/fetch-snapshot-chain.ts +135 -0
  124. package/src/ipfs/ipfs-utils.ts +132 -0
  125. package/src/ipfs.ts +3 -0
  126. package/src/ipns/ipns-record.ts +115 -0
  127. package/src/ipns.ts +1 -0
  128. package/src/pubsub/UCAN Specs Overview.md +217 -0
  129. package/src/pubsub/connector.ts +9 -0
  130. package/src/pubsub/pub-pull.ts +31 -0
  131. package/src/pubsub/pubsub-types.ts +90 -0
  132. package/src/pubsub/snap-push.ts +277 -0
  133. package/src/pubsub/ucan-example.ts +61 -0
  134. package/src/pubsub/ucan.ts +56 -0
  135. package/src/pubsub.ts +4 -0
  136. package/src/query/attr-helpers.ts +5 -0
  137. package/src/query/basic.ts +1245 -0
  138. package/src/query/divergences.ts +50 -0
  139. package/src/query/entity-collection.ts +131 -0
  140. package/src/query/liveFilterAndMap.test.ts +102 -0
  141. package/src/query/matchers.ts +8 -0
  142. package/src/query/memoized.test.ts +151 -0
  143. package/src/query/memoized.ts +180 -0
  144. package/src/query/query-steps.ts +4 -0
  145. package/src/query/query.test.ts +538 -0
  146. package/src/query/situations.ts +261 -0
  147. package/src/query/subscribable.test.ts +245 -0
  148. package/src/query/subscribable.ts +234 -0
  149. package/src/query/types.ts +155 -0
  150. package/src/query/withoutDeleted.test.ts +204 -0
  151. package/src/query.ts +9 -0
  152. package/src/retrieve/index.ts +1 -0
  153. package/src/retrieve/update-thread.ts +248 -0
  154. package/src/retrieve.ts +1 -0
  155. package/src/test/perf/query.1m.perf.test.ts +94 -0
  156. package/src/test/perf/query.perf.test.ts +389 -0
  157. package/src/test/perf/query.realdata.perf.test.ts +182 -0
  158. package/src/thread/basic.ts +209 -0
  159. package/src/thread/filters.ts +227 -0
  160. package/src/thread/indexes.ts +250 -0
  161. package/src/thread/joinThreads.test.ts +304 -0
  162. package/src/thread/mapped.ts +226 -0
  163. package/src/thread/utils.ts +144 -0
  164. package/src/thread/writeable.ts +163 -0
  165. package/src/thread.ts +6 -0
  166. package/src/types/typescript-utils.ts +64 -0
  167. package/src/types.ts +1 -0
  168. package/src/utils/debug-name.ts +54 -0
  169. package/src/utils.ts +4 -0
  170. package/dist/chunk-2Y2PYHGR.min.js +0 -65
  171. package/dist/chunk-2Y2PYHGR.min.js.map +0 -1
  172. package/dist/chunk-5MMGBK2U.min.js +0 -1
  173. package/dist/chunk-7IDQIMQO.min.js +0 -1
  174. package/dist/chunk-BRC7LSM6.min.js.map +0 -1
  175. package/dist/chunk-COXXILXC.min.js +0 -512
  176. package/dist/chunk-COXXILXC.min.js.map +0 -1
  177. package/dist/chunk-GDX2OO7L.min.js +0 -9080
  178. package/dist/chunk-GDX2OO7L.min.js.map +0 -1
  179. package/dist/chunk-H3VQJP56.min.js.map +0 -1
  180. package/dist/chunk-HYMC7W6S.min.js +0 -1549
  181. package/dist/chunk-HYMC7W6S.min.js.map +0 -1
  182. package/dist/chunk-KEHU7HGZ.min.js +0 -5216
  183. package/dist/chunk-KEHU7HGZ.min.js.map +0 -1
  184. package/dist/chunk-KXMTKPF4.min.js.map +0 -1
  185. package/dist/chunk-PHITDXZT.min.js +0 -36
  186. package/dist/chunk-QO2KMGDN.min.js +0 -3771
  187. package/dist/chunk-QO2KMGDN.min.js.map +0 -1
  188. package/dist/chunk-QPGEBDMJ.min.js.map +0 -1
  189. package/dist/chunk-WXLCBTHX.min.js +0 -1606
  190. package/dist/chunk-WXLCBTHX.min.js.map +0 -1
  191. package/dist/ipns.min.js +0 -6419
  192. package/dist/ipns.min.js.map +0 -1
  193. package/dist/mobx/mobx-utils.d.ts +0 -82
  194. package/dist/mobx/mobx-utils.d.ts.map +0 -1
  195. package/dist/mobx.d.ts +0 -2
  196. package/dist/mobx.d.ts.map +0 -1
  197. package/dist/mobx.min.js +0 -141
  198. package/dist/retrieve.min.js +0 -17
  199. package/dist/types.min.js.map +0 -1
  200. package/dist/utils.min.js +0 -10
  201. package/dist/utils.min.js.map +0 -1
  202. /package/dist/{applog.min.js.map → applog.js.map} +0 -0
  203. /package/dist/{chunk-5MMGBK2U.min.js.map → chunk-7Z5YDQKK.js.map} +0 -0
  204. /package/dist/{chunk-7IDQIMQO.min.js.map → chunk-E46VTKTZ.js.map} +0 -0
  205. /package/dist/{chunk-PHITDXZT.min.js.map → index.js.map} +0 -0
  206. /package/dist/{index.min.js.map → ipfs.js.map} +0 -0
  207. /package/dist/{ipfs.min.js.map → pubsub.js.map} +0 -0
  208. /package/dist/{mobx.min.js.map → query.js.map} +0 -0
  209. /package/dist/{pubsub.min.js.map → retrieve.js.map} +0 -0
  210. /package/dist/{query.min.js.map → thread.js.map} +0 -0
  211. /package/dist/{retrieve.min.js.map → types.js.map} +0 -0
  212. /package/dist/{thread.min.js.map → utils.js.map} +0 -0
@@ -0,0 +1,248 @@
1
+ import { CarReader } from '@ipld/car'
2
+ import * as dagJson from '@ipld/dag-json'
3
+ import { Logger } from 'besonders-logger'
4
+ import { CID } from 'multiformats/cid'
5
+ import type { Applog } from '../applog/datom-types.ts'
6
+ import { removeDuplicateAppLogs } from '../applog/applog-utils.ts'
7
+ import type { SnapRootBlock, SnapBlockLogsOrChunks } from '../pubsub/pubsub-types.ts'
8
+ import { unchunkApplogsBlock } from '../pubsub/snap-push.ts'
9
+ import { areCidsEqual } from '../ipfs/ipfs-utils.ts'
10
+ import type { WriteableThread } from '../thread/writeable.ts'
11
+ import type { BlockStore } from '../blockstore/index.ts'
12
+
13
+ const { WARN, LOG, DEBUG, VERBOSE, ERROR } = Logger.setup(Logger.INFO) // eslint-disable-line no-unused-vars
14
+
15
+ /**
16
+ * Block retrieval abstraction - fetch or get blocks.
17
+ * Implemented by gateway retriever or local blockstore.
18
+ */
19
+ export interface BlockRetriever {
20
+ /** Get single block by CID */
21
+ get(cid: CID): Promise<Uint8Array>
22
+ /** Get all blocks in DAG rooted at CID (for applogs/info sub-DAGs) */
23
+ getDag(cid: CID): AsyncIterable<{ cid: CID; bytes: Uint8Array }>
24
+ }
25
+
26
+ /**
27
+ * Wrap a BlockRetriever so fetched blocks flow through a BlockStore.
28
+ * - get: delegates to store.get() (which handles local-first / remote fallback)
29
+ * - getDag: streams from source, puts each block into store
30
+ */
31
+ export function withBlockCache(
32
+ source: BlockRetriever,
33
+ store: BlockStore,
34
+ ): BlockRetriever {
35
+ return {
36
+ get: (cid) => store.get(cid),
37
+ async *getDag(cid) {
38
+ for await (const block of source.getDag(cid)) {
39
+ await store.put(block.cid, block.bytes)
40
+ yield block
41
+ }
42
+ },
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Options for updateThreadFromSnapshot
48
+ */
49
+ export interface UpdateOptions {
50
+ /** CID of last included snapshot - exclude this and older snapshots */
51
+ excludeSnapshotCID?: CID
52
+ /** Stop when we reach this counter (walking backwards) */
53
+ stopAtCounter?: number
54
+ /** Maximum number of snapshots to traverse (default: 100) */
55
+ maxDepth?: number
56
+ }
57
+
58
+ /**
59
+ * Result from updateThreadFromSnapshot
60
+ */
61
+ export interface UpdateResult {
62
+ /** Root CID that was fetched */
63
+ cid: CID
64
+ /** All applogs decoded from the chain */
65
+ applogs: Applog[]
66
+ /** Count of applogs actually inserted (not duplicates) */
67
+ insertedCount: number
68
+ /** Number of snapshots traversed */
69
+ snapshotCount: number
70
+ /** Counter range encountered (min/max) */
71
+ counterRange?: { minCounter: number; maxCounter: number }
72
+ }
73
+
74
+ /**
75
+ * Simple in-memory block store used during snapshot chain fetch.
76
+ */
77
+ interface MemoryBlockStore {
78
+ get(cid: CID): Uint8Array | undefined
79
+ put(cid: CID, bytes: Uint8Array): void
80
+ }
81
+
82
+ function createMemoryBlockStore(): MemoryBlockStore {
83
+ const blocks = new Map<string, Uint8Array>()
84
+ return {
85
+ get(cid: CID) {
86
+ return blocks.get(cid.toV1().toString())
87
+ },
88
+ put(cid: CID, bytes: Uint8Array) {
89
+ blocks.set(cid.toV1().toString(), bytes)
90
+ },
91
+ }
92
+ }
93
+
94
+ async function getDecodedBlock<T>(blockStore: MemoryBlockStore, cid: CID): Promise<T | null> {
95
+ const bytes = blockStore.get(cid)
96
+ if (!bytes) return null
97
+ return dagJson.decode(bytes) as T
98
+ }
99
+
100
+ /**
101
+ * Fetch snapshot chain from CID using a BlockRetriever, decode applogs, insert into thread.
102
+ * Stops before excludeSnapshotCID if provided (incremental update).
103
+ *
104
+ * @param thread - WriteableThread to insert applogs into
105
+ * @param cid - Root CID of the snapshot to start from
106
+ * @param retriever - BlockRetriever for fetching blocks
107
+ * @param options - Optional configuration
108
+ * @returns UpdateResult with applogs and counts
109
+ */
110
+ export async function updateThreadFromSnapshot(
111
+ thread: WriteableThread,
112
+ cid: CID,
113
+ retriever: BlockRetriever,
114
+ options?: UpdateOptions
115
+ ): Promise<UpdateResult> {
116
+ const { excludeSnapshotCID, stopAtCounter, maxDepth = 100 } = options ?? {}
117
+
118
+ DEBUG('[updateThreadFromSnapshot] starting from', cid.toString(), {
119
+ excludeSnapshotCID: excludeSnapshotCID?.toString(),
120
+ stopAtCounter,
121
+ maxDepth,
122
+ })
123
+
124
+ const blockStore = createMemoryBlockStore()
125
+ const visited = new Set<string>()
126
+ let currentCID: CID | undefined = cid
127
+ let snapshotCount = 0
128
+ const allApplogs: Applog[] = []
129
+ let minCounter = Infinity
130
+ let maxCounter = -Infinity
131
+ let lastCounter: number | undefined
132
+
133
+ while (currentCID && snapshotCount < maxDepth) {
134
+ const cidStr = currentCID.toString()
135
+
136
+ // Loop detection
137
+ if (visited.has(cidStr)) {
138
+ throw ERROR('[updateThreadFromSnapshot] snapshot chain has a loop', {
139
+ currentCID: cidStr,
140
+ visited: [...visited],
141
+ })
142
+ }
143
+ visited.add(cidStr)
144
+
145
+ // Check stop condition BEFORE fetching content
146
+ if (excludeSnapshotCID && areCidsEqual(currentCID, excludeSnapshotCID)) {
147
+ DEBUG('[updateThreadFromSnapshot] reached excludeSnapshotCID, stopping', excludeSnapshotCID.toString())
148
+ break
149
+ }
150
+
151
+ // 1. Fetch root block
152
+ DEBUG('[updateThreadFromSnapshot] fetching root block', cidStr)
153
+ const rootBytes = await retriever.get(currentCID)
154
+ blockStore.put(currentCID, rootBytes)
155
+
156
+ // Parse root to get applogs, info, prev CIDs
157
+ const root = dagJson.decode(rootBytes) as SnapRootBlock
158
+
159
+ // Track counter range and validate sequentiality
160
+ if (typeof root.prevCounter === 'number') {
161
+ minCounter = Math.min(minCounter, root.prevCounter)
162
+ maxCounter = Math.max(maxCounter, root.prevCounter)
163
+
164
+ // Validate sequentiality (walking backwards, counter should decrease)
165
+ if (lastCounter !== undefined && root.prevCounter !== lastCounter - 1) {
166
+ WARN('[updateThreadFromSnapshot] counter gap detected', {
167
+ expected: lastCounter - 1,
168
+ got: root.prevCounter,
169
+ })
170
+ }
171
+ lastCounter = root.prevCounter
172
+ }
173
+
174
+ // Stop condition based on counter
175
+ if (stopAtCounter !== undefined && typeof root.prevCounter === 'number' && root.prevCounter <= stopAtCounter) {
176
+ DEBUG('[updateThreadFromSnapshot] reached stopAtCounter', { stopAtCounter, prevCounter: root.prevCounter })
177
+ break
178
+ }
179
+
180
+ // 2. Fetch applogs DAG
181
+ DEBUG('[updateThreadFromSnapshot] fetching applogs', root.applogs.toString())
182
+ for await (const { cid: blockCid, bytes } of retriever.getDag(root.applogs)) {
183
+ blockStore.put(blockCid, bytes)
184
+ }
185
+
186
+ // 3. Fetch info DAG
187
+ DEBUG('[updateThreadFromSnapshot] fetching info', root.info.toString())
188
+ for await (const { cid: blockCid, bytes } of retriever.getDag(root.info)) {
189
+ blockStore.put(blockCid, bytes)
190
+ }
191
+
192
+ // Decode applogs from this snapshot
193
+ const applogsBlock = await getDecodedBlock<SnapBlockLogsOrChunks>(blockStore, root.applogs)
194
+ if (!applogsBlock) {
195
+ throw ERROR('[updateThreadFromSnapshot] applogs block not found', { cid: root.applogs.toString() })
196
+ }
197
+
198
+ // Use the unchunk helper which handles both chunked and non-chunked formats
199
+ const applogCIDs = await unchunkApplogsBlock(applogsBlock, {
200
+ get: async (cid: CID) => blockStore.get(cid)!,
201
+ })
202
+
203
+ // Resolve each applog CID to actual applog data
204
+ for (const applogCID of applogCIDs) {
205
+ const applog = await getDecodedBlock<Applog>(blockStore, applogCID)
206
+ if (!applog) {
207
+ WARN('[updateThreadFromSnapshot] applog not found:', applogCID.toString())
208
+ continue
209
+ }
210
+ // Normalize pv field if it's a CID instance
211
+ if ((applog.pv as any) instanceof CID) {
212
+ applog.pv = (applog.pv as any as CID).toV1().toString()
213
+ }
214
+ allApplogs.push({
215
+ ...applog,
216
+ cid: applogCID.toV1().toString(),
217
+ })
218
+ }
219
+
220
+ snapshotCount++
221
+ currentCID = root.prev // Move to previous snapshot
222
+ }
223
+
224
+ DEBUG('[updateThreadFromSnapshot] fetched', {
225
+ snapshotCount,
226
+ applogCount: allApplogs.length,
227
+ rootCID: cid.toString(),
228
+ })
229
+
230
+ // Deduplicate applogs (in case of overlapping snapshots)
231
+ const deduplicated = removeDuplicateAppLogs(allApplogs, 'cleanup')
232
+
233
+ // Insert into thread
234
+ const inserted = thread.insertMissing(deduplicated, false)
235
+
236
+ DEBUG('[updateThreadFromSnapshot] inserted', {
237
+ insertedCount: inserted.length,
238
+ duplicateCount: deduplicated.length - inserted.length,
239
+ })
240
+
241
+ return {
242
+ cid,
243
+ applogs: deduplicated,
244
+ insertedCount: inserted.length,
245
+ snapshotCount,
246
+ counterRange: minCounter !== Infinity ? { minCounter, maxCounter } : undefined,
247
+ }
248
+ }
@@ -0,0 +1 @@
1
+ export * from './retrieve/update-thread.ts'
@@ -0,0 +1,94 @@
1
+ /**
2
+ * 1M applog stress test — static query + live query incremental update.
3
+ */
4
+ import { describe, it, expect } from 'vitest'
5
+ import type { Applog, ApplogForInsert } from '../../applog/datom-types.ts'
6
+ import { ThreadInMemory } from '../../thread/writeable.ts'
7
+ import { liveQuery, query } from '../../query/basic.ts'
8
+
9
+ const AGENT = 'stress-agent'
10
+
11
+ function generateLarge(entityCount: number): Applog[] {
12
+ const inputs: ApplogForInsert[] = []
13
+ const types = ['block', 'page', 'image', 'link', 'heading']
14
+ let ts = Date.now() - entityCount * 6
15
+
16
+ for (let i = 0; i < entityCount; i++) {
17
+ const en = `e${i}`
18
+ const nextTs = () => new Date(ts++).toISOString()
19
+ inputs.push({ en, at: 'entity/name', vl: `Entity ${i}`, ag: AGENT, ts: nextTs() } as any)
20
+ inputs.push({ en, at: 'entity/type', vl: types[i % types.length], ag: AGENT, ts: nextTs() } as any)
21
+ inputs.push({ en, at: 'entity/status', vl: i % 3 === 0 ? 'active' : 'draft', ag: AGENT, ts: nextTs() } as any)
22
+ if (i % 2 === 0) {
23
+ inputs.push({ en, at: 'entity/content', vl: `Content ${i}`, ag: AGENT, ts: nextTs() } as any)
24
+ }
25
+ if (i % 4 === 0) {
26
+ inputs.push({ en, at: 'relation/parent', vl: `e${Math.floor(i / 4)}`, ag: AGENT, ts: nextTs() } as any)
27
+ }
28
+ }
29
+
30
+ // Already sorted by construction (ts is monotonically increasing)
31
+ return inputs.map(i => ({ pv: null, ...i })) as Applog[]
32
+ }
33
+
34
+ describe('1M applog stress test', () => {
35
+ let db: ReturnType<typeof ThreadInMemory.fromArray>
36
+
37
+ it('generate ~1M applogs + load', () => {
38
+ const start = performance.now()
39
+ const dataset = generateLarge(200_000)
40
+ const genTime = performance.now() - start
41
+
42
+ const start2 = performance.now()
43
+ db = ThreadInMemory.fromArray(dataset, 'stress-1m')
44
+ const loadTime = performance.now() - start2
45
+
46
+ console.log(`\n [1M] Generated ${dataset.length.toLocaleString()} applogs in ${genTime.toFixed(0)}ms`)
47
+ console.log(` [1M] ThreadInMemory.fromArray: ${loadTime.toFixed(0)}ms`)
48
+ console.log(` [1M] Total thread size: ${db.size.toLocaleString()}`)
49
+ expect(db.size).toBeGreaterThan(700_000)
50
+ })
51
+
52
+ it('static query() — single step on ~750K applogs', () => {
53
+ const start = performance.now()
54
+ const result = query(db, [{ at: 'entity/type', vl: 'block' }])
55
+ const elapsed = performance.now() - start
56
+
57
+ console.log(` [1M] query() single-step: ${elapsed.toFixed(1)}ms → ${result.nodes.length.toLocaleString()} results`)
58
+ expect(result.nodes.length).toBe(40_000) // 200K / 5 types
59
+ })
60
+
61
+ it('liveQuery() — setup + single insert propagation on ~750K applogs', () => {
62
+ // Setup
63
+ const setupStart = performance.now()
64
+ const live = liveQuery(db, [{ at: 'entity/type', vl: 'block' }])
65
+ const setupTime = performance.now() - setupStart
66
+ const initialCount = live.nodes.length
67
+
68
+ console.log(` [1M] liveQuery() setup: ${setupTime.toFixed(1)}ms → ${initialCount.toLocaleString()} initial results`)
69
+
70
+ // Insert one matching applog and measure propagation
71
+ let eventTime: number | null = null
72
+ live.subscribe(() => {
73
+ eventTime = performance.now()
74
+ })
75
+
76
+ const insertStart = performance.now()
77
+ db.insert([{
78
+ en: 'new-entity-1m',
79
+ at: 'entity/type',
80
+ vl: 'block',
81
+ ag: AGENT,
82
+ }])
83
+ const totalTime = performance.now() - insertStart
84
+ const subscribeDelta = eventTime ? eventTime - insertStart : null
85
+
86
+ const newCount = live.nodes.length
87
+ console.log(` [1M] Insert→result updated: ${totalTime.toFixed(3)}ms`)
88
+ console.log(` [1M] Insert→subscribe fired: ${subscribeDelta?.toFixed(3) ?? 'N/A'}ms`)
89
+ console.log(` [1M] Nodes: ${initialCount.toLocaleString()} → ${newCount.toLocaleString()}`)
90
+
91
+ expect(newCount).toBe(initialCount + 1)
92
+ live.dispose()
93
+ })
94
+ }, { timeout: 120_000 })