@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.
- package/README.md +0 -12
- package/dist/applog/applog-helpers.d.ts +12 -12
- package/dist/applog/applog-helpers.d.ts.map +1 -1
- package/dist/applog/applog-utils.d.ts +40 -6
- package/dist/applog/applog-utils.d.ts.map +1 -1
- package/dist/applog/datom-types.d.ts +67 -12
- package/dist/applog/datom-types.d.ts.map +1 -1
- package/dist/applog.d.ts +3 -3
- package/dist/applog.d.ts.map +1 -1
- package/dist/{applog.min.js → applog.js} +12 -7
- package/dist/blockstore.d.ts +1 -1
- package/dist/blockstore.d.ts.map +1 -1
- package/dist/{blockstore.min.js → blockstore.js} +1 -3
- package/dist/{blockstore.min.js.map → blockstore.js.map} +1 -1
- package/dist/chunk-22WDFLXO.js +138 -0
- package/dist/chunk-22WDFLXO.js.map +1 -0
- package/dist/chunk-3SUFNJEZ.js +1026 -0
- package/dist/chunk-3SUFNJEZ.js.map +1 -0
- package/dist/chunk-6ALNRM3J.js +435 -0
- package/dist/chunk-6ALNRM3J.js.map +1 -0
- package/dist/chunk-7Z5YDQKK.js +1 -0
- package/dist/{chunk-KXMTKPF4.min.js → chunk-BLF5MAWU.js} +8 -8
- package/dist/chunk-BLF5MAWU.js.map +1 -0
- package/dist/chunk-E46VTKTZ.js +1 -0
- package/dist/{chunk-H3VQJP56.min.js → chunk-HUIQ54TT.js} +9 -9
- package/dist/chunk-HUIQ54TT.js.map +1 -0
- package/dist/{chunk-BRC7LSM6.min.js → chunk-OC6Z6CQW.js} +5 -5
- package/dist/chunk-OC6Z6CQW.js.map +1 -0
- package/dist/chunk-SHUHRHOT.js +1923 -0
- package/dist/chunk-SHUHRHOT.js.map +1 -0
- package/dist/{chunk-QPGEBDMJ.min.js → chunk-YDAKBU6Q.js} +1 -1
- package/dist/chunk-YDAKBU6Q.js.map +1 -0
- package/dist/chunk-ZAADLBSB.js +36 -0
- package/dist/chunk-ZAADLBSB.js.map +1 -0
- package/dist/index.d.ts +7 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/{index.min.js → index.js} +81 -46
- package/dist/ipfs/car.d.ts +11 -11
- package/dist/ipfs/car.d.ts.map +1 -1
- package/dist/ipfs/ipfs-utils.d.ts +2 -2
- package/dist/ipfs/ipfs-utils.d.ts.map +1 -1
- package/dist/ipfs.d.ts +3 -3
- package/dist/ipfs.d.ts.map +1 -1
- package/dist/{ipfs.min.js → ipfs.js} +7 -10
- package/dist/ipns.d.ts +1 -1
- package/dist/ipns.d.ts.map +1 -1
- package/dist/ipns.js +64 -0
- package/dist/ipns.js.map +1 -0
- package/dist/pubsub/pub-pull.d.ts +3 -3
- package/dist/pubsub/pub-pull.d.ts.map +1 -1
- package/dist/pubsub/pubsub-types.d.ts +3 -3
- package/dist/pubsub/pubsub-types.d.ts.map +1 -1
- package/dist/pubsub/snap-push.d.ts +4 -4
- package/dist/pubsub/snap-push.d.ts.map +1 -1
- package/dist/pubsub/ucan.d.ts +1 -1
- package/dist/pubsub/ucan.d.ts.map +1 -1
- package/dist/pubsub.d.ts +4 -4
- package/dist/pubsub.d.ts.map +1 -1
- package/dist/{pubsub.min.js → pubsub.js} +7 -10
- package/dist/query/attr-helpers.d.ts +5 -0
- package/dist/query/attr-helpers.d.ts.map +1 -0
- package/dist/query/basic.d.ts +87 -23
- package/dist/query/basic.d.ts.map +1 -1
- package/dist/query/divergences.d.ts +5 -5
- package/dist/query/divergences.d.ts.map +1 -1
- package/dist/query/entity-collection.d.ts +19 -0
- package/dist/query/entity-collection.d.ts.map +1 -0
- package/dist/query/matchers.d.ts +12 -1
- package/dist/query/matchers.d.ts.map +1 -1
- package/dist/query/memoized.d.ts +66 -0
- package/dist/query/memoized.d.ts.map +1 -0
- package/dist/query/situations.d.ts +2 -1
- package/dist/query/situations.d.ts.map +1 -1
- package/dist/query/subscribable.d.ts +111 -0
- package/dist/query/subscribable.d.ts.map +1 -0
- package/dist/query/types.d.ts +54 -14
- package/dist/query/types.d.ts.map +1 -1
- package/dist/query.d.ts +9 -5
- package/dist/query.d.ts.map +1 -1
- package/dist/{query.min.js → query.js} +55 -34
- package/dist/retrieve/index.d.ts +1 -1
- package/dist/retrieve/index.d.ts.map +1 -1
- package/dist/retrieve/update-thread.d.ts +3 -3
- package/dist/retrieve/update-thread.d.ts.map +1 -1
- package/dist/retrieve.d.ts +1 -1
- package/dist/retrieve.d.ts.map +1 -1
- package/dist/retrieve.js +14 -0
- package/dist/thread/basic.d.ts +15 -19
- package/dist/thread/basic.d.ts.map +1 -1
- package/dist/thread/filters.d.ts +8 -10
- package/dist/thread/filters.d.ts.map +1 -1
- package/dist/thread/indexes.d.ts +57 -0
- package/dist/thread/indexes.d.ts.map +1 -0
- package/dist/thread/mapped.d.ts +40 -11
- package/dist/thread/mapped.d.ts.map +1 -1
- package/dist/thread/utils.d.ts +5 -5
- package/dist/thread/utils.d.ts.map +1 -1
- package/dist/thread/writeable.d.ts +2 -2
- package/dist/thread/writeable.d.ts.map +1 -1
- package/dist/thread.d.ts +6 -5
- package/dist/thread.d.ts.map +1 -1
- package/dist/{thread.min.js → thread.js} +9 -6
- package/dist/types/typescript-utils.d.ts +6 -5
- package/dist/types/typescript-utils.d.ts.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/{types.min.js → types.js} +3 -4
- package/dist/utils/debug-name.d.ts +13 -0
- package/dist/utils/debug-name.d.ts.map +1 -0
- package/dist/utils.d.ts +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +9 -0
- package/package.json +32 -23
- package/src/applog/applog-helpers.ts +155 -0
- package/src/applog/applog-utils.test.ts +108 -0
- package/src/applog/applog-utils.ts +551 -0
- package/src/applog/datom-types.ts +167 -0
- package/src/applog/object-values.test.ts +106 -0
- package/src/applog.ts +3 -0
- package/src/blockstore/index.ts +36 -0
- package/src/blockstore.ts +1 -0
- package/src/index.ts +8 -0
- package/src/ipfs/car.ts +291 -0
- package/src/ipfs/fetch-snapshot-chain.ts +135 -0
- package/src/ipfs/ipfs-utils.ts +132 -0
- package/src/ipfs.ts +3 -0
- package/src/ipns/ipns-record.ts +115 -0
- package/src/ipns.ts +1 -0
- package/src/pubsub/UCAN Specs Overview.md +217 -0
- package/src/pubsub/connector.ts +9 -0
- package/src/pubsub/pub-pull.ts +31 -0
- package/src/pubsub/pubsub-types.ts +90 -0
- package/src/pubsub/snap-push.ts +278 -0
- package/src/pubsub/ucan-example.ts +61 -0
- package/src/pubsub/ucan.ts +56 -0
- package/src/pubsub.ts +4 -0
- package/src/query/attr-helpers.ts +5 -0
- package/src/query/basic.ts +1245 -0
- package/src/query/divergences.ts +50 -0
- package/src/query/entity-collection.ts +132 -0
- package/src/query/liveFilterAndMap.test.ts +102 -0
- package/src/query/matchers.ts +30 -0
- package/src/query/memoized.test.ts +151 -0
- package/src/query/memoized.ts +180 -0
- package/src/query/query-steps.ts +4 -0
- package/src/query/query.test.ts +538 -0
- package/src/query/situations.ts +261 -0
- package/src/query/subscribable.test.ts +245 -0
- package/src/query/subscribable.ts +234 -0
- package/src/query/types.ts +155 -0
- package/src/query/withoutDeleted.test.ts +204 -0
- package/src/query.ts +9 -0
- package/src/retrieve/index.ts +1 -0
- package/src/retrieve/update-thread.ts +248 -0
- package/src/retrieve.ts +1 -0
- package/src/test/perf/query.1m.perf.test.ts +94 -0
- package/src/test/perf/query.perf.test.ts +389 -0
- package/src/test/perf/query.realdata.perf.test.ts +182 -0
- package/src/thread/basic.ts +209 -0
- package/src/thread/filters.ts +227 -0
- package/src/thread/indexes.ts +256 -0
- package/src/thread/joinThreads.test.ts +304 -0
- package/src/thread/mapped.ts +226 -0
- package/src/thread/utils.ts +144 -0
- package/src/thread/writeable.ts +163 -0
- package/src/thread.ts +6 -0
- package/src/types/typescript-utils.ts +64 -0
- package/src/types.ts +1 -0
- package/src/utils/debug-name.ts +54 -0
- package/src/utils.ts +4 -0
- package/dist/chunk-2Y2PYHGR.min.js +0 -65
- package/dist/chunk-2Y2PYHGR.min.js.map +0 -1
- package/dist/chunk-5MMGBK2U.min.js +0 -1
- package/dist/chunk-7IDQIMQO.min.js +0 -1
- package/dist/chunk-BRC7LSM6.min.js.map +0 -1
- package/dist/chunk-COXXILXC.min.js +0 -512
- package/dist/chunk-COXXILXC.min.js.map +0 -1
- package/dist/chunk-GDX2OO7L.min.js +0 -9080
- package/dist/chunk-GDX2OO7L.min.js.map +0 -1
- package/dist/chunk-H3VQJP56.min.js.map +0 -1
- package/dist/chunk-HYMC7W6S.min.js +0 -1549
- package/dist/chunk-HYMC7W6S.min.js.map +0 -1
- package/dist/chunk-KEHU7HGZ.min.js +0 -5216
- package/dist/chunk-KEHU7HGZ.min.js.map +0 -1
- package/dist/chunk-KXMTKPF4.min.js.map +0 -1
- package/dist/chunk-PHITDXZT.min.js +0 -36
- package/dist/chunk-QO2KMGDN.min.js +0 -3771
- package/dist/chunk-QO2KMGDN.min.js.map +0 -1
- package/dist/chunk-QPGEBDMJ.min.js.map +0 -1
- package/dist/chunk-WXLCBTHX.min.js +0 -1606
- package/dist/chunk-WXLCBTHX.min.js.map +0 -1
- package/dist/ipns.min.js +0 -6419
- package/dist/ipns.min.js.map +0 -1
- package/dist/mobx/mobx-utils.d.ts +0 -82
- package/dist/mobx/mobx-utils.d.ts.map +0 -1
- package/dist/mobx.d.ts +0 -2
- package/dist/mobx.d.ts.map +0 -1
- package/dist/mobx.min.js +0 -141
- package/dist/retrieve.min.js +0 -17
- package/dist/types.min.js.map +0 -1
- package/dist/utils.min.js +0 -10
- package/dist/utils.min.js.map +0 -1
- /package/dist/{applog.min.js.map → applog.js.map} +0 -0
- /package/dist/{chunk-5MMGBK2U.min.js.map → chunk-7Z5YDQKK.js.map} +0 -0
- /package/dist/{chunk-7IDQIMQO.min.js.map → chunk-E46VTKTZ.js.map} +0 -0
- /package/dist/{chunk-PHITDXZT.min.js.map → index.js.map} +0 -0
- /package/dist/{index.min.js.map → ipfs.js.map} +0 -0
- /package/dist/{ipfs.min.js.map → pubsub.js.map} +0 -0
- /package/dist/{mobx.min.js.map → query.js.map} +0 -0
- /package/dist/{pubsub.min.js.map → retrieve.js.map} +0 -0
- /package/dist/{query.min.js.map → thread.js.map} +0 -0
- /package/dist/{retrieve.min.js.map → types.js.map} +0 -0
- /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
|
+
}
|