Skip to content

Commit

Permalink
7.0.0-alpha.5: graph navigation and events improvements
Browse files Browse the repository at this point in the history
- Add go() method for graph navigation through contains
- Improve value unpacking with proper type handling
- Return unsubscribe function from on()
- Fix file sync tests cleanup
- Enhance event handling in syncJSONFile
- Bump version to 7.0.0-alpha.5
  • Loading branch information
ivansglazunov committed Nov 26, 2024
1 parent df8dde5 commit bc7b356
Show file tree
Hide file tree
Showing 9 changed files with 356 additions and 156 deletions.
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.4",
"version": "7.0.0-alpha.5",
"description": "Universal solution for working with any meaning",
"license": "Unlicense",
"main": "dist/cli.js",
Expand Down
62 changes: 35 additions & 27 deletions src/deep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,20 +254,20 @@ export class Deep {
deep.contains.from = deep.__from = deep.contains.One.new();
deep.contains.out = deep.__out = deep.contains.Many.new();
deep.contains.to = deep.__to = deep.contains.One.new();
deep.contains.out = deep.__out = deep.contains.Many.new();
deep.contains.in = deep.__in = deep.contains.Many.new();
deep.contains.value = deep.__value = deep.contains.One.new();
deep.contains.valued = deep.__valued = deep.contains.Many.new();

const Condition = deep.Condition = deep.contains.Condition = Relation.new();

deep.contains.eq = Condition.new();
deep.contains.neq = Condition.new();
deep.contains.gt = Condition.new();
deep.contains.lt = Condition.new();
deep.contains.gte = Condition.new();
deep.contains.lte = Condition.new();
deep.contains.in = Condition.new();
deep.contains.nin = Condition.new();
// deep.contains.eq = Condition.new();
// deep.contains.neq = Condition.new();
// deep.contains.gt = Condition.new();
// deep.contains.lt = Condition.new();
// deep.contains.gte = Condition.new();
// deep.contains.lte = Condition.new();
// deep.contains.in = Condition.new();
// deep.contains.nin = Condition.new();

const Logic = deep.Logic = deep.contains.Logic = Relation.new();

Expand Down Expand Up @@ -1499,9 +1499,7 @@ export class Deep {
for (let key in this.deep.contains.relations.call) {
if (input.hasOwnProperty(key)) {
const relation = this.deep.contains[key].new();
if (isDeep(input[key])) {
exp.call[key] = input[key];
} else if (isPlainObject(input[key])) {
if (isDeep(input[key]) || isPlainObject(input[key])) {
const nestedSelection = this.selection();
this.exp(input[key], nestedSelection);
exp.call[key] = nestedSelection;
Expand Down Expand Up @@ -1546,7 +1544,7 @@ export class Deep {
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 = new Set([relation.to[rels[relation.type.name].invert]]);
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 ?
Expand Down Expand Up @@ -1769,21 +1767,17 @@ export class Deep {
* @returns Deep instance with unpacked value
*/
valueUnpack(packedValue: Pack['values'][0]): Deep {
try {
let typeDeep;
for (const type of [this.Symbol, this.Undefined, this.Promise, this.Boolean,
this.String, this.Number, this.BigInt, this.Set,
this.WeakSet, this.Map, this.WeakMap, this.Array,
this.Object, this.Function]) {
if (type.name === packedValue.type) {
typeDeep = type;
break;
}
}
if (!typeDeep) {
throw new Error(` Unknown type ${packedValue.type} for value`);
let typeDeep;
for (const type of this.deep.contains.Value.typed) {
if (type.name === packedValue.type) {
typeDeep = type;
break;
}

}
if (!typeDeep) {
throw new Error(` Unknown type ${packedValue.type} for value`);
}
try {
switch(packedValue.type) {
case 'Symbol':
return this.wrap(Symbol());
Expand Down Expand Up @@ -1929,6 +1923,20 @@ export class Deep {

return this.select({ or });
}

/**
* Проходит по пути из строк через contains и возвращает найденный deep или undefined
* @param paths массив строк, представляющих путь через contains
* @returns найденный deep или undefined
*/
go(...paths: string[]): Deep | undefined {
let current: Deep = this;
for (const p of paths) {
if (!current?.contains?.[p]) return undefined;
current = current.contains[p];
}
return current;
}
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
export * from './deep.js';
export * from './on.js';
export * from './benchmark.js';
export * from './pckg.js';

const { argv } = process;
const args = argv.slice(2);
Expand Down
1 change: 1 addition & 0 deletions src/on.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export function On(customOn?: any): OnI {
let callbacks: any[] = []; // Deeps in future
const on = function (callback) {
callbacks.push(customOn ? customOn(...arguments) : callback);
return () => on.off(callback);
};
on.off = (callback) => {
callbacks = callbacks.filter(c => c != callback);
Expand Down
93 changes: 93 additions & 0 deletions src/pckg.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { Deep } from './deep.js';
import chokidar from 'chokidar';
import fs from 'fs/promises';

/**
* Synchronizes a JSON file with a Deep selection.
* When file changes, creates a new selection from the file content.
* When input selection changes, saves it to the file.
*
* @param inputSelection - The Deep selection to sync with file
* @param path - Path to the JSON file
* @returns The selection created from file content
*/
export async function syncJSONFile(inputSelection: Deep, path: string): Promise<Deep> {
const deep = inputSelection.deep;
let SyncJSONFile = deep.contains.SyncJSONFile;
if (!SyncJSONFile) {
SyncJSONFile = deep.contains.SyncJSONFile = deep.new();
const allJSONFiles = deep.select({ type: SyncJSONFile });
allJSONFiles.on((event) => {
if (event.name === 'kill') {

}
});
}
const syncJSONFile = SyncJSONFile.new();

// Create initial selection from file
try {
const fileContent = await fs.readFile(path, 'utf-8');
const data = JSON.parse(fileContent);
const fileSelection = inputSelection.unpack(data);
} catch (error) {
if (error.code === 'ENOENT') { // File doesn't exist
// Save initial selection to file
const data = await inputSelection.pack;
await fs.writeFile(path, JSON.stringify(data, null, 2));
} else {
console.error(`Error loading initial data from ${path}:`, error);
throw error;
}
}

// Watch for file changes
const watcher = chokidar.watch(path, {
persistent: true,
ignoreInitial: true,
});
syncJSONFile.from = deep.wrap(() => {
console.log('closing watcher');
watcher.close();
});

let isUpdatingFile = false; // Flag to prevent recursive updates

watcher.on('change', async () => {
if (isUpdatingFile) return; // Skip if we're currently updating the file

try {
const fileContent = await fs.readFile(path, 'utf-8');
const data = JSON.parse(fileContent);
const fileSelection = await inputSelection.unpack(data);
} catch (error) {
console.error(`Error processing file change for ${path}:`, error);
}
});

// Watch for input selection changes
const unsubscribe = inputSelection.on(async (event) => {
if (isUpdatingFile) return; // Skip if we're currently updating the file

try {
isUpdatingFile = true;
const data = await inputSelection.pack;
await fs.writeFile(path, JSON.stringify(data, null, 2));
} catch (error) {
console.error(`Error saving changes to ${path}:`, error);
} finally {
isUpdatingFile = false;
}
});
syncJSONFile.to = deep.wrap(unsubscribe);

// Add cleanup method to the returned selection
syncJSONFile.kill = () => {
syncJSONFile.from.call();
syncJSONFile.to.call();
};

return syncJSONFile;
}

export default syncJSONFile;
1 change: 1 addition & 0 deletions src/test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import './tests/core.js';
import './tests/sync.js';
import './tests/select.js';
// import './tests/langchain.js';
148 changes: 22 additions & 126 deletions src/tests/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -547,97 +547,6 @@ test('getById', () => {
assert.equal(deep.getById('entity-a', agent), undefined);
});

test('select type, type.type', () => {
const deep = new Deep();
const prevAllSize = deep.memory.all.size;
const A = deep.new();
const B = deep.new();
B.from = A; B.to = A;
const a = deep.new(); a.type = A;
const b = deep.new(); b.type = B;
b.from = a; b.to = a;
assert.equal(A.type, deep);
assert.equal(A.from, undefined);
assert.equal(A.to, undefined);
assert.equal(A.value, undefined);
assert.equal(B.type, deep);
assert.equal(B.from, A);
assert.equal(B.to, A);
assert.equal(B.value, undefined);
assert.equal(a.type, A);
assert.equal(a.from, undefined);
assert.equal(a.to, undefined);
assert.equal(a.value, undefined);
assert.equal(b.type, B);
assert.equal(b.from, a);
assert.equal(b.to, a);
assert.equal(b.value, undefined);
});

test('select from.type to.type', () => {
const deep = new Deep();
const A = deep.new();
const B = deep.new();
B.from = A; B.to = A;
const C = deep.new();
C.from = A; C.to = B;
assert.equal(deep.contains.type.typed.size, 0);
assert.equal(deep.contains.from.typed.size, 0);
assert.equal(deep.contains.to.typed.size, 0);
const selection = deep.select({ from: { type: A }, to: { type: B } });
assert.equal(deep.contains.type.typed.size, 2);
assert.equal(deep.contains.from.typed.size, 1);
assert.equal(deep.contains.to.typed.size, 1);
let result = selection.call();
let outerCounter = 0;
selection.on(() => {
outerCounter++;
result = selection.call();
});
const relations1 = selection.out;
assert.equal(relations1.size, 2);
let innerRelationsCounter = 0;
relations1.each(r => r.on(() => {
innerRelationsCounter++;
}))
assert.equal(outerCounter, 0);
assert.equal(innerRelationsCounter, 0);
assert.equal(result.size, 0);
const a = A.new();
assert.equal(innerRelationsCounter, 2);
assert.equal(outerCounter, 2);
assert.equal(result.size, 0);
const b = B.new();
assert.equal(innerRelationsCounter, 4);
assert.equal(outerCounter, 4);
assert.equal(result.size, 0);
const c = C.new();
assert.equal(innerRelationsCounter, 4);
assert.equal(result.size, 0);
c.from = a;
assert.equal(innerRelationsCounter, 5);
assert.equal(result.size, 0);
c.to = b;
assert.equal(innerRelationsCounter, 6);
assert.equal(result.size, 1);
assert.equal(outerCounter, 7);
});

test('select result changes', () => {
const deep = new Deep();
const A = deep.new();
const B = deep.new();
const b = B.new();
const selection = deep.select({ type: B });
assert.equal(selection.to.size, 1);
let outerCounter = 0;
selection.on(() => {
outerCounter++;
});
b.from = A; b.to = A;
assert.equal(outerCounter, 2);
});

test('not operator', () => {
const deep = new Deep();
const prevAllSize = deep.memory.all.size;
Expand Down Expand Up @@ -820,41 +729,6 @@ test('association events order', () => {
}
});

test.skip('benchmark', async () => {
const deep = new Deep();
const { Benchmark, Benchmarked } = benchmarks(deep);

// Function that creates new Deep instances
const testFn = () => {
const d = deep.new();
const d2 = deep.new();
d.from = d2;
return d;
};

// Run benchmark
const benchmarked = await Benchmark.call(testFn);

// Check that result is a Deep instance
assert(benchmarked.typeof(Benchmarked));

// Check that from contains the test function
assert(deep.isDeep(benchmarked.from));
assert.equal(benchmarked.from.value, testFn);

// Check that to contains benchmark results
assert(deep.isDeep(benchmarked.to));
const result = benchmarked.to.value;
assert(result.target);
assert(typeof result.hz === 'number');
assert(result.stats);
assert(result.times);

// Check that value contains hz
assert(typeof benchmarked.call, 'number');
assert.equal(benchmarked.value, result.hz);
});

test('inof and outof with multiple types', () => {
const deep = new Deep();

Expand Down Expand Up @@ -985,3 +859,25 @@ test('collection getters (types, froms, tos, typeds, outs, ins)', () => {
}
}
});

test('go method', () => {
const deep = new Deep();

// Создаем тестовую структуру
const a = deep.new();
deep.contains.a = a;
const b = deep.new();
a.contains.b = b;
const c = deep.new();
b.contains.c = c;

// Проверяем успешный поиск
assert.equal(deep.go('a', 'b', 'c'), c);
assert.equal(deep.go('a', 'b'), b);
assert.equal(deep.go('a'), a);

// Проверяем неуспешный поиск
assert.equal(deep.go('x'), undefined);
assert.equal(deep.go('a', 'x'), undefined);
assert.equal(deep.go('a', 'b', 'x'), undefined);
});
Loading

0 comments on commit bc7b356

Please sign in to comment.