Skip to content

Commit c4dc424

Browse files
committed
7.0.0-alpha.8
feat(events): implement value change tracking and difference tracking - Add value change event propagation through selections - Implement Difference and Patch for tracking changes - Add tests for difference tracking and value changes - Update documentation with event system examples
1 parent 709ed13 commit c4dc424

File tree

10 files changed

+342
-99
lines changed

10 files changed

+342
-99
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,6 @@ next-env.d.ts
4848
typedoc
4949
wiki
5050
.obsidian
51-
.models
51+
.models
52+
.deep
53+
.minds

.npmignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,5 @@ typedoc
5353
wiki
5454
.obsidian
5555
.models
56+
.deep
57+
.minds

README.md

Lines changed: 41 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ Core Functionality:
6969
- [x] Universal associative graph data structure
7070
- [x] Uniform interface for all data types
7171
- [x] Reactive event system
72+
- [x] Value change tracking
73+
- [x] Selection-based event propagation
74+
- [x] Difference tracking for collections
7275
- [x] Complex querying with logical operators
7376
- [ ] Event generation and applying
7477
- [ ] Export Selection to JSON and import as Selection
@@ -154,7 +157,7 @@ AI Integration:
154157

155158
The **Deep** class is the core of the system - it represents a universal agent capable of performing operations on any type of data. Each instance of Deep is an active agent that can interact with any other Deep instance or data type through a rich set of methods:
156159

157-
### Data Operations
160+
#### Data Operations
158161

159162
All methods work uniformly across different data types, treating single items as collections of one element where the item serves as both key and value. This approach allows for consistent data manipulation regardless of whether you're working with a single item or a collection.
160163

@@ -179,46 +182,49 @@ All methods work uniformly across different data types, treating single items as
179182
- `toString()` → string - Returns string representation
180183
- `valueOf()` → any - Returns primitive value if possible
181184

182-
### Operations
185+
#### Selection
183186

184-
#### Select
187+
Selections in Deep are powerful reactive queries that not only retrieve data but also track changes in real-time. They provide a comprehensive event system that propagates changes throughout the semantic graph:
185188

186-
<details>
187-
<summary>Examples</summary>
189+
- **Value Change Events**: Track modifications to node values within the selection
190+
- **Selection Events**: Monitor changes in selection contents (additions/removals)
191+
- **Difference Tracking**: Track detailed changes in selections over time
192+
- **Event Propagation**: Events automatically propagate through related selections
188193

194+
Example of tracking value changes in a selection:
189195
```typescript
190-
const A = deep.new();
191-
const B = deep.new();
192-
const C = deep.new();
193-
const X = deep.new();
196+
const Type1 = deep.new();
197+
const instance = Type1.new();
198+
199+
// Create and monitor a selection
200+
const selection = deep.select({ type: Type1 });
201+
selection.on((event) => {
202+
if (event.name === 'change' && event.field === 'value') {
203+
console.log('Value changed:', event);
204+
}
205+
});
194206

195-
const a = A.new();
196-
const b1 = B.new();
197-
b1.from = a;
198-
const c = C.new();
199-
c.from = a;
200-
201-
const x = X.new();
202-
const b2 = B.new();
203-
b2.from = x;
204-
205-
// Search by specific relations
206-
deep.select({ type: C }).to; // Deep<Set<[c]>>
207-
208-
// Search for links that referenced from B
209-
deep.select({
210-
out: { type: B }
211-
}).to; // Deep<Set<[a,x]>>
212-
213-
// Get only those links from which both B and C instances originate at least one
214-
deep.select({
215-
and: [
216-
{ out: { type: B } },
217-
{ out: { type: C } },
218-
]
219-
}).to; // Deep<Set<[a]>>
207+
// Changes to instance will trigger selection events
208+
instance.value = 'new value';
209+
```
210+
211+
Example of difference tracking:
212+
```typescript
213+
const selection = deep.select({ type: Type1 });
214+
const difference = deep.Difference.call(selection);
215+
216+
// Make some changes
217+
instance1.value = 'test1';
218+
instance2.kill();
219+
const instance3 = Type1.new();
220+
221+
// Get patch of changes
222+
const patch = difference.call();
223+
console.log('Events:', patch.call.events.length);
224+
console.log('Added:', patch.call.added.length);
225+
console.log('Updated:', patch.call.updated.length);
226+
console.log('Removed:', patch.call.removed.length);
220227
```
221-
</details>
222228

223229
- `select(expression)` → Selection - Creates a reactive selection of links based on expression
224230
- Expression is an object that can contain the following keys, where each key's value can be either another expression object or a Deep instance:
@@ -273,14 +279,6 @@ deep.select({
273279
complexQuery.call() // returns Deep instance with multiple results
274280
```
275281

276-
#### Selection
277-
278-
Selection is a special type of association that represents a dynamic query result. When created:
279-
- It executes immediately once and stores the result in `selection.to`
280-
- `selection.to` always contains the latest query result
281-
- Calling `selection.call()` re-executes the query and updates the results
282-
- The selection automatically updates when the underlying data changes
283-
284282
#### Modify (Coming Soon)
285283
- `insert({ type, from, to, value })`Deep - Creates new link
286284
- `update({ type?, from?, to?, value? })`Deep - Updates existing link

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"title": "deep",
44
"appId": "com.deep_foundation.deep.app",
55
"macCategory": "public.app-category.developer-tools",
6-
"version": "7.0.0-alpha.7",
6+
"version": "7.0.0-alpha.8",
77
"description": "Universal solution for working with any meaning",
88
"license": "Unlicense",
99
"main": "dist/cli.js",

src/deep.ts

Lines changed: 131 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ export class Memory {
134134
}
135135

136136
export interface Event {
137-
name: 'change' | 'new' | 'kill' | 'add' | 'remove';
137+
name: 'change' | 'new' | 'kill' | 'update' | 'add' | 'remove';
138138
deep: Deep;
139139
prev: {
140140
id?: string;
@@ -152,7 +152,14 @@ export interface Event {
152152
};
153153
}
154154

155-
interface Pack {
155+
export interface Patch {
156+
events: Event[];
157+
added: Event[];
158+
updated: Event[];
159+
removed: Event[];
160+
}
161+
162+
export interface Pack {
156163
deep: Array<{
157164
id: string;
158165
type?: string;
@@ -873,6 +880,63 @@ export class Deep {
873880
});
874881
_insert(deep.contains.Compatable, deep.contains.ObjectJoin, deep.contains.Object);
875882

883+
deep.Patch = deep.contains.Patch = deep.new();
884+
885+
let nextPatch = { events: [], added: [], updated: [], removed: [] } as Patch;
886+
887+
deep.Difference = deep.contains.Difference = deep.new((selection: Deep) => {
888+
const difference = deep.Difference.new(() => {
889+
difference.from.call();
890+
difference.to.kill();
891+
const newPatch = deep.Patch.new(nextPatch);
892+
difference.to = newPatch;
893+
nextPatch = { events: [], added: [], updated: [], removed: [] } as Patch;
894+
return newPatch;
895+
});
896+
897+
difference.from = selection;
898+
899+
const initialPatch = deep.Patch.new({
900+
events: [],
901+
added: selection.to.map(item => ({
902+
name: 'add',
903+
deep: item,
904+
prev: {},
905+
next: {
906+
id: item.id(),
907+
type: item.type,
908+
from: item.from,
909+
to: item.to,
910+
value: item.value
911+
}
912+
})),
913+
updated: [],
914+
removed: []
915+
} as Patch);
916+
difference.to = initialPatch;
917+
918+
selection.on((event: Event) => {
919+
if (['add', 'update', 'remove'].includes(event.name)) {
920+
const currentPatch = difference.to;
921+
nextPatch.events.push(event);
922+
923+
switch(event.name) {
924+
case 'add':
925+
nextPatch.added.push(event);
926+
break;
927+
case 'update':
928+
nextPatch.updated.push(event);
929+
break;
930+
case 'remove':
931+
nextPatch.removed.push(event);
932+
break;
933+
}
934+
}
935+
});
936+
937+
return difference;
938+
});
939+
876940
deep._events = true;
877941
}
878942
}
@@ -958,6 +1022,15 @@ export class Deep {
9581022
* Sets the value in this Deep instance. Value wrap into untyped Deep instance, if this Deep is typed. Values not duplicating inside this.deep.memory.
9591023
* @param value - Value to set
9601024
*/
1025+
/**
1026+
* Sets the value for this Deep instance. If the value is undefined, it will unset the current value.
1027+
* If the value is considered a valid value or a Deep instance containing a valid value, it will be set.
1028+
* Throws an error if the value is not valid or if trying to erase a value that is a Value instance.
1029+
* Emits a change event with name 'change' if the value is successfully changed.
1030+
*
1031+
* @param value - The value to set for this Deep instance
1032+
* @throws Error - If value is not valid or if attempting to erase a Value instance
1033+
*/
9611034
set value(value) {
9621035
const previous = this.call;
9631036
if (isUndefined(value)) {
@@ -974,9 +1047,23 @@ export class Deep {
9741047
this.deep.memory.values.set(this, value);
9751048
} else throw new Error(' Value must be isValue(value) or isValue(value.value) or isUndefined(value)');
9761049
const current = this.value;
977-
if (previous !== current) {
978-
const event = this._createChangeEvent('change', 'value', previous, current);
1050+
if (previous !== current?.call) {
1051+
const event = this._createChangeEvent('change', 'value', previous, current?.call);
9791052
this.on.emit(event);
1053+
1054+
// Propagate through related selections
1055+
let notifiedSelections = new Set();
1056+
for (let d of this.deep.memory.types.many(this.deep.__value)) {
1057+
if (d?.to?.to && d.to.type == this.deep.Selection && d.to.to.call.has(this)) {
1058+
d.emit(event);
1059+
notifiedSelections.add(d.to);
1060+
}
1061+
}
1062+
// Propagate through all selections
1063+
for (let selection of this.deep.memory.types.many(this.deep.Selection)) {
1064+
if (!notifiedSelections.has(notifiedSelections) && selection.to && !isArray(selection.to.call) && selection.to.call.has(this))
1065+
selection.emit(event);
1066+
}
9801067
}
9811068
}
9821069

@@ -1226,12 +1313,14 @@ export class Deep {
12261313
* Removes this Deep instance and all its references, also kill event emitting.
12271314
*/
12281315
kill() {
1229-
this.deep.memory.all.delete(this);
1230-
this.deep.memory.values.unset(this);
1231-
this.from = undefined;
1232-
this.to = undefined;
1233-
this.type = undefined;
1234-
if (this._on) this._on.kill();
1316+
if (this.deep.memory.all.has(this)) {
1317+
this.deep.memory.all.delete(this);
1318+
this.deep.memory.values.unset(this);
1319+
this.from = undefined;
1320+
this.to = undefined;
1321+
this.type = undefined;
1322+
if (this._on) this._on.kill();
1323+
}
12351324
}
12361325

12371326
/**
@@ -1532,6 +1621,7 @@ export class Deep {
15321621
*/
15331622
selection() {
15341623
const rels = this.deep.contains.relations.call;
1624+
let changes = [];
15351625
const selection = this.deep.Selection.new(() => {
15361626
const inRelations = selection.inof(this.deep.Relation);
15371627
const outRelations = selection.outof(this.deep.Relation);
@@ -1582,10 +1672,17 @@ export class Deep {
15821672
const oldSet = selection.to?.call || new Set();
15831673
const newSet = result.call;
15841674
this.emitDifference(oldSet, newSet, selection);
1675+
for (let change of changes) {
1676+
if (oldSet.has(change.deep) && newSet.has(change.deep)) selection.emit({ ...change, name: 'update' });
1677+
}
1678+
changes = [];
15851679
}
15861680
selection.to = result;
15871681
return selection.to;
15881682
});
1683+
selection.on(event => {
1684+
if (event.name === 'change') changes.push(event);
1685+
})
15891686
return selection;
15901687
}
15911688

@@ -1608,15 +1705,22 @@ export class Deep {
16081705
}
16091706

16101707
/**
1611-
* Emits difference events between two sets
1708+
* Emits difference events between two sets and returns the difference
16121709
* @param before - Set of items before change
16131710
* @param after - Set of items after change
16141711
* @param target - Deep instance to emit events on
1712+
* @returns Object containing added and removed items
16151713
*/
1616-
public emitDifference(before: Set<Deep>, after: Set<Deep>, target: Deep): void {
1714+
public emitDifference(before: Set<Deep>, after: Set<Deep>, target: Deep): { added: Deep[], removed: Deep[] } {
1715+
const difference = {
1716+
added: [] as Deep[],
1717+
removed: [] as Deep[],
1718+
};
1719+
16171720
// Find added elements (present in after, not in before)
16181721
for (const item of after) {
16191722
if (!before.has(item)) {
1723+
difference.added.push(item);
16201724
const event: Event = {
16211725
name: 'add',
16221726
deep: item,
@@ -1630,6 +1734,7 @@ export class Deep {
16301734
// Find removed elements (present in before, not in after)
16311735
for (const item of before) {
16321736
if (!after.has(item)) {
1737+
difference.removed.push(item);
16331738
const event: Event = {
16341739
name: 'remove',
16351740
deep: item,
@@ -1639,6 +1744,8 @@ export class Deep {
16391744
target.emit(event);
16401745
}
16411746
}
1747+
1748+
return difference;
16421749
}
16431750

16441751
/**
@@ -2036,18 +2143,22 @@ export class Contains {
20362143
},
20372144
set(target, key, value, receiver) {
20382145
if (key === 'deep') throw new Error(' Key "deep" is reserved in contains!');
2039-
if (!isDeep(value)) throw new Error(' Value must be Deep!');
20402146
let founded: Deep | void = undefined;
20412147
const Contain = target.deep.deep.Contain;
2042-
for (let contain of target.deep.out.call) {
2043-
if (contain.type === Contain && contain.value.value === key) {
2044-
founded = contain.to;
2148+
let contain;
2149+
for (let c of target.deep.out.call) {
2150+
if (c.type === Contain && c.call === key) {
2151+
contain = c;
2152+
break;
20452153
}
20462154
}
2047-
if (!founded) {
2048-
founded = value;
2049-
const c = target.deep.deep.Contain.new();
2050-
c.from = target.deep; c.to = founded; c.value = key;
2155+
if (isDeep(value)) {
2156+
if (!contain) contain = target.deep.deep.Contain.new();
2157+
contain.from = target.deep;
2158+
contain.to = value;
2159+
contain.value = key;
2160+
} else if(typeof (value) === 'undefined') {
2161+
if (contain) contain.kill();
20512162
}
20522163
return true;
20532164
},

0 commit comments

Comments
 (0)