@wovin/core 0.0.0-ciao-mobx-955482e8
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/LICENSE +661 -0
- package/README.md +3 -0
- package/dist/applog/applog-helpers.d.ts +47 -0
- package/dist/applog/applog-helpers.d.ts.map +1 -0
- package/dist/applog/applog-utils.d.ts +57 -0
- package/dist/applog/applog-utils.d.ts.map +1 -0
- package/dist/applog/datom-types.d.ts +128 -0
- package/dist/applog/datom-types.d.ts.map +1 -0
- package/dist/applog.d.ts +4 -0
- package/dist/applog.d.ts.map +1 -0
- package/dist/applog.js +101 -0
- package/dist/applog.js.map +1 -0
- package/dist/blockstore/index.d.ts +21 -0
- package/dist/blockstore/index.d.ts.map +1 -0
- package/dist/blockstore.d.ts +2 -0
- package/dist/blockstore.d.ts.map +1 -0
- package/dist/blockstore.js +24 -0
- package/dist/blockstore.js.map +1 -0
- package/dist/chunk-6MQKRL6W.js +86 -0
- package/dist/chunk-6MQKRL6W.js.map +1 -0
- package/dist/chunk-7MW34UEO.js +40 -0
- package/dist/chunk-7MW34UEO.js.map +1 -0
- package/dist/chunk-7Z5YDQKK.js +1 -0
- package/dist/chunk-7Z5YDQKK.js.map +1 -0
- package/dist/chunk-CY4NLISM.js +144 -0
- package/dist/chunk-CY4NLISM.js.map +1 -0
- package/dist/chunk-E46VTKTZ.js +1 -0
- package/dist/chunk-E46VTKTZ.js.map +1 -0
- package/dist/chunk-O43W7UW6.js +434 -0
- package/dist/chunk-O43W7UW6.js.map +1 -0
- package/dist/chunk-XIQSYEV3.js +1604 -0
- package/dist/chunk-XIQSYEV3.js.map +1 -0
- package/dist/chunk-XVGW4QC3.js +55 -0
- package/dist/chunk-XVGW4QC3.js.map +1 -0
- package/dist/chunk-YDAKBU6Q.js +9 -0
- 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/chunk-ZXCJRYD7.js +883 -0
- package/dist/chunk-ZXCJRYD7.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +354 -0
- package/dist/index.js.map +1 -0
- package/dist/ipfs/car.d.ts +59 -0
- package/dist/ipfs/car.d.ts.map +1 -0
- package/dist/ipfs/fetch-snapshot-chain.d.ts +32 -0
- package/dist/ipfs/fetch-snapshot-chain.d.ts.map +1 -0
- package/dist/ipfs/ipfs-utils.d.ts +35 -0
- package/dist/ipfs/ipfs-utils.d.ts.map +1 -0
- package/dist/ipfs.d.ts +4 -0
- package/dist/ipfs.d.ts.map +1 -0
- package/dist/ipfs.js +60 -0
- package/dist/ipfs.js.map +1 -0
- package/dist/ipns/ipns-record.d.ts +34 -0
- package/dist/ipns/ipns-record.d.ts.map +1 -0
- package/dist/ipns.d.ts +2 -0
- package/dist/ipns.d.ts.map +1 -0
- package/dist/ipns.js +64 -0
- package/dist/ipns.js.map +1 -0
- package/dist/pubsub/connector.d.ts +9 -0
- package/dist/pubsub/connector.d.ts.map +1 -0
- package/dist/pubsub/pub-pull.d.ts +14 -0
- package/dist/pubsub/pub-pull.d.ts.map +1 -0
- package/dist/pubsub/pubsub-types.d.ts +72 -0
- package/dist/pubsub/pubsub-types.d.ts.map +1 -0
- package/dist/pubsub/snap-push.d.ts +41 -0
- package/dist/pubsub/snap-push.d.ts.map +1 -0
- package/dist/pubsub/ucan-example.d.ts +3 -0
- package/dist/pubsub/ucan-example.d.ts.map +1 -0
- package/dist/pubsub/ucan.d.ts +16 -0
- package/dist/pubsub/ucan.d.ts.map +1 -0
- package/dist/pubsub.d.ts +5 -0
- package/dist/pubsub.d.ts.map +1 -0
- package/dist/pubsub.js +31 -0
- package/dist/pubsub.js.map +1 -0
- package/dist/query/basic.d.ts +105 -0
- package/dist/query/basic.d.ts.map +1 -0
- package/dist/query/divergences.d.ts +12 -0
- package/dist/query/divergences.d.ts.map +1 -0
- package/dist/query/matchers.d.ts +4 -0
- package/dist/query/matchers.d.ts.map +1 -0
- package/dist/query/memoized.d.ts +66 -0
- package/dist/query/memoized.d.ts.map +1 -0
- package/dist/query/query-steps.d.ts +4 -0
- package/dist/query/query-steps.d.ts.map +1 -0
- package/dist/query/situations.d.ts +80 -0
- package/dist/query/situations.d.ts.map +1 -0
- package/dist/query/subscribable.d.ts +102 -0
- package/dist/query/subscribable.d.ts.map +1 -0
- package/dist/query/types.d.ts +70 -0
- package/dist/query/types.d.ts.map +1 -0
- package/dist/query.d.ts +8 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +108 -0
- package/dist/query.js.map +1 -0
- package/dist/retrieve/index.d.ts +2 -0
- package/dist/retrieve/index.d.ts.map +1 -0
- package/dist/retrieve/update-thread.d.ts +64 -0
- package/dist/retrieve/update-thread.d.ts.map +1 -0
- package/dist/retrieve.d.ts +2 -0
- package/dist/retrieve.d.ts.map +1 -0
- package/dist/retrieve.js +14 -0
- package/dist/retrieve.js.map +1 -0
- package/dist/thread/basic.d.ts +60 -0
- package/dist/thread/basic.d.ts.map +1 -0
- package/dist/thread/filters.d.ts +47 -0
- package/dist/thread/filters.d.ts.map +1 -0
- package/dist/thread/mapped.d.ts +31 -0
- package/dist/thread/mapped.d.ts.map +1 -0
- package/dist/thread/utils.d.ts +23 -0
- package/dist/thread/utils.d.ts.map +1 -0
- package/dist/thread/writeable.d.ts +41 -0
- package/dist/thread/writeable.d.ts.map +1 -0
- package/dist/thread.d.ts +6 -0
- package/dist/thread.d.ts.map +1 -0
- package/dist/thread.js +54 -0
- package/dist/thread.js.map +1 -0
- package/dist/types/typescript-utils.d.ts +34 -0
- package/dist/types/typescript-utils.d.ts.map +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +26 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/debug-name.d.ts +13 -0
- package/dist/utils/debug-name.d.ts.map +1 -0
- package/dist/utils.d.ts +4 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +9 -0
- package/dist/utils.js.map +1 -0
- package/package.json +110 -0
- package/src/applog/applog-helpers.ts +150 -0
- package/src/applog/applog-utils.ts +398 -0
- package/src/applog/datom-types.ts +148 -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 +277 -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/basic.ts +1061 -0
- package/src/query/divergences.ts +50 -0
- package/src/query/matchers.ts +8 -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 +536 -0
- package/src/query/situations.ts +261 -0
- package/src/query/subscribable.test.ts +245 -0
- package/src/query/subscribable.ts +225 -0
- package/src/query/types.ts +155 -0
- package/src/query.ts +7 -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 +175 -0
- package/src/thread/basic.ts +209 -0
- package/src/thread/filters.ts +234 -0
- package/src/thread/mapped.ts +166 -0
- package/src/thread/utils.ts +146 -0
- package/src/thread/writeable.ts +163 -0
- package/src/thread.ts +5 -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
|
@@ -0,0 +1,398 @@
|
|
|
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
|
+
export const compareApplogsByTs = (logA: Applog, logB: Applog, dir: 'asc' | 'desc' = 'asc') => isoDateStrCompare(logA.ts, logB.ts, dir)
|
|
28
|
+
export const compareApplogsByEnAt = partial(objEqualByKeys, ['en', 'at'])
|
|
29
|
+
|
|
30
|
+
/** Sort by TS (modifies the array, but also returns for ease of use) */
|
|
31
|
+
export function sortApplogsByTs(appLogArray: Applog[], dir: 'asc' | 'desc' = 'asc') {
|
|
32
|
+
return appLogArray.sort((a, b) => compareApplogsByTs(a, b, dir))
|
|
33
|
+
}
|
|
34
|
+
export const isTsBefore = (log: Applog, logToCompare: Applog) => isBefore(new Date(log.ts), new Date(logToCompare.ts))
|
|
35
|
+
export const uniqueEnFromAppLogs = (appLogArray: Applog[]) => [...new Set(appLogArray.map(eachLog => eachLog.en))]
|
|
36
|
+
export const areApplogsEqual = (logA: Applog, logB: Applog) => isEqual(logA, logB)
|
|
37
|
+
|
|
38
|
+
export type RemoveDuplicateAppLogsMode = 'safety' | 'cleanup'
|
|
39
|
+
|
|
40
|
+
const warnMissingRemoveDuplicateMode = () => {
|
|
41
|
+
WARN(`[removeDuplicateAppLogs] mode not set; pass 'safety' or 'cleanup' for optimal behavior`)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const removeDuplicateAppLogsCleanup = (appLogArray: Applog[]) => {
|
|
45
|
+
const logMap = new Map<string, Applog>()
|
|
46
|
+
const verboseEnabled = VERBOSE.isEnabled
|
|
47
|
+
for (const eachLog of appLogArray) {
|
|
48
|
+
if (!eachLog) {
|
|
49
|
+
ERROR(`falsy entry in applogs`, appLogArray)
|
|
50
|
+
throw new Error(`falsy entry in applogs`)
|
|
51
|
+
}
|
|
52
|
+
if (!eachLog.cid) {
|
|
53
|
+
ERROR(`applog with missing CID`, eachLog)
|
|
54
|
+
throw new Error(`applog with missing CID`)
|
|
55
|
+
}
|
|
56
|
+
const key = eachLog.cid
|
|
57
|
+
const existing = logMap.get(key)
|
|
58
|
+
if (existing) {
|
|
59
|
+
if (verboseEnabled) VERBOSE(`Skipping duplicate applog:`, [existing, eachLog])
|
|
60
|
+
} else {
|
|
61
|
+
logMap.set(key, eachLog)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return Array.from(logMap.values())
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const removeDuplicateAppLogsSafety = (appLogArray: Applog[]) => {
|
|
68
|
+
const seen = new Set<string>()
|
|
69
|
+
const verboseEnabled = VERBOSE.isEnabled
|
|
70
|
+
const existingByCid = verboseEnabled ? new Map<string, Applog>() : null
|
|
71
|
+
let result: Applog[] | null = null
|
|
72
|
+
let index = 0
|
|
73
|
+
for (const eachLog of appLogArray) {
|
|
74
|
+
if (!eachLog) {
|
|
75
|
+
ERROR(`falsy entry in applogs`, appLogArray)
|
|
76
|
+
throw new Error(`falsy entry in applogs`)
|
|
77
|
+
}
|
|
78
|
+
if (!eachLog.cid) {
|
|
79
|
+
ERROR(`applog with missing CID`, eachLog)
|
|
80
|
+
throw new Error(`applog with missing CID`)
|
|
81
|
+
}
|
|
82
|
+
const key = eachLog.cid
|
|
83
|
+
if (seen.has(key)) {
|
|
84
|
+
if (!result) {
|
|
85
|
+
result = appLogArray.slice(0, index)
|
|
86
|
+
}
|
|
87
|
+
if (verboseEnabled) VERBOSE(`Skipping duplicate applog:`, [existingByCid?.get(key), eachLog])
|
|
88
|
+
} else {
|
|
89
|
+
seen.add(key)
|
|
90
|
+
if (existingByCid) existingByCid.set(key, eachLog)
|
|
91
|
+
if (result) result.push(eachLog)
|
|
92
|
+
}
|
|
93
|
+
index++
|
|
94
|
+
}
|
|
95
|
+
return result ?? appLogArray
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Deduplicate applogs by CID.
|
|
100
|
+
* - safety: fast duplicate check; returns original array if no duplicates found.
|
|
101
|
+
* - cleanup: optimized for merged arrays with likely duplicates.
|
|
102
|
+
*/
|
|
103
|
+
export const removeDuplicateAppLogs = (appLogArray: Applog[], mode?: RemoveDuplicateAppLogsMode) => {
|
|
104
|
+
if (!mode) {
|
|
105
|
+
warnMissingRemoveDuplicateMode()
|
|
106
|
+
return removeDuplicateAppLogsCleanup(appLogArray)
|
|
107
|
+
}
|
|
108
|
+
return mode === 'safety'
|
|
109
|
+
? removeDuplicateAppLogsSafety(appLogArray)
|
|
110
|
+
: removeDuplicateAppLogsCleanup(appLogArray)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// export const removeDuplicateAndMaybeDeletedAppLogs = (ds: Thread, appLogArray: Applog[], removeDeletedEntities = true) => {
|
|
114
|
+
// const logMap = new Map()
|
|
115
|
+
// for (const eachLog of appLogArray) {
|
|
116
|
+
// if (!removeDeletedEntities || ds.entityIsDeleted(eachLog.en))
|
|
117
|
+
// logMap.set(stringify(eachLog), eachLog)
|
|
118
|
+
// }
|
|
119
|
+
// return Array.from(logMap.values())
|
|
120
|
+
// }
|
|
121
|
+
|
|
122
|
+
export const getHashID = (stringifiable: any, lngth = 8) => cyrb53hash(stringify(stringifiable), 31, lngth) as string
|
|
123
|
+
|
|
124
|
+
export function isVariable(x: any): x is string {
|
|
125
|
+
return typeof x === 'string' && x.startsWith('?')
|
|
126
|
+
}
|
|
127
|
+
export function variableNameWithoutQuestionmark(str: string) {
|
|
128
|
+
return str.slice(1)
|
|
129
|
+
}
|
|
130
|
+
// export function isMatcher(x: any): x is string {
|
|
131
|
+
// return
|
|
132
|
+
// }
|
|
133
|
+
export function isStaticPattern(x: any): x is ApplogValue {
|
|
134
|
+
if (!['string', 'boolean', 'number', 'function'].includes(typeof x)) WARN(`Unhandled pattern value type:`, typeof x, x)
|
|
135
|
+
return !isVariable(x) && ['string', 'boolean', 'number'].includes(typeof x)
|
|
136
|
+
}
|
|
137
|
+
// export function isIgnorePattern(x: any): boolean {
|
|
138
|
+
// return x === '_'
|
|
139
|
+
// }
|
|
140
|
+
|
|
141
|
+
/*
|
|
142
|
+
* In a pattern from a Query:
|
|
143
|
+
* - variables that don't have a value in the search context:
|
|
144
|
+
* - remove from pattern
|
|
145
|
+
* - add to variableToFill as: { en: 'movieID' } (useful for mapTo)
|
|
146
|
+
* - variables that have a value set:
|
|
147
|
+
* - replace placeholder with actual value
|
|
148
|
+
*/
|
|
149
|
+
export function resolveOrRemoveVariables(pattern: DatalogQueryPattern, candidate: SearchContext) {
|
|
150
|
+
let variablesToFill = {} as Partial<{ [key in keyof Applog]: string }>
|
|
151
|
+
const newPattern: DatalogQueryPattern = {}
|
|
152
|
+
for (const [patternKey, patternValue] of Object.entries(pattern)) {
|
|
153
|
+
if (isVariable(patternValue)) {
|
|
154
|
+
const varName = variableNameWithoutQuestionmark(patternValue)
|
|
155
|
+
const candidateValue = candidate[varName]
|
|
156
|
+
if (candidateValue) {
|
|
157
|
+
newPattern[patternKey] = candidateValue
|
|
158
|
+
// & not adding it to newPattern
|
|
159
|
+
} else {
|
|
160
|
+
variablesToFill[patternKey] = varName
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
newPattern[patternKey] = patternValue // keep static value
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return [newPattern, variablesToFill] as const
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function matchVariable(variable: string, triplePart: ApplogValue, context: SearchContext): SearchContext {
|
|
171
|
+
if (context.hasOwnProperty(variable)) {
|
|
172
|
+
// TODO: fix lint error with: if (Object.hasOwnProperty.call(context, variable)) {
|
|
173
|
+
const bound = context[variable]
|
|
174
|
+
const match = matchPart(bound, triplePart, context)
|
|
175
|
+
// if (VERBOSE.isEnabled) VERBOSE('[matchVariable] match?', variable, bound, match)
|
|
176
|
+
return match
|
|
177
|
+
}
|
|
178
|
+
// if (VERBOSE.isEnabled) VERBOSE('[matchVariable] initializing variable', variable, 'to', triplePart)
|
|
179
|
+
return { ...context, [variable]: triplePart }
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function matchPartStatic(field: keyof Applog, patternPart: ValueOrMatcher<ApplogValue>, atomPart: ApplogValue): boolean {
|
|
183
|
+
// if (VERBOSE.isEnabled) VERBOSE('[matchPartStatic]', field, patternPart, patternPart === atomPart ? '===' : '!==', atomPart)
|
|
184
|
+
let result
|
|
185
|
+
if (patternPart) {
|
|
186
|
+
const typ = typeof patternPart
|
|
187
|
+
if (typ === 'string') {
|
|
188
|
+
result = patternPart === atomPart // shortcut for most common use-case
|
|
189
|
+
} else if (typ === 'function') {
|
|
190
|
+
result = (patternPart as Function)(atomPart)
|
|
191
|
+
} else if (typeof (patternPart as any).has === 'function') {
|
|
192
|
+
result = (patternPart as Set<any>).has(atomPart)
|
|
193
|
+
} else if (Array.isArray(patternPart) && !Array.isArray(atomPart) /* ? how to handle array values */) {
|
|
194
|
+
result = patternPart.includes(atomPart)
|
|
195
|
+
} // if (field === 'at' && typ === 'string' && patternPart.endsWith('*')) {
|
|
196
|
+
// return typeof atomPart === 'string' && atomPart.startsWith(patternPart.slice(0, -1))
|
|
197
|
+
// }
|
|
198
|
+
else {
|
|
199
|
+
result = patternPart === atomPart
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
result = patternPart === atomPart
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// if (VERBOSE.isEnabled) VERBOSE('[matchPartStatic] =>', field.startsWith('!') ? '!' : '', result)
|
|
206
|
+
if (field.charAt(0) === '!') {
|
|
207
|
+
return !result
|
|
208
|
+
} else {
|
|
209
|
+
return result
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
export function matchPart(patternPart: ValueOrMatcher<ApplogValue>, atomPart: ApplogValue, context: SearchContext): ResultContext {
|
|
213
|
+
if (!context) {
|
|
214
|
+
// if (VERBOSE.isEnabled) VERBOSE('[matchPart] no context')
|
|
215
|
+
return null
|
|
216
|
+
}
|
|
217
|
+
if (typeof patternPart === 'string') {
|
|
218
|
+
if (isVariable(patternPart)) {
|
|
219
|
+
return matchVariable(patternPart, atomPart, context)
|
|
220
|
+
} /* TODO: else if (isIgnorePattern(patternPart)) {
|
|
221
|
+
return matchVariable(patternPart, atomPart, context)
|
|
222
|
+
} */
|
|
223
|
+
}
|
|
224
|
+
// if (VERBOSE.isEnabled) VERBOSE('[matchPart]', patternPart, patternPart === atomPart ? '===' : '!==', atomPart)
|
|
225
|
+
if (typeof patternPart === 'function') {
|
|
226
|
+
return patternPart(atomPart) ? context : null
|
|
227
|
+
}
|
|
228
|
+
return patternPart === atomPart ? context : null
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Check if pattern matches triple with context substitutions
|
|
233
|
+
*/
|
|
234
|
+
export function matchPattern(pattern: DatalogQueryPattern, applog: Applog, context: SearchContext): ResultContext {
|
|
235
|
+
return Object.entries(pattern).reduce((context, [field, patternValue]) => {
|
|
236
|
+
const applogValue = applog[field]
|
|
237
|
+
// @ts-expect-error wtf no idea //HACK: ts weird
|
|
238
|
+
const patternValT: ValueOrMatcher<ApplogValue> = patternValue
|
|
239
|
+
return matchPart(patternValT, applogValue, context)
|
|
240
|
+
}, context)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function actualize<SELECT extends string>(context: ResultContext, find: readonly SELECT[]): DatalogQueryResultEntry<SELECT> {
|
|
244
|
+
return Object.fromEntries(find.map((findField) => {
|
|
245
|
+
if (context === null) {
|
|
246
|
+
throw new Error(`actualize context is null ${find}`)
|
|
247
|
+
}
|
|
248
|
+
return [
|
|
249
|
+
isVariable(findField) ? findField.replace(/^\?/, '') : findField,
|
|
250
|
+
isVariable(findField) ? context[findField] : findField,
|
|
251
|
+
]
|
|
252
|
+
})) as DatalogQueryResultEntry<SELECT>
|
|
253
|
+
}
|
|
254
|
+
const sum = function sum(array: number[]) {
|
|
255
|
+
var num = 0
|
|
256
|
+
for (var i = 0, l = array.length; i < l; i++) num += array[i]
|
|
257
|
+
return num
|
|
258
|
+
}
|
|
259
|
+
const mean = function mean(array: number[]) {
|
|
260
|
+
return sum(array) / array.length
|
|
261
|
+
}
|
|
262
|
+
export const arrStats = {
|
|
263
|
+
max: function(array: number[]) {
|
|
264
|
+
return Math.max.apply(null, array)
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
min: function(array: number[]) {
|
|
268
|
+
return Math.min.apply(null, array)
|
|
269
|
+
},
|
|
270
|
+
|
|
271
|
+
range: function(array: number[]) {
|
|
272
|
+
return arrStats.max(array) - arrStats.min(array)
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
midrange: function(array: number[]) {
|
|
276
|
+
return arrStats.range(array) / 2
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
sum,
|
|
280
|
+
|
|
281
|
+
mean,
|
|
282
|
+
|
|
283
|
+
average: mean,
|
|
284
|
+
|
|
285
|
+
median: function(array: number[]) {
|
|
286
|
+
array.sort(function(a, b) {
|
|
287
|
+
return a - b
|
|
288
|
+
})
|
|
289
|
+
var mid = array.length / 2
|
|
290
|
+
return mid % 1 ? array[mid - 0.5] : (array[mid - 1] + array[mid]) / 2
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
modes: function(array: number[]) {
|
|
294
|
+
if (!array.length) return []
|
|
295
|
+
var modeMap = {},
|
|
296
|
+
maxCount = 0,
|
|
297
|
+
modes = []
|
|
298
|
+
|
|
299
|
+
array.forEach(function(val) {
|
|
300
|
+
if (!modeMap[val]) modeMap[val] = 1
|
|
301
|
+
else modeMap[val]++
|
|
302
|
+
|
|
303
|
+
if (modeMap[val] > maxCount) {
|
|
304
|
+
modes = [val]
|
|
305
|
+
maxCount = modeMap[val]
|
|
306
|
+
} else if (modeMap[val] === maxCount) {
|
|
307
|
+
modes.push(val)
|
|
308
|
+
maxCount = modeMap[val]
|
|
309
|
+
}
|
|
310
|
+
})
|
|
311
|
+
return modes
|
|
312
|
+
},
|
|
313
|
+
|
|
314
|
+
variance: function(array: number[]) {
|
|
315
|
+
var mean = arrStats.mean(array)
|
|
316
|
+
return arrStats.mean(array.map(function(num) {
|
|
317
|
+
return Math.pow(num - mean, 2)
|
|
318
|
+
}))
|
|
319
|
+
},
|
|
320
|
+
|
|
321
|
+
standardDeviation: function(array: number[]) {
|
|
322
|
+
return Math.sqrt(arrStats.variance(array))
|
|
323
|
+
},
|
|
324
|
+
|
|
325
|
+
meanAbsoluteDeviation: function(array: number[]) {
|
|
326
|
+
var mean = arrStats.mean(array)
|
|
327
|
+
return arrStats.mean(array.map(function(num) {
|
|
328
|
+
return Math.abs(num - mean)
|
|
329
|
+
}))
|
|
330
|
+
},
|
|
331
|
+
|
|
332
|
+
zScores: function(array: number[]) {
|
|
333
|
+
var mean = arrStats.mean(array)
|
|
334
|
+
var standardDeviation = arrStats.standardDeviation(array)
|
|
335
|
+
return array.map(function(num) {
|
|
336
|
+
return (num - mean) / standardDeviation
|
|
337
|
+
})
|
|
338
|
+
},
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Function aliases:
|
|
342
|
+
arrStats.average = arrStats.mean
|
|
343
|
+
|
|
344
|
+
export const tsNearlySame = (timeA: string, timeB: string) => timeB.startsWith(timeA.slice(0, timeA.length - 4)) // HACK: to quickly check if same second
|
|
345
|
+
|
|
346
|
+
/*
|
|
347
|
+
cyrb53 (c) 2018 bryc (github.com/bryc)
|
|
348
|
+
A fast and simple hash function with decent collision resistance.
|
|
349
|
+
Largely inspired by MurmurHash2/3, but with a focus on speed/simplicity.
|
|
350
|
+
Public domain. Attribution appreciated.
|
|
351
|
+
|
|
352
|
+
ripped from https://github.com/bryc/code/blob/mast`er/jshash/experimental/cyrb53.js
|
|
353
|
+
*/
|
|
354
|
+
export const cyrb53hash = function(
|
|
355
|
+
str: string,
|
|
356
|
+
seed = 13,
|
|
357
|
+
strLength: number, /* = 0 */
|
|
358
|
+
) {
|
|
359
|
+
if (!str?.length) {
|
|
360
|
+
throw new Error(`Empty string: ${str}`)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
let h1 = 0xdeadbeef ^ seed
|
|
364
|
+
let h2 = 0x41c6ce57 ^ seed
|
|
365
|
+
for (let i = 0, ch; i < str.length; i++) {
|
|
366
|
+
ch = str.charCodeAt(i)
|
|
367
|
+
h1 = Math.imul(h1 ^ ch, 2654435761)
|
|
368
|
+
h2 = Math.imul(h2 ^ ch, 1597334677)
|
|
369
|
+
}
|
|
370
|
+
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909)
|
|
371
|
+
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909)
|
|
372
|
+
// if (strLength) {
|
|
373
|
+
const asHex = (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(16)
|
|
374
|
+
return asHex.slice(-strLength).padStart(strLength, '0')
|
|
375
|
+
// }
|
|
376
|
+
// // if not specified return as 16 digit integer
|
|
377
|
+
// return 4294967296 * (2097151 & h2) + (h1 >>> 0)
|
|
378
|
+
}
|
|
379
|
+
export function arraysContainSameElements(arr1, arr2) {
|
|
380
|
+
if (arr1.length !== arr2.length) {
|
|
381
|
+
return false
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const sortedArr1 = [...arr1].sort()
|
|
385
|
+
const sortedArr2 = [...arr2].sort()
|
|
386
|
+
|
|
387
|
+
for (let i = 0; i < sortedArr1.length; i++) {
|
|
388
|
+
if (sortedArr1[i] !== sortedArr2[i]) {
|
|
389
|
+
return false
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return true
|
|
394
|
+
}
|
|
395
|
+
export function dateNowIso(): string {
|
|
396
|
+
const now = new Date()
|
|
397
|
+
return now.toISOString()
|
|
398
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// import type { AgentHash } from '../pubsub/pubsub-types.ts'
|
|
2
|
+
// import type { CID } from '@oddjs/odd'
|
|
3
|
+
import { FormatRegistry, Static, TSchema, Type } from '@sinclair/typebox'
|
|
4
|
+
import { TypeCompiler } from '@sinclair/typebox/compiler'
|
|
5
|
+
import { CID } from 'multiformats/cid'
|
|
6
|
+
import type { PartialBy, Tagged } from '../types/typescript-utils.ts'
|
|
7
|
+
|
|
8
|
+
export const Nullable = <T extends TSchema>(schema: T) => Type.Union([schema, Type.Null()])
|
|
9
|
+
export const EntityID_LENGTH = 7
|
|
10
|
+
// const bagu = 'baguqeerav3h4b46j2pyxikqhtm5si5vhzsyrba2duhrtltfutrlmj42anmvq'
|
|
11
|
+
// const k51q = 'k51qzi5uqu5dhe1bxxjxj144bj2a225o1681yobevns26xlxtsfidjgnpwknfd'
|
|
12
|
+
const isCID = /^(k51qz|baguq)[0-9a-z]{56,57}$/ // FIXME: k51 is not really a CID, is it?
|
|
13
|
+
const isShortHash = /^[0-9A-Fa-f]{7,8}$/g // TODO awkward why are some 7 and some 8 long
|
|
14
|
+
// engine level: min 6 (lenient within reason)
|
|
15
|
+
// note3 TBD... either fixed for all entity types VS 7 for pub/sub, 8 for tags, 9 for blocks, 10 for relations etc...
|
|
16
|
+
|
|
17
|
+
FormatRegistry.Set('EntityID', (value) => !!value.match(isShortHash) || !!value.match(isCID))
|
|
18
|
+
export const EntityID = Type.String() // HACK how to configure ID format?
|
|
19
|
+
/*{ format: 'EntityID' }*/
|
|
20
|
+
export type EntityID = Static<typeof EntityID>
|
|
21
|
+
|
|
22
|
+
export type AgentHash = Tagged<string, 'AgentHash'>
|
|
23
|
+
export type DatomPart = string // TODO refactor
|
|
24
|
+
export type CidString = Tagged<string, CID>
|
|
25
|
+
export type IpnsString = Tagged<CidString, 'IPNS'>
|
|
26
|
+
export type AgentID = EntityID
|
|
27
|
+
export type Attribute = string
|
|
28
|
+
export type ApplogValue = string | boolean | number | null // TODO: use Tagged types
|
|
29
|
+
// ? allow objects? or just as serialized strings? Or serialize everything anyways?
|
|
30
|
+
|
|
31
|
+
export interface Atom {
|
|
32
|
+
en: EntityID
|
|
33
|
+
at: Attribute
|
|
34
|
+
vl: ApplogValue
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type Timestamp = string
|
|
38
|
+
export interface Applog extends Atom {
|
|
39
|
+
cid: CidString
|
|
40
|
+
pv: CidString | null // ? | CID
|
|
41
|
+
ts: Timestamp
|
|
42
|
+
ag: AgentHash
|
|
43
|
+
}
|
|
44
|
+
export type ApplogNoCid = Omit<Applog, 'cid'>
|
|
45
|
+
export type ApplogOptionalCid = PartialBy<Applog, 'cid'>
|
|
46
|
+
export type ApplogForInsert = PartialBy<ApplogNoCid, 'ts' | 'pv'>
|
|
47
|
+
export type ApplogForInsertOptionalAgent = PartialBy<ApplogForInsert, 'ag'>
|
|
48
|
+
|
|
49
|
+
export interface ApplogEnc {
|
|
50
|
+
cid: CidString
|
|
51
|
+
enc: Uint8Array
|
|
52
|
+
iv?: Uint8Array // iv may be needed if we prefer a different strategy for transmitting iv (odd appends a random iv to the payload via keystoreAES.(en|de)cryptBytes)
|
|
53
|
+
}
|
|
54
|
+
export type ApplogEncNoCid = Omit<ApplogEnc, 'cid'>
|
|
55
|
+
|
|
56
|
+
export type ApplogArrayMaybeEncrypted = (Applog | ApplogEnc)[]
|
|
57
|
+
export type ApplogArrayMaybeEncryptedRO = readonly (Applog | ApplogEnc)[]
|
|
58
|
+
export type ApplogArrayNoCIDMaybeEncryptedRO = readonly (ApplogNoCid | ApplogEncNoCid)[]
|
|
59
|
+
|
|
60
|
+
export type ApplogOfSomeSort = Applog | ApplogEnc | ApplogNoCid | ApplogEncNoCid | ApplogForInsert
|
|
61
|
+
|
|
62
|
+
export const isEncryptedApplog = (l: ApplogOfSomeSort): l is ApplogEnc => (l as ApplogEnc)?.enc instanceof Uint8Array
|
|
63
|
+
|
|
64
|
+
export type AtomPattern = Atom | Applog
|
|
65
|
+
|
|
66
|
+
export interface DatalogStateIdentifier {
|
|
67
|
+
lastTS: Timestamp
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// New generic type for fields that can be a value, an array of that, or a function
|
|
71
|
+
export type ValueOrMatcher<T> = T | readonly T[] | ReadonlySet<T> | ((value: T) => boolean)
|
|
72
|
+
// Generic type that applies ValueOrMatcher to each field of T
|
|
73
|
+
export type WithMatchers<T extends Record<string, any>> = {
|
|
74
|
+
[K in keyof T & string as `${K}` | `!${K}`]?: ValueOrMatcher<T[K]>
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export type DatalogQueryPattern = Partial<WithMatchers<Applog>>
|
|
78
|
+
export type DatalogQueryPatternArray = DatalogQueryPattern[]
|
|
79
|
+
export interface DatalogQuery<SELECT extends string> {
|
|
80
|
+
find: readonly SELECT[] // see: https://github.com/microsoft/TypeScript/issues/20965#issuecomment-868981458
|
|
81
|
+
where: DatalogQueryPatternArray
|
|
82
|
+
onlyLatest?: boolean
|
|
83
|
+
}
|
|
84
|
+
export type DatalogQueryResultEntry<SELECT extends string> = Record<
|
|
85
|
+
// SELECT,
|
|
86
|
+
StripPrefix<'?', SELECT>,
|
|
87
|
+
DatomPart
|
|
88
|
+
>
|
|
89
|
+
export type DatalogQueryResultRows<SELECT extends string> = DatalogQueryResultEntry<SELECT>[]
|
|
90
|
+
// export type StripTest = StripPrefix<'?', '?A' | '?B'>
|
|
91
|
+
// export type DatalogQueryResultEntryTEST = DatalogQueryResultEntry<'?A' | '?B'>
|
|
92
|
+
// export type DatalogQueryResultEntryTESTX = MapKeysStripPrefix<'?A' | '?B', '?'>
|
|
93
|
+
|
|
94
|
+
export interface SearchContext {
|
|
95
|
+
[key: string]: ApplogValue
|
|
96
|
+
}
|
|
97
|
+
export interface SearchContextWithLog {
|
|
98
|
+
context: SearchContext
|
|
99
|
+
applog?: Applog
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export type ResultContext = SearchContext | null
|
|
103
|
+
|
|
104
|
+
/* https://stackoverflow.com/a/72497461 */
|
|
105
|
+
type StripPrefix<
|
|
106
|
+
TPrefix extends string,
|
|
107
|
+
T extends string,
|
|
108
|
+
> = T extends `${TPrefix}${infer R}` ? R : never
|
|
109
|
+
|
|
110
|
+
type MapKeysStripPrefix<SELECT extends string, TPrefix extends string> = {
|
|
111
|
+
[K in SELECT as StripPrefix<TPrefix, K>]: DatomPart
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
FormatRegistry.Set('CID', (value) => !!value.match(isCID))
|
|
115
|
+
export const CIDTB = Type.String({ format: 'CID' })
|
|
116
|
+
export type CIDTB = Static<typeof EntityID>
|
|
117
|
+
|
|
118
|
+
const isURL = /^http([s]?):\/\/.*\..*/
|
|
119
|
+
FormatRegistry.Set('URL', (value) => !!value.match(isURL))
|
|
120
|
+
export const URL = Type.String({ format: 'URL' })
|
|
121
|
+
export type URL = Static<typeof URL>
|
|
122
|
+
|
|
123
|
+
export const AppLogNoCidTB = Type.Object({
|
|
124
|
+
en: EntityID, // EntityID
|
|
125
|
+
at: Type.String(), // Attribute
|
|
126
|
+
vl: Nullable(Type.Union([Type.String(), Type.Boolean(), Type.Number()])), // TODO refactor to semantic typesafe ApplogValue
|
|
127
|
+
ts: Type.String(), // Timestamp
|
|
128
|
+
ag: Type.String(), // AgentHash
|
|
129
|
+
pv: Nullable(CIDTB), // CidString
|
|
130
|
+
})
|
|
131
|
+
export type AppLogNoCidTB = Static<typeof AppLogNoCidTB> // type T = {
|
|
132
|
+
|
|
133
|
+
export const AppLogNoCidTBC = TypeCompiler.Compile(AppLogNoCidTB)
|
|
134
|
+
export const getApplogNoCidTypeErrors = (obj: any) => Array.from(AppLogNoCidTBC.Errors(obj))
|
|
135
|
+
export const isValidApplogNoCid = AppLogNoCidTBC.Check.bind(AppLogNoCidTBC) // ? Include CID
|
|
136
|
+
|
|
137
|
+
export const AppLogTB = Type.Composite([
|
|
138
|
+
Type.Object({
|
|
139
|
+
cid: CIDTB,
|
|
140
|
+
}),
|
|
141
|
+
AppLogNoCidTB,
|
|
142
|
+
])
|
|
143
|
+
export type AppLogTB = Static<typeof AppLogTB> // type T = {
|
|
144
|
+
|
|
145
|
+
export const AppLogTBC = TypeCompiler.Compile(AppLogTB)
|
|
146
|
+
export const getApplogTypeErrors = (obj: any) => Array.from(AppLogTBC.Errors(obj))
|
|
147
|
+
export const isValidApplog = AppLogTBC.Check.bind(AppLogTBC) // ? Include CID
|
|
148
|
+
// maybe useful for defaulting https://github.com/sinclairzx81/typebox#cast
|
package/src/applog.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { CID } from 'multiformats/cid'
|
|
2
|
+
|
|
3
|
+
/** Minimal async block store interface — get/put/has. */
|
|
4
|
+
export interface BlockStore {
|
|
5
|
+
get(cid: CID): Promise<Uint8Array>
|
|
6
|
+
put(cid: CID, bytes: Uint8Array): Promise<void>
|
|
7
|
+
has(cid: CID): Promise<boolean>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A block store that reads locally first, with optional remote fallback.
|
|
12
|
+
* On get: local hit → return; local miss + remote → fetch, write-back, return.
|
|
13
|
+
* put/has always operate on local only.
|
|
14
|
+
*/
|
|
15
|
+
export class LocalFirstBlockStore implements BlockStore {
|
|
16
|
+
constructor(
|
|
17
|
+
private local: BlockStore,
|
|
18
|
+
private remote?: Pick<BlockStore, 'get'>,
|
|
19
|
+
) {}
|
|
20
|
+
|
|
21
|
+
async get(cid: CID): Promise<Uint8Array> {
|
|
22
|
+
if (await this.local.has(cid)) return this.local.get(cid)
|
|
23
|
+
if (!this.remote) throw new Error(`Block not found: ${cid}`)
|
|
24
|
+
const bytes = await this.remote.get(cid)
|
|
25
|
+
await this.local.put(cid, bytes)
|
|
26
|
+
return bytes
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
put(cid: CID, bytes: Uint8Array): Promise<void> {
|
|
30
|
+
return this.local.put(cid, bytes)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
has(cid: CID): Promise<boolean> {
|
|
34
|
+
return this.local.has(cid)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './blockstore/index.ts'
|