@wovin/core 0.2.0 → 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/dist/applog/applog-utils.d.ts +15 -0
- package/dist/applog/applog-utils.d.ts.map +1 -1
- package/dist/applog/datom-types.d.ts +63 -7
- package/dist/applog/datom-types.d.ts.map +1 -1
- package/dist/applog.js +7 -1
- package/dist/{chunk-CPSDKFBG.js → chunk-22WDFLXO.js} +5 -14
- package/dist/chunk-22WDFLXO.js.map +1 -0
- package/dist/{chunk-QZXKQCAY.js → chunk-3SUFNJEZ.js} +2 -2
- package/dist/{chunk-3WZVG277.js → chunk-6ALNRM3J.js} +5 -4
- package/dist/chunk-6ALNRM3J.js.map +1 -0
- package/dist/{chunk-3JZMOEOD.js → chunk-BLF5MAWU.js} +2 -2
- package/dist/{chunk-J2FDHGOZ.js → chunk-HUIQ54TT.js} +3 -3
- package/dist/{chunk-PD3C7XUM.js → chunk-OC6Z6CQW.js} +2 -2
- package/dist/{chunk-L5EEEGE6.js → chunk-SHUHRHOT.js} +738 -677
- package/dist/chunk-SHUHRHOT.js.map +1 -0
- package/dist/index.js +17 -9
- package/dist/ipfs.js +4 -4
- package/dist/pubsub/snap-push.d.ts.map +1 -1
- package/dist/pubsub.js +4 -4
- package/dist/query/basic.d.ts +3 -3
- package/dist/query/basic.d.ts.map +1 -1
- package/dist/query/entity-collection.d.ts.map +1 -1
- package/dist/query/matchers.d.ts +12 -1
- package/dist/query/matchers.d.ts.map +1 -1
- package/dist/query.js +7 -5
- package/dist/retrieve.js +4 -4
- package/dist/thread/indexes.d.ts +3 -2
- package/dist/thread/indexes.d.ts.map +1 -1
- package/dist/thread.js +1 -1
- package/package.json +1 -1
- package/src/applog/applog-utils.ts +48 -4
- package/src/applog/datom-types.ts +22 -3
- package/src/applog/object-values.test.ts +106 -0
- package/src/pubsub/snap-push.ts +2 -1
- package/src/query/entity-collection.ts +2 -1
- package/src/query/matchers.ts +23 -1
- package/src/thread/basic.ts +2 -2
- package/src/thread/indexes.ts +15 -9
- package/dist/chunk-3WZVG277.js.map +0 -1
- package/dist/chunk-CPSDKFBG.js.map +0 -1
- package/dist/chunk-L5EEEGE6.js.map +0 -1
- /package/dist/{chunk-QZXKQCAY.js.map → chunk-3SUFNJEZ.js.map} +0 -0
- /package/dist/{chunk-3JZMOEOD.js.map → chunk-BLF5MAWU.js.map} +0 -0
- /package/dist/{chunk-J2FDHGOZ.js.map → chunk-HUIQ54TT.js.map} +0 -0
- /package/dist/{chunk-PD3C7XUM.js.map → chunk-OC6Z6CQW.js.map} +0 -0
package/dist/query/matchers.d.ts
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
import { DatomPart } from '../applog/datom-types.ts';
|
|
1
|
+
import { ApplogValue, DatomPart } from '../applog/datom-types.ts';
|
|
2
2
|
export declare function includes(str: string): (vl: DatomPart) => boolean;
|
|
3
3
|
export declare function includedIn(arr: string[]): (vl: DatomPart) => boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Set-membership matcher: matches when the field equals any of `vals`. Use this instead of a
|
|
6
|
+
* bare array in a query pattern — bare arrays are rejected by the matcher (a bare array is
|
|
7
|
+
* ambiguous now that `vl` can hold a literal array value).
|
|
8
|
+
*
|
|
9
|
+
* Returns a `Set`, which the matcher engine checks via `.has` (O(1)). Members must be
|
|
10
|
+
* primitives: `Set.has` is referential, so object/array members would silently never match —
|
|
11
|
+
* we throw rather than fail silently. For object membership, use a predicate matcher, e.g.
|
|
12
|
+
* `(v) => members.some(m => isEqual(v, m))`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function anyOf<T extends ApplogValue>(...vals: T[]): ReadonlySet<T>;
|
|
4
15
|
//# sourceMappingURL=matchers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"matchers.d.ts","sourceRoot":"","sources":["../../src/query/matchers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;
|
|
1
|
+
{"version":3,"file":"matchers.d.ts","sourceRoot":"","sources":["../../src/query/matchers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AAEjE,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,IAC3B,IAAI,SAAS,aACrB;AACD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,IAC/B,IAAI,SAAS,aACrB;AAED;;;;;;;;;GASG;AACH,wBAAgB,KAAK,CAAC,CAAC,SAAS,WAAW,EAAE,GAAG,IAAI,EAAE,CAAC,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC,CAUzE"}
|
package/dist/query.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
includedIn,
|
|
3
|
-
includes,
|
|
4
2
|
liveEntityCollection,
|
|
5
3
|
queryDivergencesByPrev
|
|
6
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-22WDFLXO.js";
|
|
7
5
|
import {
|
|
8
6
|
LiveQueryResult,
|
|
9
7
|
QueryNode,
|
|
@@ -46,17 +44,20 @@ import {
|
|
|
46
44
|
throwOnTimeout,
|
|
47
45
|
withTimeout,
|
|
48
46
|
withoutDeleted
|
|
49
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-3SUFNJEZ.js";
|
|
50
48
|
import {
|
|
51
49
|
SubscribableArrayImpl,
|
|
52
50
|
SubscribableImpl,
|
|
51
|
+
anyOf,
|
|
53
52
|
createDebugName,
|
|
54
53
|
createDebugNameObj,
|
|
54
|
+
includedIn,
|
|
55
|
+
includes,
|
|
55
56
|
isArrayInitEvent,
|
|
56
57
|
memoizedFn,
|
|
57
58
|
prettifyThreadName,
|
|
58
59
|
refCountedMemoizedFn
|
|
59
|
-
} from "./chunk-
|
|
60
|
+
} from "./chunk-SHUHRHOT.js";
|
|
60
61
|
import "./chunk-ZAADLBSB.js";
|
|
61
62
|
export {
|
|
62
63
|
LiveQueryResult,
|
|
@@ -65,6 +66,7 @@ export {
|
|
|
65
66
|
SubscribableArrayImpl,
|
|
66
67
|
SubscribableImpl,
|
|
67
68
|
agentsOfThread,
|
|
69
|
+
anyOf,
|
|
68
70
|
createDebugName,
|
|
69
71
|
createDebugNameObj,
|
|
70
72
|
createObjMapper,
|
package/dist/retrieve.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
updateThreadFromSnapshot,
|
|
3
3
|
withBlockCache
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-HUIQ54TT.js";
|
|
5
|
+
import "./chunk-6ALNRM3J.js";
|
|
6
6
|
import "./chunk-YDAKBU6Q.js";
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
7
|
+
import "./chunk-3SUFNJEZ.js";
|
|
8
|
+
import "./chunk-SHUHRHOT.js";
|
|
9
9
|
import "./chunk-ZAADLBSB.js";
|
|
10
10
|
export {
|
|
11
11
|
updateThreadFromSnapshot,
|
package/dist/thread/indexes.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type ValueKey } from '../applog/applog-utils.ts';
|
|
2
|
+
import { Applog, EntityID } from '../applog/datom-types.ts';
|
|
2
3
|
import { type Subscribable } from '../query/subscribable.ts';
|
|
3
4
|
import { Thread } from './basic.ts';
|
|
4
5
|
/**
|
|
@@ -23,7 +24,7 @@ export declare const applogsByEntity: (thread: Thread) => ReadonlyMap<EntityID,
|
|
|
23
24
|
* - Snapshot use: read `.value` — zero subscription overhead
|
|
24
25
|
* - Reactive use: `.subscribe()` activates the chain for incremental updates
|
|
25
26
|
*/
|
|
26
|
-
export declare const applogsByAttrValue: (thread: Thread, attr: string) => Subscribable<ReadonlyMap<
|
|
27
|
+
export declare const applogsByAttrValue: (thread: Thread, attr: string) => Subscribable<ReadonlyMap<ValueKey, readonly Applog[]>>;
|
|
27
28
|
export interface LinkEntry {
|
|
28
29
|
/** The link entity's own ID */
|
|
29
30
|
linkId: EntityID;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"indexes.d.ts","sourceRoot":"","sources":["../../src/thread/indexes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"indexes.d.ts","sourceRoot":"","sources":["../../src/thread/indexes.ts"],"names":[],"mappings":"AACA,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,2BAA2B,CAAA;AACnE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AAG3D,OAAO,EAAoB,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAC9E,OAAO,EAAe,MAAM,EAAE,MAAM,YAAY,CAAA;AAKhD;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,WAEM,MAAM,KAAG,WAAW,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAiCjF,CAAA;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB,WAGrB,MAAM,QACR,MAAM,KACV,YAAY,CAAC,WAAW,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC,CA4CxD,CAAA;AAID,MAAM,WAAW,SAAS;IACzB,+BAA+B;IAC/B,MAAM,EAAE,QAAQ,CAAA;IAChB,yCAAyC;IACzC,MAAM,EAAE,QAAQ,CAAA;IAChB,yCAAyC;IACzC,MAAM,EAAE,QAAQ,CAAA;CAChB;AAED,MAAM,WAAW,oBAAoB;IACpC,sEAAsE;IACtE,GAAG,EAAE,WAAW,CAAC,QAAQ,EAAE,SAAS,SAAS,EAAE,CAAC,CAAA;IAChD,sEAAsE;IACtE,GAAG,EAAE,WAAW,CAAC,QAAQ,EAAE,SAAS,SAAS,EAAE,CAAC,CAAA;CAChD;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,eAAe,WAGlB,MAAM,SACP,MAAM,SACN,MAAM,KACX,YAAY,CAAC,oBAAoB,CAiGpC,CAAA"}
|
package/dist/thread.js
CHANGED
package/package.json
CHANGED
|
@@ -24,6 +24,33 @@ export const objEqualByKeys = (keys: string[], objA: object, objB: object) => {
|
|
|
24
24
|
return isEqual(pick(objA, keys), pick(objB, keys))
|
|
25
25
|
}
|
|
26
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
|
+
|
|
27
54
|
/** Transitive total-order comparator over (ts, cid). Safe for `Array.sort`. */
|
|
28
55
|
export const compareApplogsByTs = (logA: Applog, logB: Applog, dir: 'asc' | 'desc' = 'asc') => {
|
|
29
56
|
const tsCmp = isoDateStrCompare(logA.ts, logB.ts, dir)
|
|
@@ -297,15 +324,22 @@ export function matchPartStatic(field: keyof Applog, patternPart: ValueOrMatcher
|
|
|
297
324
|
result = patternPart === atomPart // shortcut for most common use-case
|
|
298
325
|
} else if (typ === 'function') {
|
|
299
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
|
+
)
|
|
300
335
|
} else if (typeof (patternPart as any).has === 'function') {
|
|
301
336
|
result = (patternPart as Set<any>).has(atomPart)
|
|
302
|
-
} else if (Array.isArray(patternPart) && !Array.isArray(atomPart) /* ? how to handle array values */) {
|
|
303
|
-
result = patternPart.includes(atomPart)
|
|
304
337
|
} // if (field === 'at' && typ === 'string' && patternPart.endsWith('*')) {
|
|
305
338
|
// return typeof atomPart === 'string' && atomPart.startsWith(patternPart.slice(0, -1))
|
|
306
339
|
// }
|
|
307
340
|
else {
|
|
308
|
-
|
|
341
|
+
// object/array values compare by deep equality; primitives via isEqual's === fast-path
|
|
342
|
+
result = valueEq(patternPart as ApplogValue, atomPart)
|
|
309
343
|
}
|
|
310
344
|
} else {
|
|
311
345
|
result = patternPart === atomPart
|
|
@@ -334,7 +368,17 @@ export function matchPart(patternPart: ValueOrMatcher<ApplogValue>, atomPart: Ap
|
|
|
334
368
|
if (typeof patternPart === 'function') {
|
|
335
369
|
return patternPart(atomPart) ? context : null
|
|
336
370
|
}
|
|
337
|
-
|
|
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
|
|
338
382
|
}
|
|
339
383
|
|
|
340
384
|
/**
|
|
@@ -25,8 +25,14 @@ export type CidString = Tagged<string, CID>
|
|
|
25
25
|
export type IpnsString = Tagged<CidString, 'IPNS'>
|
|
26
26
|
export type AgentID = EntityID
|
|
27
27
|
export type Attribute = string
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
// JSON-serializable value space for a datom's `vl` field.
|
|
29
|
+
export type JsonPrimitive = string | boolean | number | null
|
|
30
|
+
export interface JsonObject {
|
|
31
|
+
[key: string]: JsonValue
|
|
32
|
+
}
|
|
33
|
+
export type JsonArray = JsonValue[]
|
|
34
|
+
export type JsonValue = JsonPrimitive | JsonObject | JsonArray
|
|
35
|
+
export type ApplogValue = JsonValue // TODO: use Tagged types
|
|
30
36
|
|
|
31
37
|
export interface Atom {
|
|
32
38
|
en: EntityID
|
|
@@ -120,10 +126,23 @@ FormatRegistry.Set('URL', (value) => !!value.match(isURL))
|
|
|
120
126
|
export const URL = Type.String({ format: 'URL' })
|
|
121
127
|
export type URL = Static<typeof URL>
|
|
122
128
|
|
|
129
|
+
// Recursive JSON value: primitives plus nested arrays/objects (mirrors ApplogValue / JsonValue).
|
|
130
|
+
export const JsonValueTB = Type.Recursive(This =>
|
|
131
|
+
Type.Union([
|
|
132
|
+
Type.String(),
|
|
133
|
+
Type.Number(),
|
|
134
|
+
Type.Boolean(),
|
|
135
|
+
Type.Null(),
|
|
136
|
+
Type.Array(This),
|
|
137
|
+
Type.Record(Type.String(), This),
|
|
138
|
+
])
|
|
139
|
+
)
|
|
140
|
+
export type JsonValueTB = Static<typeof JsonValueTB>
|
|
141
|
+
|
|
123
142
|
export const AppLogNoCidTB = Type.Object({
|
|
124
143
|
en: EntityID, // EntityID
|
|
125
144
|
at: Type.String(), // Attribute
|
|
126
|
-
vl:
|
|
145
|
+
vl: JsonValueTB, // ApplogValue (JSON-serializable: primitives, arrays, objects)
|
|
127
146
|
ts: Type.String(), // Timestamp
|
|
128
147
|
ag: Type.String(), // AgentHash
|
|
129
148
|
pv: Nullable(CIDTB), // CidString
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for JSON object/array support in a datom's `vl` field.
|
|
3
|
+
*
|
|
4
|
+
* Covers: schema validation, deep-equality dedup, value-index keying,
|
|
5
|
+
* matcher semantics (deep-eq literal match, anyOf membership, predicate
|
|
6
|
+
* for nested access) and the fail-loud behaviour for bare-array patterns.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, expect, it } from 'vitest'
|
|
9
|
+
import { isValidApplog } from './datom-types.ts'
|
|
10
|
+
import { matchPartStatic, valueEq, valueKey } from './applog-utils.ts'
|
|
11
|
+
import { anyOf } from '../query/matchers.ts'
|
|
12
|
+
import { finalizeApplogForInsert } from './applog-helpers.ts'
|
|
13
|
+
import { applogsByAttrValue } from '../thread/indexes.ts'
|
|
14
|
+
import { ThreadInMemory } from '../thread/writeable.ts'
|
|
15
|
+
import type { Applog, ApplogForInsert } from './datom-types.ts'
|
|
16
|
+
|
|
17
|
+
let tsCounter = 0
|
|
18
|
+
function makeLog(spec: Pick<ApplogForInsert, 'en' | 'at' | 'vl'> & Partial<Applog>): Applog {
|
|
19
|
+
tsCounter++
|
|
20
|
+
return finalizeApplogForInsert({
|
|
21
|
+
ts: new Date(1700000000000 + tsCounter * 1000).toISOString(),
|
|
22
|
+
pv: null,
|
|
23
|
+
ag: 'testAgent',
|
|
24
|
+
...spec,
|
|
25
|
+
} as ApplogForInsert, {})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe('object/array applog values', () => {
|
|
29
|
+
it('schema accepts nested objects and arrays as vl', () => {
|
|
30
|
+
const obj = makeLog({ en: 'e1', at: 'config', vl: { theme: 'dark', nested: { n: [1, 2, 3] } } })
|
|
31
|
+
const arr = makeLog({ en: 'e1', at: 'tags', vl: ['a', 'b', { x: 1 }] })
|
|
32
|
+
expect(isValidApplog(obj)).toBe(true)
|
|
33
|
+
expect(isValidApplog(arr)).toBe(true)
|
|
34
|
+
// primitives still valid
|
|
35
|
+
expect(isValidApplog(makeLog({ en: 'e1', at: 'name', vl: 'hi' }))).toBe(true)
|
|
36
|
+
expect(isValidApplog(makeLog({ en: 'e1', at: 'n', vl: null }))).toBe(true)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('valueEq compares object/array values by structure', () => {
|
|
40
|
+
expect(valueEq({ a: 1, b: 2 }, { b: 2, a: 1 })).toBe(true)
|
|
41
|
+
expect(valueEq([1, 2, 3], [1, 2, 3])).toBe(true)
|
|
42
|
+
expect(valueEq({ a: 1 }, { a: 2 })).toBe(false)
|
|
43
|
+
expect(valueEq('x', 'x')).toBe(true)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('valueKey canonicalizes objects (key-order independent) without colliding with strings', () => {
|
|
47
|
+
expect(valueKey({ a: 1, b: 2 })).toBe(valueKey({ b: 2, a: 1 }))
|
|
48
|
+
// a literal string equal to an object's serialization must NOT collide
|
|
49
|
+
expect(valueKey('{"a":1}')).not.toBe(valueKey({ a: 1 }))
|
|
50
|
+
// primitives pass through as their own value
|
|
51
|
+
expect(valueKey('hi')).toBe('hi')
|
|
52
|
+
expect(valueKey(42)).toBe(42)
|
|
53
|
+
expect(valueKey(null)).toBe(null)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('hasApplogWithDiffTs matches structurally-equal object values (deep, key-order independent)', () => {
|
|
57
|
+
const existing = makeLog({ en: 'e1', at: 'config', vl: { a: 1, b: 2 } })
|
|
58
|
+
const thread = ThreadInMemory.fromArray([existing], 'dedup')
|
|
59
|
+
// same en/at/ag, deep-equal vl with different key order → recognised as the same datom
|
|
60
|
+
expect(thread.hasApplogWithDiffTs({ en: 'e1', at: 'config', vl: { b: 2, a: 1 }, ag: 'testAgent' } as any)).toBeTruthy()
|
|
61
|
+
// a structurally different value is NOT matched
|
|
62
|
+
expect(thread.hasApplogWithDiffTs({ en: 'e1', at: 'config', vl: { a: 9 }, ag: 'testAgent' } as any)).toBeFalsy()
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('applogsByAttrValue groups structurally-equal object values into one bucket', () => {
|
|
66
|
+
const logs = [
|
|
67
|
+
makeLog({ en: 'e1', at: 'config', vl: { a: 1, b: 2 } }),
|
|
68
|
+
makeLog({ en: 'e2', at: 'config', vl: { b: 2, a: 1 } }),
|
|
69
|
+
makeLog({ en: 'e3', at: 'config', vl: { a: 9 } }),
|
|
70
|
+
]
|
|
71
|
+
const thread = ThreadInMemory.fromArray(logs, 'idx')
|
|
72
|
+
const index = applogsByAttrValue(thread, 'config').value
|
|
73
|
+
expect(index.get(valueKey({ a: 1, b: 2 }))).toHaveLength(2)
|
|
74
|
+
expect(index.get(valueKey({ a: 9 }))).toHaveLength(1)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('matchPartStatic matches a literal object value by deep equality', () => {
|
|
78
|
+
expect(matchPartStatic('vl', { a: 1, b: 2 }, { b: 2, a: 1 })).toBe(true)
|
|
79
|
+
expect(matchPartStatic('vl', { a: 1 }, { a: 2 })).toBe(false)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('matching a literal array value requires a predicate (a bare array throws)', () => {
|
|
83
|
+
// bare arrays are reserved/rejected, so an array value is matched via a predicate
|
|
84
|
+
expect(matchPartStatic('vl', (v: any) => valueEq(v, [1, 2, 3]), [1, 2, 3])).toBe(true)
|
|
85
|
+
expect(() => matchPartStatic('vl', [1, 2, 3] as any, [1, 2, 3])).toThrow(/anyOf/)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('anyOf(...) provides set-membership and matches via matchPartStatic', () => {
|
|
89
|
+
expect(matchPartStatic('at', anyOf('a', 'b', 'c'), 'b')).toBe(true)
|
|
90
|
+
expect(matchPartStatic('at', anyOf('a', 'b'), 'z')).toBe(false)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('a predicate matcher can reach into nested object values', () => {
|
|
94
|
+
const isDark = (v: any) => v?.theme === 'dark'
|
|
95
|
+
expect(matchPartStatic('vl', isDark, { theme: 'dark' })).toBe(true)
|
|
96
|
+
expect(matchPartStatic('vl', isDark, { theme: 'light' })).toBe(false)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('a bare array pattern fails loudly rather than matching ambiguously', () => {
|
|
100
|
+
expect(() => matchPartStatic('at', ['a', 'b'] as any, 'a')).toThrow(/anyOf/)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('anyOf rejects object/array members (would silently never match)', () => {
|
|
104
|
+
expect(() => anyOf({ a: 1 } as any)).toThrow(/object\/array/)
|
|
105
|
+
})
|
|
106
|
+
})
|
package/src/pubsub/snap-push.ts
CHANGED
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
import { BlockStoreish, DecodedCar, getDecodedBlock, makeCarBlob } from '../ipfs/car.ts'
|
|
16
16
|
import { encodeBlockOriginal, prepareForPub } from '../ipfs/ipfs-utils.ts'
|
|
17
17
|
import { lastWriteWins } from './../query/basic.ts'
|
|
18
|
+
import { anyOf } from '../query/matchers.ts'
|
|
18
19
|
import { ApplogsOrThread, getLogsFromThread, Thread } from '../thread.ts'
|
|
19
20
|
import { rollingFilter } from '../thread/filters.ts'
|
|
20
21
|
import { keepTruthy } from '../utils.ts'
|
|
@@ -146,7 +147,7 @@ export async function prepareSnapshotForPush(
|
|
|
146
147
|
const infoLogs = [
|
|
147
148
|
...rollingFilter(lastWriteWins(appThread), { // TODO: use static filter for performance
|
|
148
149
|
en: agent.ag,
|
|
149
|
-
at:
|
|
150
|
+
at: anyOf('agent/ecdh', 'agent/jwkd', 'agent/appAgent'),
|
|
150
151
|
}).applogs,
|
|
151
152
|
...(shareNameLog ? [shareNameLog] : []),
|
|
152
153
|
...(shareCounterLog ? [shareCounterLog] : []),
|
|
@@ -3,6 +3,7 @@ import { Applog, ApplogValue, DatalogQueryPattern, EntityID } from '../applog/da
|
|
|
3
3
|
import { isInitEvent, Thread } from '../thread/basic.ts'
|
|
4
4
|
import { makeFilter, rollingFilter } from '../thread/filters.ts'
|
|
5
5
|
import { resolveKeyMapper } from './basic.ts'
|
|
6
|
+
import { anyOf } from './matchers.ts'
|
|
6
7
|
import { memoizedFn } from './memoized.ts'
|
|
7
8
|
import { SubscribableImpl } from './subscribable.ts'
|
|
8
9
|
import type { Subscribable } from './subscribable.ts'
|
|
@@ -49,7 +50,7 @@ const _liveEntityCollection = memoizedFn('liveEntityCollection',
|
|
|
49
50
|
DEBUG('liveEntityCollection', discoveryPattern, liveAttributes)
|
|
50
51
|
const discoveryAttr = discoveryPattern.at as string
|
|
51
52
|
const allAttrs = new Set([discoveryAttr, ...liveAttributes])
|
|
52
|
-
const filtered = rollingFilter(thread, { at:
|
|
53
|
+
const filtered = rollingFilter(thread, { at: anyOf(...allAttrs) })
|
|
53
54
|
const isDiscoveryMatch = makeFilter(discoveryPattern)
|
|
54
55
|
const attrSet = new Set<string>(liveAttributes)
|
|
55
56
|
const key = resolveKeyMapper(opts)
|
package/src/query/matchers.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DatomPart } from '../applog/datom-types.ts'
|
|
1
|
+
import { ApplogValue, DatomPart } from '../applog/datom-types.ts'
|
|
2
2
|
|
|
3
3
|
export function includes(str: string) {
|
|
4
4
|
return (vl: DatomPart) => vl?.includes?.(str)
|
|
@@ -6,3 +6,25 @@ export function includes(str: string) {
|
|
|
6
6
|
export function includedIn(arr: string[]) {
|
|
7
7
|
return (vl: DatomPart) => arr?.includes?.(vl)
|
|
8
8
|
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Set-membership matcher: matches when the field equals any of `vals`. Use this instead of a
|
|
12
|
+
* bare array in a query pattern — bare arrays are rejected by the matcher (a bare array is
|
|
13
|
+
* ambiguous now that `vl` can hold a literal array value).
|
|
14
|
+
*
|
|
15
|
+
* Returns a `Set`, which the matcher engine checks via `.has` (O(1)). Members must be
|
|
16
|
+
* primitives: `Set.has` is referential, so object/array members would silently never match —
|
|
17
|
+
* we throw rather than fail silently. For object membership, use a predicate matcher, e.g.
|
|
18
|
+
* `(v) => members.some(m => isEqual(v, m))`.
|
|
19
|
+
*/
|
|
20
|
+
export function anyOf<T extends ApplogValue>(...vals: T[]): ReadonlySet<T> {
|
|
21
|
+
for (const v of vals) {
|
|
22
|
+
if (v !== null && typeof v === 'object') {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`[anyOf] object/array members are not supported (referential Set membership would silently never match).`
|
|
25
|
+
+ ` Use a predicate matcher instead, e.g. (v) => members.some(m => isEqual(v, m)).`,
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return new Set(vals)
|
|
30
|
+
}
|
package/src/thread/basic.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { createBLAKE3 } from 'hash-wasm'
|
|
|
3
3
|
import { pick } from 'lodash-es'
|
|
4
4
|
import { CID } from 'multiformats'
|
|
5
5
|
import { arraysContainSameElements } from '../applog/applog-utils.ts'
|
|
6
|
-
import { areApplogsEqual } from '../applog/applog-utils.ts'
|
|
6
|
+
import { areApplogsEqual, valueEq } from '../applog/applog-utils.ts'
|
|
7
7
|
import { type Applog, ApplogForInsert, CidString } from '../applog/datom-types.ts'
|
|
8
8
|
import type { SubscribableArray, ArrayEvent } from '../query/subscribable.ts'
|
|
9
9
|
import { isArrayInitEvent } from '../query/subscribable.ts'
|
|
@@ -152,7 +152,7 @@ export abstract class Thread implements SubscribableArray<Applog> {
|
|
|
152
152
|
return this.applogs.find(existing => (
|
|
153
153
|
existing.en === applog.en
|
|
154
154
|
&& existing.at === applog.at
|
|
155
|
-
&& existing.vl
|
|
155
|
+
&& valueEq(existing.vl, applog.vl)
|
|
156
156
|
&& existing.ag === applog.ag
|
|
157
157
|
))
|
|
158
158
|
}
|
package/src/thread/indexes.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Logger } from 'besonders-logger'
|
|
2
|
-
import {
|
|
2
|
+
import { valueKey, type ValueKey } from '../applog/applog-utils.ts'
|
|
3
|
+
import { Applog, EntityID } from '../applog/datom-types.ts'
|
|
4
|
+
import { anyOf } from '../query/matchers.ts'
|
|
3
5
|
import { memoizedFn } from '../query/memoized.ts'
|
|
4
6
|
import { SubscribableImpl, type Subscribable } from '../query/subscribable.ts'
|
|
5
7
|
import { isInitEvent, Thread } from './basic.ts'
|
|
@@ -70,23 +72,27 @@ export const applogsByAttrValue = memoizedFn(
|
|
|
70
72
|
function applogsByAttrValue(
|
|
71
73
|
thread: Thread,
|
|
72
74
|
attr: string,
|
|
73
|
-
): Subscribable<ReadonlyMap<
|
|
74
|
-
|
|
75
|
+
): Subscribable<ReadonlyMap<ValueKey, readonly Applog[]>> {
|
|
76
|
+
// Keyed by `valueKey(vl)`: object/array values can't be Map keys by identity, so they're
|
|
77
|
+
// canonicalized. Look up via `valueKey(value)`; for primitive values the key IS the value.
|
|
78
|
+
const map = new Map<ValueKey, Applog[]>()
|
|
75
79
|
|
|
76
80
|
const add = (log: Applog) => {
|
|
77
|
-
|
|
81
|
+
const key = valueKey(log.vl)
|
|
82
|
+
let arr = map.get(key)
|
|
78
83
|
if (!arr) {
|
|
79
84
|
arr = []
|
|
80
|
-
map.set(
|
|
85
|
+
map.set(key, arr)
|
|
81
86
|
}
|
|
82
87
|
arr.push(log)
|
|
83
88
|
}
|
|
84
89
|
const remove = (log: Applog) => {
|
|
85
|
-
const
|
|
90
|
+
const key = valueKey(log.vl)
|
|
91
|
+
const arr = map.get(key)
|
|
86
92
|
if (!arr) return
|
|
87
93
|
const idx = arr.indexOf(log)
|
|
88
94
|
if (idx >= 0) arr.splice(idx, 1)
|
|
89
|
-
if (arr.length === 0) map.delete(
|
|
95
|
+
if (arr.length === 0) map.delete(key)
|
|
90
96
|
}
|
|
91
97
|
|
|
92
98
|
const filtered = rollingFilter(thread, { at: attr })
|
|
@@ -94,7 +100,7 @@ export const applogsByAttrValue = memoizedFn(
|
|
|
94
100
|
// Initial build from current filtered state
|
|
95
101
|
for (const log of filtered.applogs) add(log)
|
|
96
102
|
|
|
97
|
-
const result = new SubscribableImpl<ReadonlyMap<
|
|
103
|
+
const result = new SubscribableImpl<ReadonlyMap<ValueKey, readonly Applog[]>>(
|
|
98
104
|
map,
|
|
99
105
|
() => filtered.subscribe(event => {
|
|
100
106
|
if (isInitEvent(event)) {
|
|
@@ -225,7 +231,7 @@ export const entityLinkIndex = memoizedFn(
|
|
|
225
231
|
for (const log of applogs) processLog(log)
|
|
226
232
|
}
|
|
227
233
|
|
|
228
|
-
const filtered = rollingFilter(thread, { at:
|
|
234
|
+
const filtered = rollingFilter(thread, { at: anyOf(attrA, attrB) })
|
|
229
235
|
|
|
230
236
|
// Initial build
|
|
231
237
|
buildFull(filtered.applogs)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/pubsub/snap-push.ts","../src/ipfs/car.ts"],"sourcesContent":["import * as dagJson from '@ipld/dag-json'\nimport { Logger } from 'besonders-logger'\nimport { CID } from 'multiformats/cid'\nimport stringify from 'safe-stable-stringify'\nimport { ensureTsPvAndFinalizeApplog } from '../applog/applog-helpers.ts'\nimport type {\n\tApplog,\n\tApplogArrayMaybeEncrypted,\n\tApplogArrayMaybeEncryptedRO,\n\tApplogArrayNoCIDMaybeEncryptedRO,\n\tApplogEnc,\n\tApplogEncNoCid,\n\tCidString,\n} from '../applog/datom-types.ts'\nimport { BlockStoreish, DecodedCar, getDecodedBlock, makeCarBlob } from '../ipfs/car.ts'\nimport { encodeBlockOriginal, prepareForPub } from '../ipfs/ipfs-utils.ts'\nimport { lastWriteWins } from './../query/basic.ts'\nimport { ApplogsOrThread, getLogsFromThread, Thread } from '../thread.ts'\nimport { rollingFilter } from '../thread/filters.ts'\nimport { keepTruthy } from '../utils.ts'\nimport type { AppAgent, IShare, SnapBlockChunks, SnapBlockLogs, SnapBlockLogsOrChunks } from './pubsub-types.ts'\n\nconst { WARN, LOG, DEBUG, VERBOSE, ERROR } = Logger.setup(Logger.INFO) // eslint-disable-line no-unused-vars\n\n// export const neverEncryptAttrs = [\n// \t'agent/jwkd',\n// \t'agent/appAgent',\n// \t'pub/encryptedFor',\n// \t'pub/sharedKey',\n// ]\n\n// export interface WovinPublicationInfo {\n// \tid: string\n// }\n\nexport async function prepareSnapshotForPush(\n\tagent: AppAgent,\n\tappThread: Thread,\n\tthreadToPublish: ApplogsOrThread,\n\tshare: IShare,\n\tprevSnapCID: CID | null,\n\tprevCounter: number | null,\n) {\n\tif (prevCounter !== null && !prevSnapCID) {\n\t\tthrow ERROR('[prepareSnapshotForPush] prevCounter provided without prevSnapCID')\n\t}\n\t// TODO prevent publish if there is no new info\n\tlet logsToPublish = getLogsFromThread(threadToPublish)\n\n\t// const logsFromLastPush = await getLogsFromPub(publication)\n\t// logsToPublish = logsToPublish.filter(eachLog => !logsFromLastPush.includes(eachLog.cid)) // TODO deep compare includes\n\t// const prevPushCIDs = [\n\t// \tpublication.lastCID,\n\t// \t//TODO add this one and update the publication data after push\n\t// ]\n\t// const includedLogCIDs = [\n\t// \t'full array of CIDS from all previous pushes'\n\t// ]\n\n\tDEBUG(`[preparePubForPush] Collected ${logsToPublish.length} logs :`, {\n\t\tlogsToPublish,\n\t\tthreadOrLogsCount: (threadToPublish as any).nameAndSizeUntracked || (`[${(threadToPublish as any).length}]`),\n\t})\n\n\tconst { sharedAgents, sharedKeyMap, sharedKey, pubCounter } = share ?? {}\n\n\tconst getExistingOrNewLog = (thread: Thread, share: IShare, ag: string, at: string, vl) => {\n\t\tlet logInQuestion = rollingFilter(lastWriteWins(thread), { en: share.id, at }).latestLog\n\t\tif (!logInQuestion && vl !== undefined) {\n\t\t\tlogInQuestion = ensureTsPvAndFinalizeApplog({ ag, en: share.id, at, vl }, thread)\n\t\t}\n\t\treturn logInQuestion // can be undefined if the passed vl is undefined and the log is not found\n\t}\n\tconst shareNameLog = getExistingOrNewLog(appThread, share, agent.ag, 'share/name', share.name)\n\n\t// ? using did as it is derived from the same ecdh in note3 and part of the minimal AppAgent type required here in wovin core\n\tconst shareCounterLog = getExistingOrNewLog(appThread, share, agent.ag, 'share/counter', `${agent.did}<::>${pubCounter}`)\n\t// ? discuss if this works to bind the counter to a specific derivation key - did is not necessarily derived from the same key by all lib users\n\n\tconst encryptApplog = async (applog: Applog, keyToUse: CryptoKey): Promise<Uint8Array> => {\n\t\tconst { log: eachLog, cid } = prepareForPub(applog) // without cid\n\t\tconst enc = new TextEncoder()\n\t\tconst stringified = stringify(eachLog)\n\t\tconst stringifiedEncodedAppLogPayload = enc.encode(stringified) // TODO: consider encodeToDagJson instead\n\t\tVERBOSE('[odd]', { eachLog, stringified, stringifiedEncodedAppLogPayload })\n\n\t\ttry {\n\t\t\t// @ts-expect-error\n\t\t\tconst encPayload = await agent.crypto?.aes.encrypt(stringifiedEncodedAppLogPayload, keyToUse, 'AES-GCM')\n\t\t\t// TODO get rid of odd down here\n\t\t\tVERBOSE('[odd] encrypted length:', stringifiedEncodedAppLogPayload.length, { encPayload })\n\t\t\treturn encPayload\n\t\t} catch (err) {\n\t\t\tthrow ERROR('FAILED TO ENC payload length:', stringifiedEncodedAppLogPayload.length, { err })\n\t\t}\n\n\t\t// const decrypted = await decryptWithAesSharedKey(encPayload, keyToUse, 'string')\n\t}\n\n\tlet maybeEncryptedApplogs: ApplogEncNoCid[] | readonly Applog[]\n\tconst encryptedApplogs = [] as { enc: Uint8Array }[]\n\tconst agentSharedKeyLogs = []\n\tif (sharedAgents) { // encrypt all Applogs\n\t\tif (!sharedKey || !sharedKeyMap) {\n\t\t\tthrow ERROR('sharedAgents but no Keys/Map', { sharedAgents, sharedKeyMap, sharedKey })\n\t\t}\n\t\tVERBOSE('encrypting', { sharedAgents, sharedKeyMap })\n\n\t\tfor (const [eachAgent, eachEncKey] of Array.from(sharedKeyMap.entries())) {\n\t\t\tVERBOSE('adding key', { eachAgent, eachEncKey })\n\t\t\tagentSharedKeyLogs.push({\n\t\t\t\tag: agent.ag,\n\t\t\t\ten: eachAgent,\n\t\t\t\tat: 'share/sharedKey',\n\t\t\t\tvl: eachEncKey, // these are encrypted with the derived key from the local agent private and remote agent public keys\n\t\t\t})\n\t\t}\n\t\t// const encryptedForLogs = await insertApplogsInAppDB(agentSharedKeyLogs)\n\t\t// DEBUG(`[publish] adding agentSharedKeyLogs:`, encryptedForLogs)\n\t\tconst CIDlist: { cid: CidString; encCID?: CidString }[] = []\n\t\tconst pubCIDmap: Record<CidString, typeof CIDlist> = {}\n\t\t// TODO ensure that all needed keys are in\n\t\tfor (const eachLog of logsToPublish) {\n\t\t\tVERBOSE('[crypto] encrypting ', { eachLog, sharedKey })\n\t\t\t// try {\n\t\t\tconst encPayload = await encryptApplog(eachLog, sharedKey)\n\t\t\tDEBUG('[crypto] encrypted ', { eachLog, encPayload, sharedKey })\n\t\t\t// } catch (err) {\n\t\t\t// \t// its already traced in encryptAndTestDecrypt\n\t\t\t// \t// continue\n\t\t\t// }\n\t\t\tencryptedApplogs.push({ enc: encPayload })\n\t\t}\n\t\tmaybeEncryptedApplogs = encryptedApplogs\n\t} else {\n\t\tmaybeEncryptedApplogs = logsToPublish // publish nonEncrypted\n\t}\n\n\tDEBUG('adding all agent info and shareAtoms', {\n\t\tshare,\n\t\tagent,\n\t\tlogsToPublish,\n\t\t// threadToPublish, - very verbose\n\t\tagentSharedKeyLogs,\n\t})\n\tconst infoLogs = [\n\t\t...rollingFilter(lastWriteWins(appThread), { // TODO: use static filter for performance\n\t\t\ten: agent.ag,\n\t\t\tat: ['agent/ecdh', 'agent/jwkd', 'agent/appAgent'],\n\t\t}).applogs,\n\t\t...(shareNameLog ? [shareNameLog] : []),\n\t\t...(shareCounterLog ? [shareCounterLog] : []),\n\t\t...agentSharedKeyLogs,\n\t]\n\tDEBUG(`[prepareSnapshotForPush] info logs:`, infoLogs)\n\tif (!infoLogs.find(({ at }) => at === 'agent/appAgent')) throw ERROR(`[prepareSnapshotForPush] appThread missing agent/appAgent log`)\n\n\tconst applogsToEncode = keepTruthy(maybeEncryptedApplogs)\n\tconst infologsToEncode = keepTruthy(infoLogs)\n\tif (!applogsToEncode.length) {\n\t\tthrow ERROR('no valid applogs', { agent, maybeEncryptedApplogs, infoLogs, applogsToEncode, infologsToEncode, prevSnapCID })\n\t}\n\tif (!infologsToEncode.length) {\n\t\tthrow ERROR('no valid infologs', { agent, maybeEncryptedApplogs, infoLogs, applogsToEncode, infologsToEncode, prevSnapCID })\n\t}\n\tconst encodedSnapshot = await encodeSnapshotAsCar(agent, applogsToEncode, infologsToEncode, prevSnapCID, prevCounter)\n\tDEBUG('inPrepareSnapshotForPush', { encodedSnapshot })\n\treturn encodedSnapshot\n}\n\n/**\n * @param applogs Encrypted or plain applogs\n * @returns Car file\n */\nexport async function encodeSnapshotAsCar(\n\tagent: AppAgent,\n\tapplogs: ApplogArrayNoCIDMaybeEncryptedRO,\n\tinfoLogs: readonly Applog[],\n\tprevSnapCID: CID | null,\n\tprevCounter: number | null,\n) {\n\tDEBUG(`[encodeSnapshotAsCar] encoding`, { agent, applogs, infoLogs })\n\tconst { cids: infoLogCids, encodedApplogs: encodedInfoLogs } = await encodeApplogsAsIPLD(infoLogs)\n\tconst { cids: applogCids, encodedApplogs } = await encodeApplogsAsIPLD(applogs)\n\tlet blocks = encodedApplogs.concat(encodedInfoLogs)\n\t// We need to wrap the array to get a CID\n\tconst infoLogsWrap = await encodeBlockOriginal({ logs: infoLogCids })\n\tblocks.push(infoLogsWrap)\n\tconst { rootCID: chunkRootCID, blocks: chunkBlocks } = await chunkApplogs(applogCids)\n\tblocks = blocks.concat(chunkBlocks) // (i) concat bc. https://stackoverflow.com/a/51860949\n\tconst infoSignature = await agent.sign(infoLogsWrap.cid.bytes)\n\tconst applogsSignature = await agent.sign(chunkRootCID.bytes)\n\tconst root = {\n\t\tinfo: infoLogsWrap.cid,\n\t\tapplogs: chunkRootCID,\n\t\tinfoSignature,\n\t\tapplogsSignature,\n\t\tprev: prevSnapCID,\n\t\tprevCounter: !prevSnapCID ? 0 : prevCounter !== null ? prevCounter + 1 : null,\n\t}\n\tDEBUG('[encodeSnapshotAsCar] encoding root', { root, logCids: applogCids, infoLogCids })\n\tconst encodedRoot = await encodeBlockOriginal(root)\n\tblocks.push(encodedRoot)\n\tDEBUG('[encodeSnapshotAsCar] => root', { encodedRoot })\n\n\treturn {\n\t\tcid: encodedRoot.cid,\n\t\tblob: await makeCarBlob(encodedRoot.cid, blocks), // TODO: create CarBuilder (incl .encodeAndAdd({...}))\n\t\tblocks,\n\t\tinfoLogCids,\n\t\tapplogCids,\n\t}\n}\n\n/** (i) IPFS has a block size limit of 1MB - which is about 15K CIDs */\nexport async function chunkApplogs(applogCids: CID<unknown, 297, 18, 1>[], size = 10000) {\n\tif (!applogCids.length) throw ERROR(`[chunkApplogs] called with empty array`)\n\tconst chunks = []\n\t// TODO: chunk by stable btree based on size or something like that\n\tfor (let i = 0; i < applogCids.length; i += size) {\n\t\tconst chunk = await encodeBlockOriginal({ logs: applogCids.slice(i, Math.min(i + applogCids.length, i + size)) })\n\t\tchunks.push(chunk)\n\t}\n\tif (chunks.length === 1) return { rootCID: chunks[0].cid, blocks: chunks }\n\tconst root = await encodeBlockOriginal({ chunks: chunks.map(chunk => chunk.cid) })\n\tconst blocks = [root, ...chunks]\n\tDEBUG(`[chunkApplogs] ${applogCids.length} logs chunked into ${chunks.length}`, { applogCids, root, blocks, chunks, dagJson })\n\treturn { rootCID: root.cid, blocks, chunks }\n}\nexport async function unchunkApplogsBlock(block: SnapBlockLogsOrChunks, blockStore: BlockStoreish): Promise<CID[]> {\n\tif (isSnapBlockChunks(block)) {\n\t\treturn (await Promise.all(\n\t\t\tblock.chunks.map(async (chunkCid) => {\n\t\t\t\tconst block = (await getDecodedBlock(blockStore, chunkCid)) as SnapBlockLogs\n\t\t\t\tif (!block.logs) throw ERROR(`Weird chunk`, block)\n\t\t\t\treturn block.logs\n\t\t\t}),\n\t\t)).flat()\n\t} else {\n\t\treturn block.logs\n\t}\n}\nexport function isSnapBlockChunks(block: SnapBlockLogsOrChunks): block is SnapBlockChunks {\n\treturn (block as any).chunks\n}\n/**\n * @param applogs Encrypted or plain applogs\n * @returns Car file\n */\nexport async function encodeSnapshotApplogsAsCar(\n\tapplogs: ApplogArrayMaybeEncryptedRO,\n) {\n\tconst encoded = await encodeApplogsAsIPLD(applogs)\n\tif (!encoded) throw ERROR('invalid applogs cannot continue', { applogs, encoded })\n\tconst { cids, encodedApplogs } = encoded\n\tconst root = { applogs: cids }\n\tconst encodedRoot = await encodeBlockOriginal(root)\n\tDEBUG('[encodeSnapshotApplogsAsCar] encoded root', { cids, encodedRoot })\n\n\treturn await makeCarBlob(encodedRoot.cid, [encodedRoot, ...encodedApplogs])\n}\n\nasync function encodeApplogsAsIPLD(applogs: ApplogArrayNoCIDMaybeEncryptedRO) {\n\tDEBUG({ applogs })\n\tconst validApplogs = applogs.filter(eachLog => !!eachLog)\n\tDEBUG({ validApplogs })\n\tif (!validApplogs.length) throw ERROR('no valid applogs')\n\tconst preppedLogs = validApplogs.map(log => prepareForPub(log as Applog).log)\n\tconst encodedApplogs = await Promise.all(preppedLogs.map(encodeBlockOriginal))\n\tDEBUG('[encodeApplogsAsIpld] encoded applogs', { preppedLogs, encodedApplogs })\n\n\tconst cids = encodedApplogs.map(b => {\n\t\tif (!b.cid) throw ERROR(`[publish] no cid for encoded log:`, b)\n\t\treturn b.cid\n\t})\n\treturn { cids, encodedApplogs }\n}\n","import { CarReader, CarWriter } from '@ipld/car'\nimport * as dagJson from '@ipld/dag-json'\nimport { Logger } from 'besonders-logger'\nimport { BlockView, CID } from 'multiformats'\nimport { sortApplogsByTs } from '../applog/applog-utils.ts'\nimport { Applog, ApplogArrayMaybeEncrypted, CidString } from '../applog/datom-types.ts'\nimport { unchunkApplogsBlock } from '../pubsub/snap-push.ts'\nimport { SnapBlockLogs, SnapBlockLogsOrChunks, SnapRootBlock } from '../pubsub/pubsub-types.ts'\nimport { areCidsEqual, containsCid } from './ipfs-utils.ts'\n\nconst { WARN, LOG, DEBUG, VERBOSE, ERROR } = Logger.setup(Logger.INFO) // eslint-disable-line no-unused-vars\n\nexport type CIDForCar = CID // Exclude<Parameters<(typeof CarWriter)['create']>[0], void>\nexport type BlockForCar = Parameters<CarWriter['put']>[0]\n\nexport interface BlockStoreish {\n\tget(cid: CID): PromiseLike<Uint8Array> // (i) not using decoded version to be similar to blockstore-idb\n}\n\nexport interface DecodedCar {\n\trootCID: CID\n\t// blocks: Map<CidString, any>\n\tblockStore: BlockStoreish\n}\n\n/** Warning: unsorted & maybe encrypted */\nexport async function decodePubFromCar(car: CarReader) {\n\tconst decoded = await getBlocksOfCar(car)\n\treturn await decodePubFromBlocks(decoded)\n}\n\nexport async function decodePubFromBlocks(\n\t{ rootCID, blockStore }: DecodedCar,\n\t_recursionTrace: CID[] = [], // DEPRECATED: kept for API compat, unused in iterative version\n\tstopAtCID?: CID // NEW: stop iteration when we hit this CID\n) {\n\tif (!rootCID || !blockStore) {\n\t\tthrow ERROR('Empty roots/blocks', { rootCID, blockStore })\n\t}\n\n\tlet allApplogs: ApplogArrayMaybeEncrypted = []\n\tlet firstInfo: { logs: CID[] } | null = null\n\tlet currentCID: CID | undefined = rootCID\n\tconst visited = new Set<string>() // Loop detection (replaces recursionTrace)\n\tlet applogsCID: CID | null = null // From first snapshot only\n\n\twhile (currentCID) {\n\t\tconst cidStr = currentCID.toString()\n\n\t\t// Loop detection\n\t\tif (visited.has(cidStr)) {\n\t\t\tthrow ERROR('[decodePubFromBlocks] pub chain has a loop', {\n\t\t\t\tcurrentCID: cidStr,\n\t\t\t\tvisited: [...visited]\n\t\t\t})\n\t\t}\n\t\tvisited.add(cidStr)\n\n\t\t// Decode current snapshot\n\t\tconst root = (await getDecodedBlock(blockStore, currentCID)) as SnapRootBlock\n\t\tVERBOSE(`[decodePubFromBlocks] root:`, cidStr, root, { blockStore })\n\t\tif (!root) {\n\t\t\tthrow ERROR('[decodePubFromBlocks] root not found in blockStore', { blockStore, currentCID: cidStr })\n\t\t}\n\n\t\t// Decode applogs for this snapshot\n\t\tlet pubLogsArray: CID[]\n\t\tif (root?.info) {\n\t\t\t// New(er) format\n\t\t\tif (!applogsCID) applogsCID = root.applogs // Save from first snapshot\n\t\t\tconst applogsBlock = (await getDecodedBlock(blockStore, root.applogs)) as SnapBlockLogsOrChunks\n\t\t\tpubLogsArray = await unchunkApplogsBlock(applogsBlock, blockStore)\n\t\t\t// Info only from first (most recent) snapshot\n\t\t\tif (!firstInfo) {\n\t\t\t\tfirstInfo = (await getDecodedBlock(blockStore, root.info)) as SnapBlockLogs\n\t\t\t\tDEBUG(`new format - infoLogs`, firstInfo.logs.map(l => ({ [l.toString()]: l })))\n\t\t\t}\n\t\t\t// TODO: verify signatures\n\t\t} else {\n\t\t\t// Old format\n\t\t\tpubLogsArray = root.applogs as any as CID[]\n\t\t}\n\n\t\tconst resolveLogFromCidLink = async (cidOrLink: CID) => {\n\t\t\tconst cid = cidOrLink\n\t\t\tconst applog = (await getDecodedBlock(blockStore, cid)) as Applog\n\t\t\tif (!applog) {\n\t\t\t\tERROR(`Could not find applog CID in pub blocks:`, cid.toString(), { cid, root, blockStore })\n\t\t\t\tthrow new Error(`Could not find applog CID in pub blocks: ${cid.toString()}`)\n\t\t\t}\n\t\t\tif ((applog.pv as any) instanceof CID) applog.pv = (applog.pv as any as CID).toV1().toString()\n\t\t\treturn {\n\t\t\t\t...applog,\n\t\t\t\tcid: cid.toV1().toString(),\n\t\t\t}\n\t\t}\n\n\t\tconst snapshotApplogs = await Promise.all(pubLogsArray.map(resolveLogFromCidLink))\n\t\tallApplogs = allApplogs.concat(snapshotApplogs)\n\n\t\t// Check if we should stop\n\t\tif (!root.prev) {\n\t\t\tbreak // End of chain\n\t\t}\n\t\tif (stopAtCID && areCidsEqual(root.prev, stopAtCID)) {\n\t\t\tDEBUG('[decodePubFromBlocks] stopping at stopAtCID:', stopAtCID.toString())\n\t\t\tbreak // Reached already-pulled snapshot\n\t\t}\n\n\t\t// Verify prev exists before continuing\n\t\tconst prevBytes = await blockStore.get(root.prev)\n\t\tif (!prevBytes) {\n\t\t\tthrow ERROR('[decodePubFromBlocks] prev snapshot missing from blockStore', {\n\t\t\t\tcurrentCID: cidStr,\n\t\t\t\tprev: root.prev.toString(),\n\t\t\t\tstopAtCID: stopAtCID?.toString(),\n\t\t\t\tvisited: [...visited]\n\t\t\t})\n\t\t}\n\n\t\tcurrentCID = root.prev // Move to previous snapshot\n\t}\n\n\tconst result = {\n\t\tcid: rootCID,\n\t\tinfo: firstInfo ? {\n\t\t\t...firstInfo,\n\t\t\tlogs: await Promise.all(firstInfo.logs.map(async (cidOrLink: CID) => {\n\t\t\t\tconst cid = cidOrLink\n\t\t\t\tconst applog = (await getDecodedBlock(blockStore, cid)) as Applog\n\t\t\t\tif (!applog) {\n\t\t\t\t\tERROR(`Could not find info log CID in pub blocks:`, cid.toString(), { cid, blockStore })\n\t\t\t\t\tthrow new Error(`Could not find info log CID in pub blocks: ${cid.toString()}`)\n\t\t\t\t}\n\t\t\t\tif ((applog.pv as any) instanceof CID) applog.pv = (applog.pv as any as CID).toV1().toString()\n\t\t\t\treturn {\n\t\t\t\t\t...applog,\n\t\t\t\t\tcid: cid.toV1().toString(),\n\t\t\t\t}\n\t\t\t})),\n\t\t} : null,\n\t\tapplogsCID,\n\t\tapplogs: allApplogs,\n\t}\n\tDEBUG('[decodePubFromBlocks] result:', result, { rootCID: rootCID.toString(), blockStore, applogs: allApplogs })\n\treturn result\n}\n\nexport async function getBlocksOfCar(car: CarReader) {\n\tconst rootsFromCar = await car.getRoots()\n\tconst roots = rootsFromCar.map(c => ((typeof c.toV1 === 'function') ? c : CID.decode(c.bytes)).toV1().toString() as CidString) // HACK\n\tconst blocks = new Map<CidString, any>()\n\tfor await (const { cid: cidFromCarblocks, bytes } of car.blocks()) {\n\t\tconst cid = (typeof cidFromCarblocks.toV1 === 'function') ? cidFromCarblocks : CID.decode(cidFromCarblocks.bytes)\n\t\tVERBOSE({ cidFromCarblocks, cid })\n\t\t// blocks.set(cid.toV1().toString(), dagJson.decode(bytes)) // HACK: tried using CID as map key, but because it's based on referential equality it's not working\n\t\tblocks.set(cid.toV1().toString(), bytes) // HACK: tried using CID as map key, but because it's based on referential equality it's not working\n\t}\n\tif (roots.length !== 1) {\n\t\tWARN('Unexpected roots count:', roots)\n\t}\n\treturn {\n\t\trootCID: CID.parse(roots[0]),\n\t\tblockStore: {\n\t\t\tget: (cid) => blocks.get(cid.toV1().toString()),\n\t\t},\n\t} satisfies DecodedCar\n}\nexport async function getDecodedBlock(blockStore: BlockStoreish, cid: CID) {\n\ttry {\n\t\tvar blob = await blockStore.get(cid)\n\t\tif (!blob) {\n\t\t\tWARN('returning null')\n\t\t\treturn null // I don't think this ever happens actually\n\t\t}\n\t} catch (err) {\n\t\tif ((err as any).message === 'Not Found') return null\n\t\tthrow err\n\t}\n\treturn dagJson.decode(blob)\n}\n\n// make out in the car... been a while but also sounds nice\nexport async function makeCarOut(roots: CIDForCar, blocks: BlockForCar[]) {\n\tconst { writer, out } = CarWriter.create(Array.isArray(roots) ? roots : [roots])\n\n\t// add the blocks to the CAR and close it\n\tVERBOSE(`Writing ${blocks.length} blocks to CAR`, { roots, blocks })\n\tblocks.forEach(b => writer.put(b))\n\twriter.close()\n\t// VERBOSE(`Wrote ${blocks.length} blocks to CAR`, writer)\n\treturn out\n} /** create a new CarWriter, with the encoded block as the root */\n\n// export async function makeCarReader(roots: CIDForCar, blocks: BlockForCar[]) {\n// \tconst out = await makeCarOut(roots, blocks)\n\n// \t// create a new CarReader we can hand to web3.storage.putCar\n// \tconst reader = await CarReader.fromIterable(out)\n// \tVERBOSE(`CAR reader`, reader)\n// \treturn reader\n// } /** create a new CarWriter, with the encoded block as the root */\n\nexport async function makeCarBlob(roots: CIDForCar, blocks: BlockForCar[]) {\n\tconst carOut = await makeCarOut(roots, blocks)\n\tconst chunks = []\n\tfor await (const chunk of carOut) {\n\t\tchunks.push(chunk)\n\t}\n\tconst blob = new Blob(chunks)\n\treturn blob\n}\nexport async function carFromBlob(blob: Blob | File): Promise<CarReader> {\n\treturn CarReader.fromBytes(new Uint8Array(await blob.arrayBuffer()))\n}\n\nfunction extractCids(value: unknown): CID[] {\n\tif (value instanceof CID) return [value]\n\tif (Array.isArray(value)) return value.flatMap(extractCids)\n\tif (value && typeof value === 'object') return Object.values(value).flatMap(extractCids)\n\treturn []\n}\n\nconst MAX_COLLECT_BLOCKS = 1_000_000\n\nexport async function collectDagBlocks(\n\tstartCID: CID,\n\tblockStore: BlockStoreish,\n): Promise<BlockForCar[]> {\n\tconst visited = new Set<string>()\n\tconst blocks: BlockForCar[] = []\n\tconst queue: CID[] = [startCID]\n\n\twhile (queue.length > 0) {\n\t\tif (blocks.length >= MAX_COLLECT_BLOCKS) {\n\t\t\tWARN(`[collectDagBlocks] hit ${MAX_COLLECT_BLOCKS} block limit, returning partial result`)\n\t\t\tbreak\n\t\t}\n\n\t\tconst cid = queue.shift()!\n\t\tconst cidStr = cid.toString()\n\t\tif (visited.has(cidStr)) continue\n\t\tvisited.add(cidStr)\n\n\t\tlet bytes: Uint8Array\n\t\ttry {\n\t\t\tbytes = await blockStore.get(cid)\n\t\t} catch {\n\t\t\tWARN(`[collectDagBlocks] block not found: ${cidStr}, stopping this branch`)\n\t\t\tcontinue\n\t\t}\n\t\tif (!bytes) {\n\t\t\tWARN(`[collectDagBlocks] block not found: ${cidStr}, stopping this branch`)\n\t\t\tcontinue\n\t\t}\n\n\t\tblocks.push({ cid, bytes })\n\n\t\tif (blocks.length % 1000 === 0) {\n\t\t\tLOG(`[collectDagBlocks] collected ${blocks.length} blocks...`)\n\t\t}\n\n\t\ttry {\n\t\t\tconst decoded = dagJson.decode(bytes)\n\t\t\tconst childCids = extractCids(decoded)\n\t\t\tfor (const child of childCids) {\n\t\t\t\tif (!visited.has(child.toString())) {\n\t\t\t\t\tqueue.push(child)\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Not dag-json — leaf block, no children to walk\n\t\t}\n\t}\n\n\tDEBUG(`[collectDagBlocks] collected ${blocks.length} blocks from ${startCID.toString()}`)\n\treturn blocks\n}\n\nexport function streamReaderToIterable(bodyReader: ReadableStreamDefaultReader<Uint8Array>): AsyncIterable<Uint8Array> {\n\treturn (async function*() {\n\t\twhile (true) {\n\t\t\tconst { done, value } = await bodyReader.read()\n\t\t\tVERBOSE(`[car] chunk`, { done, value })\n\t\t\tif (done) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tyield value\n\t\t}\n\t})()\n}\n"],"mappings":"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","names":["dagJson","Logger","WARN","LOG","DEBUG","VERBOSE","ERROR","Logger","share","dagJson","block"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/query/divergences.ts","../src/query/matchers.ts","../src/query/entity-collection.ts"],"sourcesContent":["import { Logger } from 'besonders-logger'\nimport stringify from 'safe-stable-stringify'\nimport { Applog, CidString } from '../applog/datom-types.ts'\nimport { createDebugName } from '../utils/debug-name.ts'\nimport { Thread } from '../thread/basic.ts'\nimport { ThreadInMemory } from '../thread/writeable.ts'\nimport { memoizedFn } from './memoized.ts'\n\nconst { WARN, LOG, DEBUG, VERBOSE, ERROR } = Logger.setup(Logger.INFO) // eslint-disable-line no-unused-vars\n\nexport interface DivergenceLeaf {\n\tlog: Applog\n\tthread: Thread\n}\n\nexport const queryDivergencesByPrev = memoizedFn('queryDivergencesByPrev', function queryConflictingByPrev(\n\tsourceThread: Thread,\n) {\n\tDEBUG(`queryDivergencesByPrev<${sourceThread.nameAndSizeUntracked}>`)\n\tif (sourceThread.filters.includes('lastWriteWins')) WARN(`queryDivergencesByPrev on thread lastWriteWins`, sourceThread)\n\n\tconst logsForNode = new Map<CidString, Applog[]>()\n\tconst leafs = new Set<CidString>()\n\tVERBOSE('all applogs:', sourceThread.applogs)\n\tfor (const log of sourceThread.applogs) {\n\t\tlet prevLogs\n\t\tif (log.pv) {\n\t\t\tprevLogs = log.pv && logsForNode.get(log.pv.toString())\n\t\t\tleafs.delete(log.pv.toString())\n\t\t}\n\t\tVERBOSE('traversing log', { log, prevLogs, leafs: Array.from(leafs) })\n\t\tlogsForNode.set(log.cid, prevLogs ? [...prevLogs, log] : [log])\n\t\tleafs.add(log.cid)\n\t}\n\tconst divergences = Array.from(leafs).map(leafID => {\n\t\tconst thread = new ThreadInMemory(\n\t\t\tcreateDebugName({\n\t\t\t\tcaller: 'DivergenceLeaf',\n\t\t\t\tthread: sourceThread,\n\t\t\t\tpattern: `leaf: ${leafID}`,\n\t\t\t}),\n\t\t\tlogsForNode.get(leafID),\n\t\t\tsourceThread.filters,\n\t\t\ttrue,\n\t\t)\n\t\treturn ({ log: thread.latestLog, thread })\n\t})\n\t// TODO: migrate to SubscribableArray for reactive updates\n\treturn divergences\n})\n","import { DatomPart } from '../applog/datom-types.ts'\n\nexport function includes(str: string) {\n\treturn (vl: DatomPart) => vl?.includes?.(str)\n}\nexport function includedIn(arr: string[]) {\n\treturn (vl: DatomPart) => arr?.includes?.(vl)\n}\n","import { Logger } from 'besonders-logger'\nimport { Applog, ApplogValue, DatalogQueryPattern, EntityID } from '../applog/datom-types.ts'\nimport { isInitEvent, Thread } from '../thread/basic.ts'\nimport { makeFilter, rollingFilter } from '../thread/filters.ts'\nimport { resolveKeyMapper } from './basic.ts'\nimport { memoizedFn } from './memoized.ts'\nimport { SubscribableImpl } from './subscribable.ts'\nimport type { Subscribable } from './subscribable.ts'\nimport type { StripExplicitPrefix, StripFirstPrefix } from './attr-helpers.ts'\n\nconst { DEBUG } = Logger.setup(Logger.INFO) // eslint-disable-line no-unused-vars\n\nexport function liveEntityCollection<A extends string>(\n\tthread: Thread, discoveryPattern: DatalogQueryPattern, liveAttributes: readonly A[],\n): Subscribable<ReadonlyMap<EntityID, Record<A, ApplogValue | null>>>\nexport function liveEntityCollection<A extends string>(\n\tthread: Thread, discoveryPattern: DatalogQueryPattern, liveAttributes: readonly A[],\n\topts: { stripAtPrefix: true },\n): Subscribable<ReadonlyMap<EntityID, Record<StripFirstPrefix<A>, ApplogValue | null>>>\nexport function liveEntityCollection<A extends string, P extends string>(\n\tthread: Thread, discoveryPattern: DatalogQueryPattern, liveAttributes: readonly A[],\n\topts: { stripAtPrefix: P },\n): Subscribable<ReadonlyMap<EntityID, Record<StripExplicitPrefix<A, P>, ApplogValue | null>>>\nexport function liveEntityCollection<A extends string>(\n\tthread: Thread, discoveryPattern: DatalogQueryPattern, liveAttributes: readonly A[],\n\topts: { mapKeys: (attr: A) => string },\n): Subscribable<ReadonlyMap<EntityID, Record<string, ApplogValue | null>>>\nexport function liveEntityCollection<A extends string>(\n\tthread: Thread, discoveryPattern: DatalogQueryPattern, liveAttributes: readonly A[],\n\topts: { stripAtPrefix?: true | string; mapKeys?: (attr: A) => string },\n): Subscribable<ReadonlyMap<EntityID, Record<string, ApplogValue | null>>>\nexport function liveEntityCollection<A extends string>(\n\tthread: Thread,\n\tdiscoveryPattern: DatalogQueryPattern,\n\tliveAttributes: readonly A[],\n\topts?: { stripAtPrefix?: true | string; mapKeys?: (attr: A) => string },\n): Subscribable<ReadonlyMap<EntityID, Record<string, ApplogValue | null>>> {\n\treturn _liveEntityCollection(thread, discoveryPattern, liveAttributes,\n\t\topts as { stripAtPrefix?: true | string; mapKeys?: (attr: string) => string })\n}\n\nconst _liveEntityCollection = memoizedFn('liveEntityCollection',\n\tfunction liveEntityCollection<A extends string>(\n\t\tthread: Thread,\n\t\tdiscoveryPattern: DatalogQueryPattern,\n\t\tliveAttributes: readonly A[],\n\t\topts?: { stripAtPrefix?: true | string; mapKeys?: (attr: string) => string },\n\t): Subscribable<ReadonlyMap<EntityID, Record<string, ApplogValue | null>>> {\n\t\tDEBUG('liveEntityCollection', discoveryPattern, liveAttributes)\n\t\tconst discoveryAttr = discoveryPattern.at as string\n\t\tconst allAttrs = new Set([discoveryAttr, ...liveAttributes])\n\t\tconst filtered = rollingFilter(thread, { at: [...allAttrs] })\n\t\tconst isDiscoveryMatch = makeFilter(discoveryPattern)\n\t\tconst attrSet = new Set<string>(liveAttributes)\n\t\tconst key = resolveKeyMapper(opts)\n\n\t\tconst map = new Map<EntityID, Record<string, ApplogValue | null>>()\n\n\t\tfunction makeRecord(entityId: EntityID): Record<string, ApplogValue | null> {\n\t\t\tconst record = {} as Record<string, ApplogValue | null>\n\t\t\tfor (const attr of liveAttributes) record[key(attr)] = null\n\t\t\t// Backfill from current filtered state\n\t\t\tfor (const log of filtered.applogs) {\n\t\t\t\tif (log.en === entityId && attrSet.has(log.at)) {\n\t\t\t\t\trecord[key(log.at)] = log.vl\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn record\n\t\t}\n\n\t\tfunction buildFull(applogs: readonly Applog[]) {\n\t\t\tmap.clear()\n\t\t\tfor (const log of isDiscoveryMatch(applogs)) {\n\t\t\t\tif (!map.has(log.en)) map.set(log.en, makeRecord(log.en))\n\t\t\t}\n\t\t}\n\n\t\tfunction addLog(log: Applog) {\n\t\t\t// Discovery match → ensure entity exists\n\t\t\tif (isDiscoveryMatch([log]).length > 0 && !map.has(log.en)) {\n\t\t\t\tmap.set(log.en, makeRecord(log.en))\n\t\t\t\treturn // makeRecord already backfilled attrs\n\t\t\t}\n\t\t\t// Attribute match → update value\n\t\t\tif (attrSet.has(log.at)) {\n\t\t\t\tconst record = map.get(log.en)\n\t\t\t\tif (record) record[key(log.at)] = log.vl\n\t\t\t}\n\t\t}\n\n\t\tfunction removeLog(log: Applog) {\n\t\t\tif (isDiscoveryMatch([log]).length > 0) {\n\t\t\t\t// Check if entity still has another discovery match\n\t\t\t\tconst stillDiscovered = filtered.applogs.some(\n\t\t\t\t\tl => l.en === log.en && isDiscoveryMatch([l]).length > 0,\n\t\t\t\t)\n\t\t\t\tif (!stillDiscovered) {\n\t\t\t\t\tmap.delete(log.en)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (attrSet.has(log.at)) {\n\t\t\t\tconst record = map.get(log.en)\n\t\t\t\tif (record) {\n\t\t\t\t\t// Find current value from remaining applogs\n\t\t\t\t\tconst current = filtered.applogs.find(l => l.en === log.en && l.at === log.at)\n\t\t\t\t\trecord[key(log.at)] = current?.vl ?? null\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Initial build\n\t\tbuildFull(filtered.applogs)\n\n\t\tconst result = new SubscribableImpl<ReadonlyMap<EntityID, Record<string, ApplogValue | null>>>(\n\t\t\tmap,\n\t\t\t() => filtered.subscribe((event) => {\n\t\t\t\tif (isInitEvent(event)) {\n\t\t\t\t\tbuildFull(event.init)\n\t\t\t\t} else {\n\t\t\t\t\t// Process removes before adds — LWW updates appear as remove+add in same delta\n\t\t\t\t\tif (event.removed) for (const log of event.removed) removeLog(log)\n\t\t\t\t\tfor (const log of event.added) addLog(log)\n\t\t\t\t}\n\t\t\t\tresult._set(map)\n\t\t\t}, 'derived'),\n\t\t\t{ equals: false },\n\t\t)\n\t\treturn result\n\t},\n)\n"],"mappings":"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","names":["Logger","DEBUG","Logger","liveEntityCollection"]}
|