Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update vertex method #3

Merged
merged 2 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions lib/graph/file.graph.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
import { UUID } from 'crypto';
import { FileGraphAbstract, IVertex } from '../interfaces';
import { FileGraphAbstract, IVertex, uuidType } from '../interfaces';
import { StorageFile } from './storage.file';
import { uuid } from '../utils';

class FileGraphIml<T extends IVertex> implements FileGraphAbstract {
class FileGraphIml implements FileGraphAbstract {
constructor(private readonly storageFile: StorageFile) {}

Check warning on line 6 in lib/graph/file.graph.ts

View workflow job for this annotation

GitHub Actions / linter (20.x)

'storageFile' is defined but never used

Check warning on line 6 in lib/graph/file.graph.ts

View workflow job for this annotation

GitHub Actions / linter (20.x)

'storageFile' is defined but never used
public async createVertex<T extends object>(model: T): Promise<UUID> {
public async createVertex<T extends object>(vertex: T): Promise<uuidType> {
const id = uuid();
await this.storageFile.appendFile({ id, ...model });
await this.storageFile.appendFile({ id, ...vertex });
return id;
}
public async findOne<T extends object>(
predicate: (model: T & IVertex) => boolean,
predicate: (vertex: T & IVertex) => boolean,

Check warning on line 13 in lib/graph/file.graph.ts

View workflow job for this annotation

GitHub Actions / linter (20.x)

'vertex' is defined but never used

Check warning on line 13 in lib/graph/file.graph.ts

View workflow job for this annotation

GitHub Actions / linter (20.x)

'vertex' is defined but never used
): Promise<(T & IVertex) | null> {
return this.storageFile.searchLine(predicate);
}
public updateVertex<T extends object>(
updater: (vertex: T & IVertex) => (T & IVertex) | object,

Check warning on line 18 in lib/graph/file.graph.ts

View workflow job for this annotation

GitHub Actions / linter (20.x)

'vertex' is defined but never used

Check warning on line 18 in lib/graph/file.graph.ts

View workflow job for this annotation

GitHub Actions / linter (20.x)

'vertex' is defined but never used
): Promise<boolean> {
return this.storageFile.updateLine(updater);
}
}

export const FileGraph = (path: `${string}.txt`) =>
Expand Down
95 changes: 81 additions & 14 deletions lib/graph/storage.file.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,97 @@
import { createReadStream } from 'fs';
import {
createReadStream,
createWriteStream,
promises as fsPromises,
} from 'fs';
import { appendFile } from 'fs/promises';
import readline from 'readline';

export class StorageFile {
constructor(private readonly path: string) {}

Check warning on line 10 in lib/graph/storage.file.ts

View workflow job for this annotation

GitHub Actions / linter (20.x)

'path' is defined but never used

Check warning on line 10 in lib/graph/storage.file.ts

View workflow job for this annotation

GitHub Actions / linter (20.x)

'path' is defined but never used
public async appendFile<T extends Record<string, any>>(model: T): Promise<T> {
const modelSer = this.serializer(model);
await appendFile(this.path, modelSer + '\n');
return model;
public async appendFile<T extends Record<string, any>>(
vertex: T,
): Promise<T> {
const vertexSer = this.serializer(vertex);
await appendFile(this.path, vertexSer + '\n');
return vertex;
}
public async searchLine<T extends object>(predicate: (model: T) => boolean) {
public async searchLine<T extends object>(
predicate: (vertex: T) => boolean,

Check warning on line 19 in lib/graph/storage.file.ts

View workflow job for this annotation

GitHub Actions / linter (20.x)

'vertex' is defined but never used

Check warning on line 19 in lib/graph/storage.file.ts

View workflow job for this annotation

GitHub Actions / linter (20.x)

'vertex' is defined but never used
): Promise<any> {
const fileStream = this.createLineStream();
const findVertex = await fileStream(async line => {
const vertex = this.deserializer(line) as T;
if (predicate(vertex)) return vertex;
});
return findVertex ?? null;
}
public async updateLine<T extends object>(
updater: (vertex: T) => object,

Check warning on line 29 in lib/graph/storage.file.ts

View workflow job for this annotation

GitHub Actions / linter (20.x)

'vertex' is defined but never used

Check warning on line 29 in lib/graph/storage.file.ts

View workflow job for this annotation

GitHub Actions / linter (20.x)

'vertex' is defined but never used
): Promise<boolean> {
const tempPath = `${this.path}.tmp`;
let updated = false;
const fileStream = this.createLineStream();
const tempStream = createWriteStream(tempPath, { encoding: 'utf8' });
await fileStream(async line => {
const vertex = this.deserializer(line) as T;
const updaterVertex = updater(vertex);
if (updaterVertex instanceof Object) {
tempStream.write(
this.serializer({ ...vertex, ...updaterVertex }) + '\n',
);
updated = true;
} else {
tempStream.write(line + '\n');
}
});
tempStream.end();
await new Promise<void>((resolve, reject) => {
tempStream.on('finish', () => {
fsPromises
.rename(tempPath, this.path)
.then(() => resolve())
.catch(reject);
});
tempStream.on('error', reject);
});

return updated;
}
private createLineStream() {
const fileStream = createReadStream(this.path, 'utf8');
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity,
});
for await (const line of rl)
if (line.trim()) {
const model = this.deserializer(line) as T;
if (predicate(model)) return model;
return async (fn: (line: string) => Promise<any>) => {

Check warning on line 66 in lib/graph/storage.file.ts

View workflow job for this annotation

GitHub Actions / linter (20.x)

'line' is defined but never used

Check warning on line 66 in lib/graph/storage.file.ts

View workflow job for this annotation

GitHub Actions / linter (20.x)

'line' is defined but never used
try {
for await (const line of rl) {
if (line.trim()) {
const result = await fn(line);
if (result) {
rl.close();
fileStream.destroy();
return result;
}
}
}
} catch (err) {
console.error(err);
} finally {
rl.close();
fileStream.destroy();
}

return null;
return null;
};
}
private serializer(model: Record<string, any>) {
return JSON.stringify(model);

private serializer(vertex: Record<string, any>) {
try {
return JSON.stringify(vertex);
} catch (error) {
console.error(error);
throw error;
}
}
private deserializer(data: string): Record<string, any> {
try {
Expand Down
33 changes: 22 additions & 11 deletions lib/interfaces/graph.interface.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,35 @@
import crypto from 'crypto';
import { IVertex } from './params.interface';
import { IVertex, uuidType } from './params.interface';

export abstract class FileGraphAbstract {
/**
* Creates a node in the graph.
* @template T - The type of the data model representing the node.
* @param {T} model - The data model for creating the node.
* @template T - The type of the data vertex representing the node.
* @param {T} vertex - The data vertex for creating the node.
* @returns {crypto.UUID} - The unique identifier of the created node.
*/
public abstract createVertex<T extends object>(
model: T,
): Promise<crypto.UUID>;
public abstract createVertex<T extends object>(vertex: T): Promise<uuidType>;

Check warning on line 10 in lib/interfaces/graph.interface.ts

View workflow job for this annotation

GitHub Actions / linter (20.x)

'vertex' is defined but never used

Check warning on line 10 in lib/interfaces/graph.interface.ts

View workflow job for this annotation

GitHub Actions / linter (20.x)

'vertex' is defined but never used

/**
* Searches for a model in the storage file that matches the given predicate.
* Searches for a vertex in the storage file that matches the given predicate.
*
* @param {function(T): boolean} predicate - A function that takes a model as input and returns `true` if the model matches the search criteria, otherwise `false`.
* @returns {Promise<T | null>} A promise that resolves to the found model if a match is found, or `null` if no match is found.
* @param {function(T): boolean} predicate - A function that takes a vertex as input and returns `true` if the vertex matches the search criteria, otherwise `false`.
* @returns {Promise<T | null>} A promise that resolves to the found vertex if a match is found, or `null` if no match is found.
*/
public abstract findOne<T extends object>(
predicate: (model: T | IVertex) => boolean,
predicate: (vertex: T | IVertex) => boolean,

Check warning on line 19 in lib/interfaces/graph.interface.ts

View workflow job for this annotation

GitHub Actions / linter (20.x)

'predicate' is defined but never used

Check warning on line 19 in lib/interfaces/graph.interface.ts

View workflow job for this annotation

GitHub Actions / linter (20.x)

'vertex' is defined but never used

Check warning on line 19 in lib/interfaces/graph.interface.ts

View workflow job for this annotation

GitHub Actions / linter (20.x)

'predicate' is defined but never used

Check warning on line 19 in lib/interfaces/graph.interface.ts

View workflow job for this annotation

GitHub Actions / linter (20.x)

'vertex' is defined but never used
): Promise<object | null>;
/**
* Updates a vertex record in the storage that matches the given condition.
*
* This method searches for a record that satisfies the condition specified by the `updater` function
* and updates it by replacing the old record with the new one. The `updater` function should return the updated
* vertex object if it matches the condition.
*
* @template T The type of the vertex object, which must conform to the {@link IVertex} interface.
* @param {Function} updater A function that takes a vertex object {@link T & IVertex} and returns an updated vertex object {@link T & IVertex}.
* @returns {Promise<boolean>} A promise that resolves to `true` if the record was successfully updated, and `false` if the record was not found or an error occurred.
*/
public abstract updateVertex<T extends object>(
updater: (vertex: T & IVertex) => (T & IVertex) | object,
): Promise<boolean>;
}
1 change: 1 addition & 0 deletions lib/interfaces/params.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ import { UUID } from 'crypto';
export interface IVertex {
id: UUID;
}
export type uuidType = UUID;
34 changes: 26 additions & 8 deletions test/graph.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,30 @@
import { FileGraph } from 'lib';
import assert from 'node:assert';
import { describe, it } from 'node:test';
import { FileGraph } from 'lib';

import test from 'node:test';
const graph = FileGraph('./data.txt');
test('create Vertex and findOne', async t => {
const model = { name: 'Dyor', age: new Date().toString() };
const id = await graph.createVertex(model);
const finModel = await graph.findOne<typeof model>(model => model.id == id);
console.log(finModel);
assert.deepStrictEqual({ id, ...model }, finModel);
const myVertex = { name: 'Diy0r', age: new Date().toString() };
let globId = '6c5febc1-161b-46c7-abf6-b20088fd410e';
describe('CRUD', t => {
it('create Vertex and findOne', async t => {
const createdVertexId = await graph.createVertex(myVertex);
const findVertex = await graph.findOne<typeof myVertex>(
vertex => vertex.id == createdVertexId,
);
assert.deepStrictEqual({ id: createdVertexId, ...myVertex }, findVertex);
globId = findVertex.id;
});
it('update Vertex', async t => {
const isUpdate = await graph.updateVertex<typeof myVertex>(model =>
model.id == globId ? { name: 'Dupo', id: '121212', sdsds: 'sdsd' } : null,
);
assert.equal(isUpdate, true);
});

it('check updated vertex name', async t => {
const findVertex = await graph.findOne<typeof myVertex>(
vertex => vertex.id == globId,
);
assert.deepStrictEqual(findVertex.name, 'Dupo');
});
});
Loading