@wovin/core 0.1.36 → 0.2.2

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 (213) 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 +40 -6
  5. package/dist/applog/applog-utils.d.ts.map +1 -1
  6. package/dist/applog/datom-types.d.ts +67 -12
  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} +12 -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-22WDFLXO.js +138 -0
  16. package/dist/chunk-22WDFLXO.js.map +1 -0
  17. package/dist/chunk-3SUFNJEZ.js +1026 -0
  18. package/dist/chunk-3SUFNJEZ.js.map +1 -0
  19. package/dist/chunk-6ALNRM3J.js +435 -0
  20. package/dist/chunk-6ALNRM3J.js.map +1 -0
  21. package/dist/chunk-7Z5YDQKK.js +1 -0
  22. package/dist/{chunk-KXMTKPF4.min.js → chunk-BLF5MAWU.js} +8 -8
  23. package/dist/chunk-BLF5MAWU.js.map +1 -0
  24. package/dist/chunk-E46VTKTZ.js +1 -0
  25. package/dist/{chunk-H3VQJP56.min.js → chunk-HUIQ54TT.js} +9 -9
  26. package/dist/chunk-HUIQ54TT.js.map +1 -0
  27. package/dist/{chunk-BRC7LSM6.min.js → chunk-OC6Z6CQW.js} +5 -5
  28. package/dist/chunk-OC6Z6CQW.js.map +1 -0
  29. package/dist/chunk-SHUHRHOT.js +1923 -0
  30. package/dist/chunk-SHUHRHOT.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} +81 -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 +87 -23
  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 +12 -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} +55 -34
  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 +57 -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 +551 -0
  117. package/src/applog/datom-types.ts +167 -0
  118. package/src/applog/object-values.test.ts +106 -0
  119. package/src/applog.ts +3 -0
  120. package/src/blockstore/index.ts +36 -0
  121. package/src/blockstore.ts +1 -0
  122. package/src/index.ts +8 -0
  123. package/src/ipfs/car.ts +291 -0
  124. package/src/ipfs/fetch-snapshot-chain.ts +135 -0
  125. package/src/ipfs/ipfs-utils.ts +132 -0
  126. package/src/ipfs.ts +3 -0
  127. package/src/ipns/ipns-record.ts +115 -0
  128. package/src/ipns.ts +1 -0
  129. package/src/pubsub/UCAN Specs Overview.md +217 -0
  130. package/src/pubsub/connector.ts +9 -0
  131. package/src/pubsub/pub-pull.ts +31 -0
  132. package/src/pubsub/pubsub-types.ts +90 -0
  133. package/src/pubsub/snap-push.ts +278 -0
  134. package/src/pubsub/ucan-example.ts +61 -0
  135. package/src/pubsub/ucan.ts +56 -0
  136. package/src/pubsub.ts +4 -0
  137. package/src/query/attr-helpers.ts +5 -0
  138. package/src/query/basic.ts +1245 -0
  139. package/src/query/divergences.ts +50 -0
  140. package/src/query/entity-collection.ts +132 -0
  141. package/src/query/liveFilterAndMap.test.ts +102 -0
  142. package/src/query/matchers.ts +30 -0
  143. package/src/query/memoized.test.ts +151 -0
  144. package/src/query/memoized.ts +180 -0
  145. package/src/query/query-steps.ts +4 -0
  146. package/src/query/query.test.ts +538 -0
  147. package/src/query/situations.ts +261 -0
  148. package/src/query/subscribable.test.ts +245 -0
  149. package/src/query/subscribable.ts +234 -0
  150. package/src/query/types.ts +155 -0
  151. package/src/query/withoutDeleted.test.ts +204 -0
  152. package/src/query.ts +9 -0
  153. package/src/retrieve/index.ts +1 -0
  154. package/src/retrieve/update-thread.ts +248 -0
  155. package/src/retrieve.ts +1 -0
  156. package/src/test/perf/query.1m.perf.test.ts +94 -0
  157. package/src/test/perf/query.perf.test.ts +389 -0
  158. package/src/test/perf/query.realdata.perf.test.ts +182 -0
  159. package/src/thread/basic.ts +209 -0
  160. package/src/thread/filters.ts +227 -0
  161. package/src/thread/indexes.ts +256 -0
  162. package/src/thread/joinThreads.test.ts +304 -0
  163. package/src/thread/mapped.ts +226 -0
  164. package/src/thread/utils.ts +144 -0
  165. package/src/thread/writeable.ts +163 -0
  166. package/src/thread.ts +6 -0
  167. package/src/types/typescript-utils.ts +64 -0
  168. package/src/types.ts +1 -0
  169. package/src/utils/debug-name.ts +54 -0
  170. package/src/utils.ts +4 -0
  171. package/dist/chunk-2Y2PYHGR.min.js +0 -65
  172. package/dist/chunk-2Y2PYHGR.min.js.map +0 -1
  173. package/dist/chunk-5MMGBK2U.min.js +0 -1
  174. package/dist/chunk-7IDQIMQO.min.js +0 -1
  175. package/dist/chunk-BRC7LSM6.min.js.map +0 -1
  176. package/dist/chunk-COXXILXC.min.js +0 -512
  177. package/dist/chunk-COXXILXC.min.js.map +0 -1
  178. package/dist/chunk-GDX2OO7L.min.js +0 -9080
  179. package/dist/chunk-GDX2OO7L.min.js.map +0 -1
  180. package/dist/chunk-H3VQJP56.min.js.map +0 -1
  181. package/dist/chunk-HYMC7W6S.min.js +0 -1549
  182. package/dist/chunk-HYMC7W6S.min.js.map +0 -1
  183. package/dist/chunk-KEHU7HGZ.min.js +0 -5216
  184. package/dist/chunk-KEHU7HGZ.min.js.map +0 -1
  185. package/dist/chunk-KXMTKPF4.min.js.map +0 -1
  186. package/dist/chunk-PHITDXZT.min.js +0 -36
  187. package/dist/chunk-QO2KMGDN.min.js +0 -3771
  188. package/dist/chunk-QO2KMGDN.min.js.map +0 -1
  189. package/dist/chunk-QPGEBDMJ.min.js.map +0 -1
  190. package/dist/chunk-WXLCBTHX.min.js +0 -1606
  191. package/dist/chunk-WXLCBTHX.min.js.map +0 -1
  192. package/dist/ipns.min.js +0 -6419
  193. package/dist/ipns.min.js.map +0 -1
  194. package/dist/mobx/mobx-utils.d.ts +0 -82
  195. package/dist/mobx/mobx-utils.d.ts.map +0 -1
  196. package/dist/mobx.d.ts +0 -2
  197. package/dist/mobx.d.ts.map +0 -1
  198. package/dist/mobx.min.js +0 -141
  199. package/dist/retrieve.min.js +0 -17
  200. package/dist/types.min.js.map +0 -1
  201. package/dist/utils.min.js +0 -10
  202. package/dist/utils.min.js.map +0 -1
  203. /package/dist/{applog.min.js.map → applog.js.map} +0 -0
  204. /package/dist/{chunk-5MMGBK2U.min.js.map → chunk-7Z5YDQKK.js.map} +0 -0
  205. /package/dist/{chunk-7IDQIMQO.min.js.map → chunk-E46VTKTZ.js.map} +0 -0
  206. /package/dist/{chunk-PHITDXZT.min.js.map → index.js.map} +0 -0
  207. /package/dist/{index.min.js.map → ipfs.js.map} +0 -0
  208. /package/dist/{ipfs.min.js.map → pubsub.js.map} +0 -0
  209. /package/dist/{mobx.min.js.map → query.js.map} +0 -0
  210. /package/dist/{pubsub.min.js.map → retrieve.js.map} +0 -0
  211. /package/dist/{query.min.js.map → thread.js.map} +0 -0
  212. /package/dist/{retrieve.min.js.map → types.js.map} +0 -0
  213. /package/dist/{thread.min.js.map → utils.js.map} +0 -0
@@ -0,0 +1,551 @@
1
+ import { Logger } from 'besonders-logger'
2
+ import { isBefore } from 'date-fns'
3
+ import { partial, pick } from 'lodash-es'
4
+ import { isEqual } from 'lodash-es'
5
+ import stringify from 'safe-stable-stringify'
6
+ import type {
7
+ Applog,
8
+ ApplogForInsert,
9
+ ApplogValue,
10
+ DatalogQueryPattern,
11
+ DatalogQueryResultEntry,
12
+ ResultContext,
13
+ SearchContext,
14
+ ValueOrMatcher,
15
+ } from './datom-types.ts'
16
+
17
+ const { WARN, LOG, DEBUG, VERBOSE, ERROR } = Logger.setup(Logger.INFO) // eslint-disable-line no-unused-vars
18
+
19
+ export const isoDateStrCompare = (strA: string, strB: string, dir: 'asc' | 'desc' = 'asc') =>
20
+ dir === 'asc'
21
+ ? strA.localeCompare(strB, 'en-US')
22
+ : strB.localeCompare(strA, 'en-US')
23
+ export const objEqualByKeys = (keys: string[], objA: object, objB: object) => {
24
+ return isEqual(pick(objA, keys), pick(objB, keys))
25
+ }
26
+
27
+ /**
28
+ * Deep equality for an `ApplogValue`. `vl` can now hold JSON objects/arrays, so a bare
29
+ * `===` would treat structurally-identical objects as different. `isEqual` short-circuits
30
+ * on referential identity internally, so primitives stay on the cheap path.
31
+ */
32
+ export const valueEq = (a: ApplogValue, b: ApplogValue): boolean => isEqual(a, b)
33
+
34
+ /** Canonical, hashable Map key for an `ApplogValue` (see {@link valueKey}). */
35
+ export type ValueKey = string | number | boolean | null
36
+
37
+ /**
38
+ * Canonical key for using an `ApplogValue` as a Map key. Primitives pass through unchanged
39
+ * (so primitive-keyed lookups are identity-stable); objects/arrays are stably stringified
40
+ * (key-sorted) behind a `\0obj:` sentinel so an object value can't collide with a string
41
+ * value that happens to equal its serialization.
42
+ */
43
+ export const valueKey = (vl: ApplogValue): ValueKey => {
44
+ switch (typeof vl) {
45
+ case 'string':
46
+ case 'number':
47
+ case 'boolean':
48
+ return vl
49
+ default: // null | object | array
50
+ return vl === null ? null : '\0obj:' + stringify(vl)
51
+ }
52
+ }
53
+
54
+ /** Transitive total-order comparator over (ts, cid). Safe for `Array.sort`. */
55
+ export const compareApplogsByTs = (logA: Applog, logB: Applog, dir: 'asc' | 'desc' = 'asc') => {
56
+ const tsCmp = isoDateStrCompare(logA.ts, logB.ts, dir)
57
+ if (tsCmp !== 0) return tsCmp
58
+ return dir === 'asc' ? logA.cid.localeCompare(logB.cid) : logB.cid.localeCompare(logA.cid)
59
+ }
60
+ export const compareApplogsByEnAt = partial(objEqualByKeys, ['en', 'at'])
61
+
62
+ /**
63
+ * Pairwise: is `a` strictly later than `b` per (ts, pv-chain, cid)?
64
+ *
65
+ * NOT TRANSITIVE. Use only for two-log decisions (e.g. lastWriteWins replacement
66
+ * check). Do NOT pass to `Array.sort` — it can produce cycles at 3+ chain links
67
+ * and break the engine's sort.
68
+ */
69
+ export function isLaterByTsAndPv(a: Applog, b: Applog): boolean {
70
+ const tsCmp = isoDateStrCompare(a.ts, b.ts)
71
+ if (tsCmp !== 0) return tsCmp > 0
72
+ if (a.en === b.en && a.at === b.at) {
73
+ if (a.pv === b.cid) return true
74
+ if (b.pv === a.cid) return false
75
+ }
76
+ return a.cid.localeCompare(b.cid) > 0
77
+ }
78
+
79
+ /**
80
+ * Sort applogs in chain-aware order (modifies array, also returns for chaining).
81
+ *
82
+ * Two-phase:
83
+ * 1. Transitive `(ts, cid)` `Array.sort` — fast, total-order, common case.
84
+ * 2. For each contiguous same-ts cluster (rare), chain-stabilize via `pv`
85
+ * so chain-tail logs land last (asc) / first (desc) within their (en, at)
86
+ * sub-group. Cross-group order within a cluster stays cid-lex.
87
+ *
88
+ * No-tie inputs pay only one linear scan past `Array.sort`.
89
+ */
90
+ export function sortApplogsByTs(appLogArray: Applog[], dir: 'asc' | 'desc' = 'asc') {
91
+ appLogArray.sort((a, b) => compareApplogsByTs(a, b, dir))
92
+ let i = 0
93
+ while (i < appLogArray.length) {
94
+ let j = i + 1
95
+ while (j < appLogArray.length && appLogArray[j].ts === appLogArray[i].ts) j++
96
+ if (j - i > 1) chainStabilizeCluster(appLogArray, i, j, dir)
97
+ i = j
98
+ }
99
+ return appLogArray
100
+ }
101
+
102
+ /**
103
+ * Re-order applogs[from..to-1] (all sharing `ts`) so each (en, at) sub-group
104
+ * appears in pv-chain order. Cross-group positions in the cluster are preserved
105
+ * — only logs within the same (en, at) sub-group get reshuffled.
106
+ */
107
+ function chainStabilizeCluster(applogs: Applog[], from: number, to: number, dir: 'asc' | 'desc') {
108
+ const groups = new Map<string, Applog[]>()
109
+ for (let k = from; k < to; k++) {
110
+ const log = applogs[k]
111
+ const key = log.en + '|' + log.at
112
+ const existing = groups.get(key)
113
+ if (existing) existing.push(log)
114
+ else groups.set(key, [log])
115
+ }
116
+ // All groups singleton — nothing to do (the common case)
117
+ if (groups.size === to - from) return
118
+
119
+ const orderedByGroup = new Map<string, Applog[]>()
120
+ for (const [key, logs] of groups) {
121
+ orderedByGroup.set(key, logs.length === 1 ? logs : topoSortByPv(logs, dir))
122
+ }
123
+ const cursors = new Map<string, number>()
124
+ for (let k = from; k < to; k++) {
125
+ const key = applogs[k].en + '|' + applogs[k].at
126
+ const cursor = cursors.get(key) ?? 0
127
+ applogs[k] = orderedByGroup.get(key)![cursor]
128
+ cursors.set(key, cursor + 1)
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Topologically order `logs` (all sharing en, at, ts) by pv-chain.
134
+ * asc → root-first; desc → tail-first. Forks / multi-roots: tie-break by cid.
135
+ */
136
+ function topoSortByPv(logs: Applog[], dir: 'asc' | 'desc'): Applog[] {
137
+ const cidSet = new Set(logs.map(l => l.cid))
138
+ const childrenByPv = new Map<string, Applog[]>()
139
+ const roots: Applog[] = []
140
+ for (const log of logs) {
141
+ if (log.pv && cidSet.has(log.pv)) {
142
+ const children = childrenByPv.get(log.pv)
143
+ if (children) children.push(log)
144
+ else childrenByPv.set(log.pv, [log])
145
+ } else {
146
+ roots.push(log)
147
+ }
148
+ }
149
+ const lexCmp = (a: Applog, b: Applog) => a.cid.localeCompare(b.cid)
150
+ roots.sort(lexCmp)
151
+ for (const children of childrenByPv.values()) children.sort(lexCmp)
152
+
153
+ const result: Applog[] = []
154
+ const visited = new Set<string>()
155
+ const stack = [...roots].reverse()
156
+ while (stack.length > 0) {
157
+ const log = stack.pop()!
158
+ if (visited.has(log.cid)) continue
159
+ visited.add(log.cid)
160
+ result.push(log)
161
+ const children = childrenByPv.get(log.cid)
162
+ if (children) for (let i = children.length - 1; i >= 0; i--) stack.push(children[i])
163
+ }
164
+ // Defensive: cycle (shouldn't happen with content-addressed pv) — append unvisited
165
+ if (result.length < logs.length) {
166
+ for (const log of logs) if (!visited.has(log.cid)) result.push(log)
167
+ }
168
+ return dir === 'desc' ? result.reverse() : result
169
+ }
170
+ export const isTsBefore = (log: Applog, logToCompare: Applog) => isBefore(new Date(log.ts), new Date(logToCompare.ts))
171
+ export const uniqueEnFromAppLogs = (appLogArray: Applog[]) => [...new Set(appLogArray.map(eachLog => eachLog.en))]
172
+ export const areApplogsEqual = (logA: Applog, logB: Applog) => isEqual(logA, logB)
173
+
174
+ export type RemoveDuplicateAppLogsMode = 'safety' | 'cleanup'
175
+
176
+ const warnMissingRemoveDuplicateMode = () => {
177
+ WARN(`[removeDuplicateAppLogs] mode not set; pass 'safety' or 'cleanup' for optimal behavior`)
178
+ }
179
+
180
+ const removeDuplicateAppLogsCleanup = (appLogArray: Applog[]) => {
181
+ const logMap = new Map<string, Applog>()
182
+ const verboseEnabled = VERBOSE.isEnabled
183
+ for (const eachLog of appLogArray) {
184
+ if (!eachLog) {
185
+ ERROR(`falsy entry in applogs`, appLogArray)
186
+ throw new Error(`falsy entry in applogs`)
187
+ }
188
+ if (!eachLog.cid) {
189
+ ERROR(`applog with missing CID`, eachLog)
190
+ throw new Error(`applog with missing CID`)
191
+ }
192
+ const key = eachLog.cid
193
+ const existing = logMap.get(key)
194
+ if (existing) {
195
+ if (verboseEnabled) VERBOSE(`Skipping duplicate applog:`, [existing, eachLog])
196
+ } else {
197
+ logMap.set(key, eachLog)
198
+ }
199
+ }
200
+ return Array.from(logMap.values())
201
+ }
202
+
203
+ const removeDuplicateAppLogsSafety = (appLogArray: Applog[]) => {
204
+ const seen = new Set<string>()
205
+ const verboseEnabled = VERBOSE.isEnabled
206
+ const existingByCid = verboseEnabled ? new Map<string, Applog>() : null
207
+ let result: Applog[] | null = null
208
+ let index = 0
209
+ for (const eachLog of appLogArray) {
210
+ if (!eachLog) {
211
+ ERROR(`falsy entry in applogs`, appLogArray)
212
+ throw new Error(`falsy entry in applogs`)
213
+ }
214
+ if (!eachLog.cid) {
215
+ ERROR(`applog with missing CID`, eachLog)
216
+ throw new Error(`applog with missing CID`)
217
+ }
218
+ const key = eachLog.cid
219
+ if (seen.has(key)) {
220
+ if (!result) {
221
+ result = appLogArray.slice(0, index)
222
+ }
223
+ if (verboseEnabled) VERBOSE(`Skipping duplicate applog:`, [existingByCid?.get(key), eachLog])
224
+ } else {
225
+ seen.add(key)
226
+ if (existingByCid) existingByCid.set(key, eachLog)
227
+ if (result) result.push(eachLog)
228
+ }
229
+ index++
230
+ }
231
+ return result ?? appLogArray
232
+ }
233
+
234
+ /**
235
+ * Deduplicate applogs by CID.
236
+ * - safety: fast duplicate check; returns original array if no duplicates found.
237
+ * - cleanup: optimized for merged arrays with likely duplicates.
238
+ */
239
+ export const removeDuplicateAppLogs = (appLogArray: Applog[], mode?: RemoveDuplicateAppLogsMode) => {
240
+ if (!mode) {
241
+ warnMissingRemoveDuplicateMode()
242
+ return removeDuplicateAppLogsCleanup(appLogArray)
243
+ }
244
+ return mode === 'safety'
245
+ ? removeDuplicateAppLogsSafety(appLogArray)
246
+ : removeDuplicateAppLogsCleanup(appLogArray)
247
+ }
248
+
249
+ // export const removeDuplicateAndMaybeDeletedAppLogs = (ds: Thread, appLogArray: Applog[], removeDeletedEntities = true) => {
250
+ // const logMap = new Map()
251
+ // for (const eachLog of appLogArray) {
252
+ // if (!removeDeletedEntities || ds.entityIsDeleted(eachLog.en))
253
+ // logMap.set(stringify(eachLog), eachLog)
254
+ // }
255
+ // return Array.from(logMap.values())
256
+ // }
257
+
258
+ export const getHashID = (stringifiable: any, lngth = 8) => cyrb53hash(stringify(stringifiable), 31, lngth) as string
259
+
260
+ export function isVariable(x: any): x is string {
261
+ return typeof x === 'string' && x.startsWith('?')
262
+ }
263
+ export function variableNameWithoutQuestionmark(str: string) {
264
+ return str.slice(1)
265
+ }
266
+ // export function isMatcher(x: any): x is string {
267
+ // return
268
+ // }
269
+ export function isStaticPattern(x: any): x is ApplogValue {
270
+ if (!['string', 'boolean', 'number', 'function'].includes(typeof x)) WARN(`Unhandled pattern value type:`, typeof x, x)
271
+ return !isVariable(x) && ['string', 'boolean', 'number'].includes(typeof x)
272
+ }
273
+ // export function isIgnorePattern(x: any): boolean {
274
+ // return x === '_'
275
+ // }
276
+
277
+ /*
278
+ * In a pattern from a Query:
279
+ * - variables that don't have a value in the search context:
280
+ * - remove from pattern
281
+ * - add to variableToFill as: { en: 'movieID' } (useful for mapTo)
282
+ * - variables that have a value set:
283
+ * - replace placeholder with actual value
284
+ */
285
+ export function resolveOrRemoveVariables(pattern: DatalogQueryPattern, candidate: SearchContext) {
286
+ let variablesToFill = {} as Partial<{ [key in keyof Applog]: string }>
287
+ const newPattern: DatalogQueryPattern = {}
288
+ for (const [patternKey, patternValue] of Object.entries(pattern)) {
289
+ if (isVariable(patternValue)) {
290
+ const varName = variableNameWithoutQuestionmark(patternValue)
291
+ const candidateValue = candidate[varName]
292
+ if (candidateValue) {
293
+ newPattern[patternKey] = candidateValue
294
+ // & not adding it to newPattern
295
+ } else {
296
+ variablesToFill[patternKey] = varName
297
+ }
298
+ } else {
299
+ newPattern[patternKey] = patternValue // keep static value
300
+ }
301
+ }
302
+
303
+ return [newPattern, variablesToFill] as const
304
+ }
305
+
306
+ function matchVariable(variable: string, triplePart: ApplogValue, context: SearchContext): SearchContext {
307
+ if (context.hasOwnProperty(variable)) {
308
+ // TODO: fix lint error with: if (Object.hasOwnProperty.call(context, variable)) {
309
+ const bound = context[variable]
310
+ const match = matchPart(bound, triplePart, context)
311
+ // if (VERBOSE.isEnabled) VERBOSE('[matchVariable] match?', variable, bound, match)
312
+ return match
313
+ }
314
+ // if (VERBOSE.isEnabled) VERBOSE('[matchVariable] initializing variable', variable, 'to', triplePart)
315
+ return { ...context, [variable]: triplePart }
316
+ }
317
+
318
+ export function matchPartStatic(field: keyof Applog, patternPart: ValueOrMatcher<ApplogValue>, atomPart: ApplogValue): boolean {
319
+ // if (VERBOSE.isEnabled) VERBOSE('[matchPartStatic]', field, patternPart, patternPart === atomPart ? '===' : '!==', atomPart)
320
+ let result
321
+ if (patternPart) {
322
+ const typ = typeof patternPart
323
+ if (typ === 'string') {
324
+ result = patternPart === atomPart // shortcut for most common use-case
325
+ } else if (typ === 'function') {
326
+ result = (patternPart as Function)(atomPart)
327
+ } else if (Array.isArray(patternPart)) {
328
+ // A bare array is ambiguous now that `vl` may itself be an array value: it could
329
+ // mean set-membership or a literal-array match. Fail loudly rather than match wrong.
330
+ throw ERROR(
331
+ `[matchPartStatic] a bare array is not a valid matcher for field '${field}'.`
332
+ + ` Use anyOf(...) for set-membership, or a predicate (v) => isEqual(v, [...]) to match a literal array value.`,
333
+ patternPart,
334
+ )
335
+ } else if (typeof (patternPart as any).has === 'function') {
336
+ result = (patternPart as Set<any>).has(atomPart)
337
+ } // if (field === 'at' && typ === 'string' && patternPart.endsWith('*')) {
338
+ // return typeof atomPart === 'string' && atomPart.startsWith(patternPart.slice(0, -1))
339
+ // }
340
+ else {
341
+ // object/array values compare by deep equality; primitives via isEqual's === fast-path
342
+ result = valueEq(patternPart as ApplogValue, atomPart)
343
+ }
344
+ } else {
345
+ result = patternPart === atomPart
346
+ }
347
+
348
+ // if (VERBOSE.isEnabled) VERBOSE('[matchPartStatic] =>', field.startsWith('!') ? '!' : '', result)
349
+ if (field.charAt(0) === '!') {
350
+ return !result
351
+ } else {
352
+ return result
353
+ }
354
+ }
355
+ export function matchPart(patternPart: ValueOrMatcher<ApplogValue>, atomPart: ApplogValue, context: SearchContext): ResultContext {
356
+ if (!context) {
357
+ // if (VERBOSE.isEnabled) VERBOSE('[matchPart] no context')
358
+ return null
359
+ }
360
+ if (typeof patternPart === 'string') {
361
+ if (isVariable(patternPart)) {
362
+ return matchVariable(patternPart, atomPart, context)
363
+ } /* TODO: else if (isIgnorePattern(patternPart)) {
364
+ return matchVariable(patternPart, atomPart, context)
365
+ } */
366
+ }
367
+ // if (VERBOSE.isEnabled) VERBOSE('[matchPart]', patternPart, patternPart === atomPart ? '===' : '!==', atomPart)
368
+ if (typeof patternPart === 'function') {
369
+ return patternPart(atomPart) ? context : null
370
+ }
371
+ if (Array.isArray(patternPart)) {
372
+ throw ERROR(
373
+ `[matchPart] a bare array is not a valid matcher.`
374
+ + ` Use anyOf(...) for set-membership, or a predicate to match a literal array value.`,
375
+ patternPart,
376
+ )
377
+ }
378
+ if (patternPart && typeof (patternPart as any).has === 'function') {
379
+ return (patternPart as Set<any>).has(atomPart) ? context : null
380
+ }
381
+ return valueEq(patternPart as ApplogValue, atomPart) ? context : null
382
+ }
383
+
384
+ /**
385
+ * Check if pattern matches triple with context substitutions
386
+ */
387
+ export function matchPattern(pattern: DatalogQueryPattern, applog: Applog, context: SearchContext): ResultContext {
388
+ return Object.entries(pattern).reduce((context, [field, patternValue]) => {
389
+ const applogValue = applog[field]
390
+ // @ts-expect-error wtf no idea //HACK: ts weird
391
+ const patternValT: ValueOrMatcher<ApplogValue> = patternValue
392
+ return matchPart(patternValT, applogValue, context)
393
+ }, context)
394
+ }
395
+
396
+ export function actualize<SELECT extends string>(context: ResultContext, find: readonly SELECT[]): DatalogQueryResultEntry<SELECT> {
397
+ return Object.fromEntries(find.map((findField) => {
398
+ if (context === null) {
399
+ throw new Error(`actualize context is null ${find}`)
400
+ }
401
+ return [
402
+ isVariable(findField) ? findField.replace(/^\?/, '') : findField,
403
+ isVariable(findField) ? context[findField] : findField,
404
+ ]
405
+ })) as DatalogQueryResultEntry<SELECT>
406
+ }
407
+ const sum = function sum(array: number[]) {
408
+ var num = 0
409
+ for (var i = 0, l = array.length; i < l; i++) num += array[i]
410
+ return num
411
+ }
412
+ const mean = function mean(array: number[]) {
413
+ return sum(array) / array.length
414
+ }
415
+ export const arrStats = {
416
+ max: function(array: number[]) {
417
+ return Math.max.apply(null, array)
418
+ },
419
+
420
+ min: function(array: number[]) {
421
+ return Math.min.apply(null, array)
422
+ },
423
+
424
+ range: function(array: number[]) {
425
+ return arrStats.max(array) - arrStats.min(array)
426
+ },
427
+
428
+ midrange: function(array: number[]) {
429
+ return arrStats.range(array) / 2
430
+ },
431
+
432
+ sum,
433
+
434
+ mean,
435
+
436
+ average: mean,
437
+
438
+ median: function(array: number[]) {
439
+ array.sort(function(a, b) {
440
+ return a - b
441
+ })
442
+ var mid = array.length / 2
443
+ return mid % 1 ? array[mid - 0.5] : (array[mid - 1] + array[mid]) / 2
444
+ },
445
+
446
+ modes: function(array: number[]) {
447
+ if (!array.length) return []
448
+ var modeMap = {},
449
+ maxCount = 0,
450
+ modes = []
451
+
452
+ array.forEach(function(val) {
453
+ if (!modeMap[val]) modeMap[val] = 1
454
+ else modeMap[val]++
455
+
456
+ if (modeMap[val] > maxCount) {
457
+ modes = [val]
458
+ maxCount = modeMap[val]
459
+ } else if (modeMap[val] === maxCount) {
460
+ modes.push(val)
461
+ maxCount = modeMap[val]
462
+ }
463
+ })
464
+ return modes
465
+ },
466
+
467
+ variance: function(array: number[]) {
468
+ var mean = arrStats.mean(array)
469
+ return arrStats.mean(array.map(function(num) {
470
+ return Math.pow(num - mean, 2)
471
+ }))
472
+ },
473
+
474
+ standardDeviation: function(array: number[]) {
475
+ return Math.sqrt(arrStats.variance(array))
476
+ },
477
+
478
+ meanAbsoluteDeviation: function(array: number[]) {
479
+ var mean = arrStats.mean(array)
480
+ return arrStats.mean(array.map(function(num) {
481
+ return Math.abs(num - mean)
482
+ }))
483
+ },
484
+
485
+ zScores: function(array: number[]) {
486
+ var mean = arrStats.mean(array)
487
+ var standardDeviation = arrStats.standardDeviation(array)
488
+ return array.map(function(num) {
489
+ return (num - mean) / standardDeviation
490
+ })
491
+ },
492
+ }
493
+
494
+ // Function aliases:
495
+ arrStats.average = arrStats.mean
496
+
497
+ export const tsNearlySame = (timeA: string, timeB: string) => timeB.startsWith(timeA.slice(0, timeA.length - 4)) // HACK: to quickly check if same second
498
+
499
+ /*
500
+ cyrb53 (c) 2018 bryc (github.com/bryc)
501
+ A fast and simple hash function with decent collision resistance.
502
+ Largely inspired by MurmurHash2/3, but with a focus on speed/simplicity.
503
+ Public domain. Attribution appreciated.
504
+
505
+ ripped from https://github.com/bryc/code/blob/mast`er/jshash/experimental/cyrb53.js
506
+ */
507
+ export const cyrb53hash = function(
508
+ str: string,
509
+ seed = 13,
510
+ strLength: number, /* = 0 */
511
+ ) {
512
+ if (!str?.length) {
513
+ throw new Error(`Empty string: ${str}`)
514
+ }
515
+
516
+ let h1 = 0xdeadbeef ^ seed
517
+ let h2 = 0x41c6ce57 ^ seed
518
+ for (let i = 0, ch; i < str.length; i++) {
519
+ ch = str.charCodeAt(i)
520
+ h1 = Math.imul(h1 ^ ch, 2654435761)
521
+ h2 = Math.imul(h2 ^ ch, 1597334677)
522
+ }
523
+ h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909)
524
+ h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909)
525
+ // if (strLength) {
526
+ const asHex = (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(16)
527
+ return asHex.slice(-strLength).padStart(strLength, '0')
528
+ // }
529
+ // // if not specified return as 16 digit integer
530
+ // return 4294967296 * (2097151 & h2) + (h1 >>> 0)
531
+ }
532
+ export function arraysContainSameElements(arr1, arr2) {
533
+ if (arr1.length !== arr2.length) {
534
+ return false
535
+ }
536
+
537
+ const sortedArr1 = [...arr1].sort()
538
+ const sortedArr2 = [...arr2].sort()
539
+
540
+ for (let i = 0; i < sortedArr1.length; i++) {
541
+ if (sortedArr1[i] !== sortedArr2[i]) {
542
+ return false
543
+ }
544
+ }
545
+
546
+ return true
547
+ }
548
+ export function dateNowIso(): string {
549
+ const now = new Date()
550
+ return now.toISOString()
551
+ }