Skip to content

Commit 22e1c8f

Browse files
committed
feat: generics
1 parent e55d757 commit 22e1c8f

File tree

3 files changed

+187
-66
lines changed

3 files changed

+187
-66
lines changed

infrastructure/evault-core/src/db/db.service.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@ describe("DbService (integration)", () => {
1616
let driver: Driver;
1717

1818
beforeAll(async () => {
19-
container = await new Neo4jContainer("neo4j:4.4.12").start();
19+
container = await new Neo4jContainer("neo4j:5.15").start();
2020

2121
const username = container.getUsername();
2222
const password = container.getPassword();
2323
const boltPort = container.getMappedPort(7687);
2424
const uri = `bolt://localhost:${boltPort}`;
2525

2626
driver = neo4j.driver(uri, neo4j.auth.basic(username, password));
27-
service = new DbService(uri, username, password);
27+
service = new DbService(driver);
2828
});
2929

3030
afterAll(async () => {

infrastructure/evault-core/src/db/db.service.ts

Lines changed: 126 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,33 @@
11
import neo4j, { Driver } from "neo4j-driver";
22
import { W3IDBuilder } from "w3id";
33
import { serializeValue, deserializeValue } from "./schema";
4-
5-
type MetaEnvelope = {
6-
ontology: string;
7-
payload: Record<string, any>;
8-
acl: string[];
9-
};
10-
11-
type Envelope = {
12-
id: string;
13-
value: any;
14-
ontology: string;
15-
valueType: string;
16-
};
17-
4+
import {
5+
MetaEnvelope,
6+
Envelope,
7+
MetaEnvelopeResult,
8+
StoreMetaEnvelopeResult,
9+
SearchMetaEnvelopesResult,
10+
GetAllEnvelopesResult,
11+
} from "./types";
12+
13+
/**
14+
* Service for managing meta-envelopes and their associated envelopes in Neo4j.
15+
* Provides functionality for storing, retrieving, searching, and updating data
16+
* with proper type handling and access control.
17+
*/
1818
export class DbService {
19-
private driver: Driver;
20-
21-
constructor(uri: string, user: string, password: string) {
22-
this.driver = neo4j.driver(uri, neo4j.auth.basic(user, password));
23-
}
24-
19+
/**
20+
* Creates a new instance of the DbService.
21+
* @param driver - The Neo4j driver instance
22+
*/
23+
constructor(private driver: Driver) {}
24+
25+
/**
26+
* Executes a Cypher query with the given parameters.
27+
* @param query - The Cypher query to execute
28+
* @param params - The parameters for the query
29+
* @returns The result of the query execution
30+
*/
2531
private async runQuery(query: string, params: Record<string, any>) {
2632
const session = this.driver.session();
2733
try {
@@ -31,7 +37,18 @@ export class DbService {
3137
}
3238
}
3339

34-
async storeMetaEnvelope(meta: Omit<MetaEnvelope, "id">, acl: string[]) {
40+
/**
41+
* Stores a new meta-envelope and its associated envelopes.
42+
* @param meta - The meta-envelope data (without ID)
43+
* @param acl - The access control list for the meta-envelope
44+
* @returns The created meta-envelope and its envelopes
45+
*/
46+
async storeMetaEnvelope<
47+
T extends Record<string, any> = Record<string, any>,
48+
>(
49+
meta: Omit<MetaEnvelope<T>, "id">,
50+
acl: string[],
51+
): Promise<StoreMetaEnvelopeResult<T>> {
3552
const w3id = await new W3IDBuilder().build();
3653

3754
const cypher: string[] = [
@@ -44,7 +61,7 @@ export class DbService {
4461
acl: acl,
4562
};
4663

47-
const createdEnvelopes: Envelope[] = [];
64+
const createdEnvelopes: Envelope<T[keyof T]>[] = [];
4865
let counter = 0;
4966

5067
for (const [key, value] of Object.entries(meta.payload)) {
@@ -74,7 +91,7 @@ export class DbService {
7491
createdEnvelopes.push({
7592
id: envelopeId,
7693
ontology: key,
77-
value: value,
94+
value: value as T[keyof T],
7895
valueType,
7996
});
8097

@@ -93,12 +110,19 @@ export class DbService {
93110
};
94111
}
95112

96-
async findMetaEnvelopesBySearchTerm(
113+
/**
114+
* Finds meta-envelopes containing the search term in any of their envelopes.
115+
* Returns all envelopes from the matched meta-envelopes.
116+
* @param ontology - The ontology to search within
117+
* @param searchTerm - The term to search for
118+
* @returns Array of matched meta-envelopes with their complete envelope sets
119+
*/
120+
async findMetaEnvelopesBySearchTerm<
121+
T extends Record<string, any> = Record<string, any>,
122+
>(
97123
ontology: string,
98124
searchTerm: string,
99-
): Promise<
100-
{ id: string; envelopes: any[]; parsed: Record<string, any> }[]
101-
> {
125+
): Promise<SearchMetaEnvelopesResult<T>> {
102126
const result = await this.runQuery(
103127
`
104128
MATCH (m:MetaEnvelope { ontology: $ontology })-[:LINKS_TO]->(e:Envelope)
@@ -111,43 +135,53 @@ export class DbService {
111135
END
112136
WITH m
113137
MATCH (m)-[:LINKS_TO]->(allEnvelopes:Envelope)
114-
RETURN m.id AS id, collect(allEnvelopes) AS envelopes
138+
RETURN m.id AS id, m.ontology AS ontology, m.acl AS acl, collect(allEnvelopes) AS envelopes
115139
`,
116140
{ ontology, term: searchTerm },
117141
);
118142

119-
return result.records.map((record) => {
120-
const envelopes = record.get("envelopes").map((node: any) => {
121-
const properties = node.properties;
122-
return {
123-
id: properties.id,
124-
ontology: properties.ontology,
125-
value: deserializeValue(
126-
properties.value,
127-
properties.valueType,
128-
),
129-
valueType: properties.valueType,
130-
};
131-
});
143+
return result.records.map((record): MetaEnvelopeResult<T> => {
144+
const envelopes = record
145+
.get("envelopes")
146+
.map((node: any): Envelope<T[keyof T]> => {
147+
const properties = node.properties;
148+
return {
149+
id: properties.id,
150+
ontology: properties.ontology,
151+
value: deserializeValue(
152+
properties.value,
153+
properties.valueType,
154+
) as T[keyof T],
155+
valueType: properties.valueType,
156+
};
157+
});
132158

133-
// Reconstruct the original payload structure
134159
const parsed = envelopes.reduce(
135-
(acc: Record<string, any>, envelope: Envelope) => {
136-
acc[envelope.ontology] = envelope.value;
160+
(acc: T, envelope: Envelope<T[keyof T]>) => {
161+
(acc as any)[envelope.ontology] = envelope.value;
137162
return acc;
138163
},
139-
{},
164+
{} as T,
140165
);
141166

142167
return {
143168
id: record.get("id"),
169+
ontology: record.get("ontology"),
170+
acl: record.get("acl"),
144171
envelopes,
145172
parsed,
146173
};
147174
});
148175
}
149176

150-
async findMetaEnvelopeById(id: string): Promise<any> {
177+
/**
178+
* Finds a meta-envelope by its ID.
179+
* @param id - The ID of the meta-envelope to find
180+
* @returns The meta-envelope with all its envelopes and parsed payload, or null if not found
181+
*/
182+
async findMetaEnvelopeById<
183+
T extends Record<string, any> = Record<string, any>,
184+
>(id: string): Promise<MetaEnvelopeResult<T> | null> {
151185
const result = await this.runQuery(
152186
`
153187
MATCH (m:MetaEnvelope { id: $id })-[:LINKS_TO]->(e:Envelope)
@@ -159,23 +193,27 @@ export class DbService {
159193
if (!result.records[0]) return null;
160194

161195
const record = result.records[0];
162-
const envelopes = record.get("envelopes").map((node: any) => {
163-
const properties = node.properties;
164-
return {
165-
id: properties.id,
166-
ontology: properties.ontology,
167-
value: deserializeValue(properties.value, properties.valueType),
168-
valueType: properties.valueType,
169-
};
170-
});
196+
const envelopes = record
197+
.get("envelopes")
198+
.map((node: any): Envelope<T[keyof T]> => {
199+
const properties = node.properties;
200+
return {
201+
id: properties.id,
202+
ontology: properties.ontology,
203+
value: deserializeValue(
204+
properties.value,
205+
properties.valueType,
206+
) as T[keyof T],
207+
valueType: properties.valueType,
208+
};
209+
});
171210

172-
// Reconstruct the original payload structure
173211
const parsed = envelopes.reduce(
174-
(acc: Record<string, any>, envelope: Envelope) => {
175-
acc[envelope.ontology] = envelope.value;
212+
(acc: T, envelope: Envelope<T[keyof T]>) => {
213+
(acc as any)[envelope.ontology] = envelope.value;
176214
return acc;
177215
},
178-
{},
216+
{} as T,
179217
);
180218

181219
return {
@@ -187,6 +225,11 @@ export class DbService {
187225
};
188226
}
189227

228+
/**
229+
* Finds all meta-envelope IDs for a given ontology.
230+
* @param ontology - The ontology to search for
231+
* @returns Array of meta-envelope IDs
232+
*/
190233
async findMetaEnvelopesByOntology(ontology: string): Promise<string[]> {
191234
const result = await this.runQuery(
192235
`
@@ -199,6 +242,10 @@ export class DbService {
199242
return result.records.map((r) => r.get("id"));
200243
}
201244

245+
/**
246+
* Deletes a meta-envelope and all its associated envelopes.
247+
* @param id - The ID of the meta-envelope to delete
248+
*/
202249
async deleteMetaEnvelope(id: string): Promise<void> {
203250
await this.runQuery(
204251
`
@@ -209,9 +256,14 @@ export class DbService {
209256
);
210257
}
211258

212-
async updateEnvelopeValue(
259+
/**
260+
* Updates the value of an envelope.
261+
* @param envelopeId - The ID of the envelope to update
262+
* @param newValue - The new value to set
263+
*/
264+
async updateEnvelopeValue<T = any>(
213265
envelopeId: string,
214-
newValue: any,
266+
newValue: T,
215267
): Promise<void> {
216268
const { value: storedValue, type: valueType } =
217269
serializeValue(newValue);
@@ -225,20 +277,30 @@ export class DbService {
225277
);
226278
}
227279

228-
async getAllEnvelopes(): Promise<Envelope[]> {
280+
/**
281+
* Retrieves all envelopes in the system.
282+
* @returns Array of all envelopes
283+
*/
284+
async getAllEnvelopes<T = any>(): Promise<GetAllEnvelopesResult<T>> {
229285
const result = await this.runQuery(`MATCH (e:Envelope) RETURN e`, {});
230-
return result.records.map((r) => {
286+
return result.records.map((r): Envelope<T> => {
231287
const node = r.get("e");
232288
const properties = node.properties;
233289
return {
234290
id: properties.id,
235291
ontology: properties.ontology,
236-
value: deserializeValue(properties.value, properties.valueType),
292+
value: deserializeValue(
293+
properties.value,
294+
properties.valueType,
295+
) as T,
237296
valueType: properties.valueType,
238297
};
239298
});
240299
}
241300

301+
/**
302+
* Closes the database connection.
303+
*/
242304
async close(): Promise<void> {
243305
await this.driver.close();
244306
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* Represents a meta-envelope that contains multiple envelopes of data.
3+
*/
4+
export type MetaEnvelope<T extends Record<string, any> = Record<string, any>> =
5+
{
6+
ontology: string;
7+
payload: T;
8+
acl: string[];
9+
};
10+
11+
/**
12+
* Represents an individual envelope containing a single piece of data.
13+
*/
14+
export type Envelope<T = any> = {
15+
id: string;
16+
value: T;
17+
ontology: string;
18+
valueType: string;
19+
};
20+
21+
/**
22+
* Base result type for all database operations that return a meta-envelope.
23+
* Includes the parsed payload structure reconstructed from the envelopes.
24+
*/
25+
export type MetaEnvelopeResult<
26+
T extends Record<string, any> = Record<string, any>,
27+
> = {
28+
id: string;
29+
ontology: string;
30+
acl: string[];
31+
envelopes: Envelope<T[keyof T]>[];
32+
parsed: T;
33+
};
34+
35+
/**
36+
* Result type for storing a new meta-envelope.
37+
*/
38+
export type StoreMetaEnvelopeResult<
39+
T extends Record<string, any> = Record<string, any>,
40+
> = {
41+
metaEnvelope: {
42+
id: string;
43+
ontology: string;
44+
acl: string[];
45+
};
46+
envelopes: Envelope<T[keyof T]>[];
47+
};
48+
49+
/**
50+
* Result type for searching meta-envelopes.
51+
*/
52+
export type SearchMetaEnvelopesResult<
53+
T extends Record<string, any> = Record<string, any>,
54+
> = MetaEnvelopeResult<T>[];
55+
56+
/**
57+
* Result type for retrieving all envelopes.
58+
*/
59+
export type GetAllEnvelopesResult<T = any> = Envelope<T>[];

0 commit comments

Comments
 (0)