Skip to content

Commit 18e6ada

Browse files
committed
refactor(client): modularize large modules and extract utilities
Extract client logic into focused modules: - Authors: Query options, params types, transformers - Institutions: Query options, params types, transformers - Sources: Query builder with search options - Works: Query options, params types, transformers Modularize: - persistent-graph: Edge filter, edge operations, node operations, types - graph-index-tier: Completeness utils - id-resolver: Checksum, patterns, types - query-builder: Constants, factories, types, utils - grouping: Statistics, type guards, types Remove deprecated static-data-provider-original and transformers. Update cache tier implementations.
1 parent a43d1df commit 18e6ada

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+6639
-7468
lines changed

packages/client/src/cache/__tests__/cache.integration.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,10 +456,11 @@ describe("Cache Integration - CachedOpenAlexClient", () => {
456456

457457
describe("Configuration Updates", () => {
458458
it("should update static cache configuration", () => {
459+
// Type coercion permitted in test files per constitution
459460
cachedClient.updateConfig({
460461
staticCacheEnabled: false,
461462
staticCacheGitHubPagesUrl: "https://new-url.com",
462-
});
463+
} as Record<string, unknown>);
463464

464465
expect(cachedClient.getStaticCacheEnabled()).toBe(false);
465466
});
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* Completeness Status Utilities
3+
*
4+
* Provides utility functions for managing node completeness status
5+
* in the graph index tier.
6+
*/
7+
8+
import type { CompletenessStatus } from '@bibgraph/types';
9+
10+
/**
11+
* Ordering of completeness statuses for upgrade comparisons
12+
*/
13+
const COMPLETENESS_ORDER: Record<CompletenessStatus, number> = {
14+
stub: 0,
15+
partial: 1,
16+
full: 2,
17+
};
18+
19+
/**
20+
* Check if completeness should be upgraded from current to proposed status.
21+
* Only allows upgrades: stub → partial → full (never downgrades)
22+
*
23+
* @param current - The current completeness status
24+
* @param proposed - The proposed new completeness status
25+
* @returns true if proposed is higher than current
26+
*/
27+
export const shouldUpgradeCompleteness = (
28+
current: CompletenessStatus,
29+
proposed: CompletenessStatus
30+
): boolean => {
31+
return COMPLETENESS_ORDER[proposed] > COMPLETENESS_ORDER[current];
32+
};
33+
34+
/**
35+
* Get the default empty stats for graph index statistics
36+
*/
37+
export const getEmptyNodesByCompleteness = (): Record<CompletenessStatus, number> => ({
38+
full: 0,
39+
partial: 0,
40+
stub: 0,
41+
});
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/**
2+
* Edge Filter Utilities
3+
*
4+
* Provides filtering logic for graph edges based on property filters.
5+
*/
6+
7+
import type { EdgePropertyFilter, GraphEdgeRecord } from '@bibgraph/types';
8+
9+
/**
10+
* Check if an edge matches the author position filter
11+
* @param edge
12+
* @param filter
13+
*/
14+
const matchesAuthorPosition = (
15+
edge: GraphEdgeRecord,
16+
filter: EdgePropertyFilter
17+
): boolean => {
18+
if (filter.authorPosition === undefined) {
19+
return true;
20+
}
21+
return edge.authorPosition === filter.authorPosition;
22+
};
23+
24+
/**
25+
* Check if an edge matches the corresponding author filter
26+
* @param edge
27+
* @param filter
28+
*/
29+
const matchesCorresponding = (
30+
edge: GraphEdgeRecord,
31+
filter: EdgePropertyFilter
32+
): boolean => {
33+
if (filter.isCorresponding === undefined) {
34+
return true;
35+
}
36+
return edge.isCorresponding === filter.isCorresponding;
37+
};
38+
39+
/**
40+
* Check if an edge matches the open access filter
41+
* @param edge
42+
* @param filter
43+
*/
44+
const matchesOpenAccess = (
45+
edge: GraphEdgeRecord,
46+
filter: EdgePropertyFilter
47+
): boolean => {
48+
if (filter.isOpenAccess === undefined) {
49+
return true;
50+
}
51+
return edge.isOpenAccess === filter.isOpenAccess;
52+
};
53+
54+
/**
55+
* Check if an edge matches the version filter
56+
* @param edge
57+
* @param filter
58+
*/
59+
const matchesVersion = (
60+
edge: GraphEdgeRecord,
61+
filter: EdgePropertyFilter
62+
): boolean => {
63+
if (filter.version === undefined) {
64+
return true;
65+
}
66+
return edge.version === filter.version;
67+
};
68+
69+
/**
70+
* Check if an edge matches the score range filters
71+
* @param edge
72+
* @param filter
73+
*/
74+
const matchesScoreRange = (
75+
edge: GraphEdgeRecord,
76+
filter: EdgePropertyFilter
77+
): boolean => {
78+
if (filter.scoreMin !== undefined && (edge.score === undefined || edge.score < filter.scoreMin)) {
79+
return false;
80+
}
81+
if (filter.scoreMax !== undefined && (edge.score === undefined || edge.score > filter.scoreMax)) {
82+
return false;
83+
}
84+
return true;
85+
};
86+
87+
/**
88+
* Check if an edge matches the years inclusion filter
89+
* @param edge
90+
* @param filter
91+
*/
92+
const matchesYearsInclude = (
93+
edge: GraphEdgeRecord,
94+
filter: EdgePropertyFilter
95+
): boolean => {
96+
if (filter.yearsInclude === undefined || filter.yearsInclude.length === 0) {
97+
return true;
98+
}
99+
if (!edge.years) {
100+
return false;
101+
}
102+
return filter.yearsInclude.some((year) => edge.years?.includes(year));
103+
};
104+
105+
/**
106+
* Check if an edge matches the award ID filter
107+
* @param edge
108+
* @param filter
109+
*/
110+
const matchesAwardId = (
111+
edge: GraphEdgeRecord,
112+
filter: EdgePropertyFilter
113+
): boolean => {
114+
if (filter.awardId === undefined) {
115+
return true;
116+
}
117+
return edge.awardId === filter.awardId;
118+
};
119+
120+
/**
121+
* Check if an edge matches the role filter
122+
* @param edge
123+
* @param filter
124+
*/
125+
const matchesRole = (
126+
edge: GraphEdgeRecord,
127+
filter: EdgePropertyFilter
128+
): boolean => {
129+
if (filter.role === undefined) {
130+
return true;
131+
}
132+
return edge.role === filter.role;
133+
};
134+
135+
/**
136+
* Check if a single edge matches all filter criteria
137+
* @param edge
138+
* @param filter
139+
*/
140+
const edgeMatchesFilter = (
141+
edge: GraphEdgeRecord,
142+
filter: EdgePropertyFilter
143+
): boolean => {
144+
return (
145+
matchesAuthorPosition(edge, filter) &&
146+
matchesCorresponding(edge, filter) &&
147+
matchesOpenAccess(edge, filter) &&
148+
matchesVersion(edge, filter) &&
149+
matchesScoreRange(edge, filter) &&
150+
matchesYearsInclude(edge, filter) &&
151+
matchesAwardId(edge, filter) &&
152+
matchesRole(edge, filter)
153+
);
154+
};
155+
156+
/**
157+
* Apply edge property filter to an array of edges
158+
*
159+
* @param edges - Array of edges to filter
160+
* @param filter - Filter criteria to apply
161+
* @returns Filtered array of edges matching all criteria
162+
*/
163+
export const applyEdgeFilter = (
164+
edges: GraphEdgeRecord[],
165+
filter: EdgePropertyFilter
166+
): GraphEdgeRecord[] => {
167+
return edges.filter((edge) => edgeMatchesFilter(edge, filter));
168+
};

0 commit comments

Comments
 (0)