Skip to content

Commit

Permalink
7.0.0-alpha.7: feat(events): add new event types and improve selection
Browse files Browse the repository at this point in the history
- Add 'add' and 'remove' event types to Event interface
- Update emitDifference to use new event types
- Improve selection documentation with clear expression object structure
- Add detailed examples of selection usage
- Translate all comments to English
- Update tests to use new event types
  • Loading branch information
ivansglazunov committed Nov 27, 2024
1 parent 7ae299e commit 709ed13
Show file tree
Hide file tree
Showing 8 changed files with 670 additions and 139 deletions.
3 changes: 2 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ yarn build
For publishing, it is necessary to:
1. Increase the version level, usually patch
2. Extract changes description from `git status` and `git diff` for all modified files
3. Format the message according to the following template:
3. Remove all NOT ENGLISH words from the all src/* files
4. Format the message according to the following template:
```
Version: type(scope): summary
Expand Down
79 changes: 63 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,23 +182,62 @@ All methods work uniformly across different data types, treating single items as
### Operations

#### Select

<details>
<summary>Examples</summary>

```typescript
const A = deep.new();
const B = deep.new();
const C = deep.new();
const X = deep.new();

const a = A.new();
const b1 = B.new();
b1.from = a;
const c = C.new();
c.from = a;

const x = X.new();
const b2 = B.new();
b2.from = x;

// Search by specific relations
deep.select({ type: C }).to; // Deep<Set<[c]>>

// Search for links that referenced from B
deep.select({
out: { type: B }
}).to; // Deep<Set<[a,x]>>

// Get only those links from which both B and C instances originate at least one
deep.select({
and: [
{ out: { type: B } },
{ out: { type: C } },
]
}).to; // Deep<Set<[a]>>
```
</details>

- `select(expression)` → Selection - Creates a reactive selection of links based on expression
- Expression can be:
- Direct (One to one) relations:
- [x] `type` - Get or set link type (returns Deep instance or undefined)
- [x] `from` - Get or set source node (returns Deep instance or undefined)
- [x] `to` - Get or set target node (returns Deep instance or undefined)
- [x] `value` - Get or set link value (returns Deep instance or JS primitive)
- Reverse (One to many) relations:
- [x] `typed` - Get links that have this as their type
- [x] `out` - Get links that have this as their from
- [x] `in` - Get links that have this as their to
- [x] `valued` - Get links that have this as their value
- Logical operators for filtering:
- [x] `and` - Array of expressions that all must match
- [x] `not` - Expression that must not match
- [x] `or` - Array of expressions where at least one must match
- Condition types (comparison operators):
- 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:
- Direct selectors:
- [x] `type` - Filter links by type
- [x] `from` - Filter links by source node
- [x] `to` - Filter links by target node
- [x] `id` - Filter links by id
- [x] `value` - Filter links by value
- Reverse selectors:
- [x] `typed` - Filter links where they have this as their type
- [x] `out` - Filter links where they have this as their from
- [x] `in` - Filter links where they have this as their to
- [x] `valued` - Filter links where they have this as their value
- Logic operators:
- [x] `not` - Exclude links matching the expression
- [x] `and` - Include links matching all expressions
- [x] `or` - Include links matching any expression (must be an array where each item can be an expression object or a Deep instance)
- Conditions (comparison operators):
- [ ] `eq` - Equal to
- [ ] `neq` - Not equal to
- [ ] `gt` - Greater than
Expand Down Expand Up @@ -234,6 +273,14 @@ All methods work uniformly across different data types, treating single items as
complexQuery.call() // returns Deep instance with multiple results
```

#### Selection

Selection is a special type of association that represents a dynamic query result. When created:
- It executes immediately once and stores the result in `selection.to`
- `selection.to` always contains the latest query result
- Calling `selection.call()` re-executes the query and updates the results
- The selection automatically updates when the underlying data changes

#### Modify (Coming Soon)
- `insert({ type, from, to, value })`Deep - Creates new link
- `update({ type?, from?, to?, value? })`Deep - Updates existing link
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"title": "deep",
"appId": "com.deep_foundation.deep.app",
"macCategory": "public.app-category.developer-tools",
"version": "7.0.0-alpha.6",
"version": "7.0.0-alpha.7",
"description": "Universal solution for working with any meaning",
"license": "Unlicense",
"main": "dist/cli.js",
Expand Down
168 changes: 108 additions & 60 deletions src/deep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,8 @@ export class Memory {
}
}

export interface DeepEvent {
name: 'change';
export interface Event {
name: 'change' | 'new' | 'kill' | 'add' | 'remove';
deep: Deep;
prev: {
id?: string;
Expand Down Expand Up @@ -890,8 +890,8 @@ export class Deep {
field?: 'id' | 'type' | 'from' | 'to' | 'value',
previousValue?: any,
currentValue?: any
): DeepEvent {
const event: DeepEvent = {
): Event {
const event: Event = {
name: 'change',
deep: this,
prev: {},
Expand All @@ -909,7 +909,7 @@ export class Deep {
/**
* Gets the deep.Id instance .to this Deep instance .from current this.deep agent. If no ID is set, one will be created.
* @param value - Optional ID value
* @param agent - Optional agent Deep instance
* @param agent - Optional agent Deep instance, default this.deep
* @returns ID value
*/
id(value?: string, agent: Deep = this.deep): string {
Expand Down Expand Up @@ -1483,7 +1483,7 @@ export class Deep {
relation.from = selection;
relation.to = nestedSelection;
relation.value = i;
relation.on((e) => selection.emit(e));
relation.on((e: Event) => selection.emit(e));
}
} else {
exp = this.deep.Exp.new({});
Expand All @@ -1493,7 +1493,7 @@ export class Deep {
const relation = this.deep.__id.new();
relation.from = selection;
relation.to = input.id;
relation.on((e) => selection.emit(e));
relation.on((e: Event) => selection.emit(e));
} else throw new Error(` Only Deep or string can be value in exp (id)!`);
}
for (let key in this.deep.contains.relations.call) {
Expand All @@ -1503,11 +1503,11 @@ export class Deep {
const nestedSelection = this.selection();
this.exp(input[key], nestedSelection);
exp.call[key] = nestedSelection;
nestedSelection.on((e) => relation.emit(e));
nestedSelection.on((e: Event) => relation.emit(e));
} else throw new Error(` Only Deep or plain objects Exp can be value in exp (${key})!`);
relation.from = selection;
relation.to = exp.call[key]; // nestedSelection
relation.on((e) => selection.emit(e));
relation.on((e: Event) => selection.emit(e));
}
}
for (let logic of this.deep.Logic.typed) {
Expand All @@ -1519,7 +1519,7 @@ export class Deep {
exp.call[logic.name] = nestedSelection;
relation.from = selection;
relation.to = exp.call[logic.name];
relation.on((e) => selection.emit(e));
relation.on((e: Event) => selection.emit(e));
}
}
}
Expand All @@ -1533,52 +1533,56 @@ export class Deep {
selection() {
const rels = this.deep.contains.relations.call;
const selection = this.deep.Selection.new(() => {
const relations = selection.out;
const inRelations = selection.inof(this.deep.Relation);
const outRelations = selection.outof(this.deep.Relation);
let set;
for (let relation of relations) {
if (relation.typeof(this.deep.Relation)) {
if (relation.typeof(this.deep.__id)) {
if (isDeep(relation.to)) {
set = set ? set.intersection(new Set([relation.to])) : new Set([relation.to]);
} else if (isString(relation.to.call)) {
throw new Error(' Sorry not relized yet.');
} else throw new Error(' Only Deep and string can be .id');
} else if (relation.typeof(this.deep.Many)) {
const nextSet = relation.to.call()[`${rels[relation.type.name].invert}s`].call;
set = set ? set.intersection(nextSet) : nextSet;
} else if (relation.typeof(this.deep.One)) {
const nextSet = relation.to.type === this.deep.Selection ?
relation.to.call().reduce((result, d) => result.union(d[rels[relation.type.name].invert].call), new Set()) :
relation.to[rels[relation.type.name].invert].call;
set = set ? set.intersection(nextSet) : nextSet;
} else if (relation.typeof(this.deep.Condition)) {

} else if (relation.typeof(this.deep.Logic)) {
if (relation.typeof(this.deep.contains.not)) {
const currentSet = set || this.deep.Everything.call;
const notSet = relation.to.call().call;
set = currentSet.difference(notSet);
} else if (relation.typeof(this.deep.contains.and)) {
const currentSet = set || this.deep.Everything.call;
const arrayOfSets = relation.to.call();
set = arrayOfSets.reduce((result, set) => {
return result.intersection(set.call);
}, currentSet);
} else if (relation.typeof(this.deep.contains.or)) {
const arrayOfSets = relation.to.call();
set = arrayOfSets.reduce((result, item) => {
const itemSet = item.call;
return result ? new Set([...result, ...itemSet]) : itemSet;
}, set);
}
} else if (relation.typeof(this.deep.Order)) {
const nextSet = relation.to.call();
set = set ? (set.push(nextSet), set) : [nextSet];
for (let relation of outRelations) {
if (relation.typeof(this.deep.__id)) {
if (isDeep(relation.to)) {
set = set ? set.intersection(new Set([relation.to])) : new Set([relation.to]);
} else if (isString(relation.to.call)) {
throw new Error(' Sorry not relized yet.');
} else throw new Error(' Only Deep and string can be .id');
} else if (relation.typeof(this.deep.Many)) {
const nextSet = relation.to.call()[`${rels[relation.type.name].invert}s`].call;
set = set ? set.intersection(nextSet) : nextSet;
} else if (relation.typeof(this.deep.One)) {
const nextSet = relation.to.type === this.deep.Selection ?
relation.to.call().reduce((result, d) => result.union(d[rels[relation.type.name].invert].call), new Set()) :
relation.to[rels[relation.type.name].invert].call;
set = set ? set.intersection(nextSet) : nextSet;
} else if (relation.typeof(this.deep.Condition)) {

} else if (relation.typeof(this.deep.Logic)) {
if (relation.typeof(this.deep.contains.not)) {
const currentSet = set || this.deep.Everything.call;
const notSet = relation.to.call().call;
set = currentSet.difference(notSet);
} else if (relation.typeof(this.deep.contains.and)) {
const currentSet = set || this.deep.Everything.call;
const arrayOfSets = relation.to.call();
set = arrayOfSets.reduce((result, set) => {
return result.intersection(set.call);
}, currentSet);
} else if (relation.typeof(this.deep.contains.or)) {
const arrayOfSets = relation.to.call();
set = arrayOfSets.reduce((result, item) => {
const itemSet = item.call;
return result ? new Set([...result, ...itemSet]) : itemSet;
}, set);
}
} else if (relation.typeof(this.deep.Order)) {
const nextSet = relation.to.call();
set = set ? (set.push(nextSet), set) : [nextSet];
}
}
if (!set) set = this.deep.Everything.call;
const result = this.wrap(set);
if (!inRelations.size) {
const oldSet = selection.to?.call || new Set();
const newSet = result.call;
this.emitDifference(oldSet, newSet, selection);
}
selection.to = result;
return selection.to;
});
Expand All @@ -1603,6 +1607,40 @@ export class Deep {
return selection;
}

/**
* Emits difference events between two sets
* @param before - Set of items before change
* @param after - Set of items after change
* @param target - Deep instance to emit events on
*/
public emitDifference(before: Set<Deep>, after: Set<Deep>, target: Deep): void {
// Find added elements (present in after, not in before)
for (const item of after) {
if (!before.has(item)) {
const event: Event = {
name: 'add',
deep: item,
prev: { value: null },
next: { value: item }
};
target.emit(event);
}
}

// Find removed elements (present in before, not in after)
for (const item of before) {
if (!after.has(item)) {
const event: Event = {
name: 'remove',
deep: item,
prev: { value: item },
next: { value: null }
};
target.emit(event);
}
}
}

/**
* Gets, or creates if not exists the event emitter for this Deep instance
* @returns Event emitter instance
Expand All @@ -1616,8 +1654,14 @@ export class Deep {
* Emits an event from this Deep instance
* @param args - Event arguments
*/
emit(...args) {
if (this._on) this._on.emit(...args);
emit(...args: any[]): void {
if (this._on) {
const event = args[0];
if (typeof event === 'object' && !event.deep) {
event.deep = this;
}
this._on.emit(...args);
}
}

/**
Expand All @@ -1628,7 +1672,7 @@ export class Deep {
inof(type: Deep): Deep {
const result = new Set<Deep>();
for (const link of this.in.call) {
if (link.type === type) {
if (link.typeof(type)) {
result.add(link);
}
}
Expand All @@ -1643,7 +1687,7 @@ export class Deep {
outof(type: Deep): Deep {
const result = new Set<Deep>();
for (const link of this.out.call) {
if (link.type === type) {
if (link.typeof(type)) {
result.add(link);
}
}
Expand Down Expand Up @@ -1925,9 +1969,9 @@ export class Deep {
}

/**
* Проходит по пути из строк через contains и возвращает найденный deep или undefined
* @param paths массив строк, представляющих путь через contains
* @returns найденный deep или undefined
* Proceeds along the path through strings through contains and returns the found deep or undefined
* @param paths array of strings representing the path through contains
* @returns found deep or undefined
*/
go(...paths: string[]): Deep | undefined {
let current: Deep = this;
Expand All @@ -1939,8 +1983,8 @@ export class Deep {
}

/**
* Возвращает путь к текущему deep через входящие Contain связи
* @returns массив строк - путь через contains
* Returns the path to the current deep through incoming Contain connections
* @returns array of strings - path through contains
*/
path(): string[] {
const result: string[] = [];
Expand All @@ -1954,7 +1998,7 @@ export class Deep {
const contain = contains.first;
if (!contain) break;

// Находим ключ в contains, по которому хранится текущий deep
// Find the key in contains where the current deep is stored
const from = contain.from;
if (!from) break;

Expand Down Expand Up @@ -2013,3 +2057,7 @@ export class Contains {
this.deep = deep;
}
}

// Global instance of Deep
export const deep = new Deep();
export default deep;
Loading

0 comments on commit 709ed13

Please sign in to comment.