Skip to content

Commit 02353fe

Browse files
authored
Merge pull request #8 from codemix:improvements
Improvements
2 parents 0709dfb + 324b72b commit 02353fe

8 files changed

Lines changed: 571 additions & 1 deletion

File tree

.changeset/bright-turkeys-study.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@codemix/graph": minor
3+
---
4+
5+
Expand traversal parity across value and edge pipelines.
6+
7+
`ValueTraversal` now supports `dedup()`, `skip()`, `limit()`, `range()`, `count()`, `property()`, and `properties()` so extracted values can keep flowing through the same shaping and projection steps as other traversal results.
8+
9+
`EdgeTraversal` now supports direct `skip()`, `limit()`, `range()`, `count()`, `map()`, `property()`, `properties()`, and `order()` operations, making it possible to paginate, transform, project, and sort edges without first converting them to vertices or raw values.

.githooks/pre-commit

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/usr/bin/env bash
2+
3+
set -euo pipefail
4+
5+
repo_root="$(git rev-parse --show-toplevel)"
6+
cd "$repo_root"
7+
8+
staged_files=()
9+
while IFS= read -r -d '' file; do
10+
case "$file" in
11+
*.cjs|*.css|*.cts|*.html|*.js|*.json|*.jsonc|*.jsx|*.md|*.mdx|*.mjs|*.mts|*.scss|*.ts|*.tsx|*.yaml|*.yml)
12+
if [[ -f "$file" ]]; then
13+
staged_files+=("$file")
14+
fi
15+
;;
16+
esac
17+
done < <(git diff --cached --name-only --diff-filter=ACMR -z)
18+
19+
if (( ${#staged_files[@]} == 0 )); then
20+
exit 0
21+
fi
22+
23+
echo "Formatting staged files with oxfmt"
24+
pnpm exec oxfmt --write -- "${staged_files[@]}"
25+
git add -- "${staged_files[@]}"

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ A fully typed, in-memory graph database. Vertices and edges are strongly typed a
3838
pnpm add @codemix/graph
3939
```
4040

41+
### Development
42+
43+
Running `pnpm install` in the monorepo configures Git to use the tracked hooks in `.githooks`.
44+
45+
The pre-commit hook formats staged supported source files with `oxfmt` and restages them automatically. You can re-run the hook setup at any time with `pnpm run setup:hooks`.
46+
4147
### Quick Start
4248

4349
```typescript

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111
"scripts": {
1212
"build": "pnpm run -r build",
1313
"changeset": "changeset",
14+
"prepare": "sh ./scripts/setup-git-hooks.sh",
1415
"version-packages": "changeset version",
1516
"release": "pnpm build && changeset publish",
17+
"setup:hooks": "sh ./scripts/setup-git-hooks.sh",
1618
"typecheck": "pnpm run -r typecheck",
1719
"test": "vitest",
1820
"test:coverage": "vitest run --coverage",

packages/graph/src/Traversals.ts

Lines changed: 271 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1017,7 +1017,7 @@ export class VertexTraversal<const TSchema extends GraphSchema, const TPath> ext
10171017
[
10181018
...this.steps,
10191019
new MapElementsStep<any>({
1020-
mapper: (path) => path.value.properties[propertyName],
1020+
mapper: (path) => path.value.get(propertyName),
10211021
}),
10221022
],
10231023
);
@@ -1106,6 +1106,53 @@ export class ValueTraversal<const TSchema extends GraphSchema, const TValue> ext
11061106
TSchema,
11071107
TValue
11081108
> {
1109+
/**
1110+
* Deduplicate the values in the traversal.
1111+
*/
1112+
public dedup() {
1113+
return new ValueTraversal<TSchema, TValue>(this.graph, [...this.steps, new DedupStep({})]);
1114+
}
1115+
1116+
/**
1117+
* Skip the first n values in the traversal.
1118+
*/
1119+
public skip(n: number) {
1120+
return new ValueTraversal<TSchema, TValue>(this.graph, [
1121+
...this.steps,
1122+
new RangeStep({ start: n, end: Infinity }),
1123+
]);
1124+
}
1125+
1126+
/**
1127+
* Take the first n values in the traversal.
1128+
* @param n The number of values to take.
1129+
*/
1130+
public limit(n: number) {
1131+
return new ValueTraversal<TSchema, TValue>(this.graph, [
1132+
...this.steps,
1133+
new RangeStep({ start: 0, end: n }),
1134+
]);
1135+
}
1136+
1137+
/**
1138+
* Slice the values in the traversal.
1139+
* @param start The index to start slicing
1140+
* @param end The index to end slicing
1141+
*/
1142+
public range(start: number, end: number) {
1143+
return new ValueTraversal<TSchema, TValue>(this.graph, [
1144+
...this.steps,
1145+
new RangeStep({ start, end }),
1146+
]);
1147+
}
1148+
1149+
/**
1150+
* Count the number of values in the traversal.
1151+
*/
1152+
public count() {
1153+
return new ValueTraversal<TSchema, number>(this.graph, [...this.steps, new CountStep({})]);
1154+
}
1155+
11091156
/**
11101157
* Map each value in the traversal to a new value.
11111158
* @param mapper A function that maps the current value to a new value.
@@ -1149,6 +1196,93 @@ export class ValueTraversal<const TSchema extends GraphSchema, const TValue> ext
11491196
]);
11501197
}
11511198

1199+
/**
1200+
* Select a single property of the values in the traversal.
1201+
* Supports both graph elements and plain object values.
1202+
* @param propertyName The name of the property to select.
1203+
*/
1204+
public property<const TPropertyName extends keyof GetValueTraversalProperties<TValue>>(
1205+
propertyName: TPropertyName,
1206+
) {
1207+
return new ValueTraversal<TSchema, GetValueTraversalProperties<TValue>[TPropertyName]>(
1208+
this.graph,
1209+
[
1210+
...this.steps,
1211+
new MapElementsStep<TValue>({
1212+
mapper: (value) => {
1213+
if (value instanceof Element) {
1214+
return value.get(propertyName as never);
1215+
}
1216+
if (typeof value === "object" && value !== null) {
1217+
return value[propertyName as keyof typeof value];
1218+
}
1219+
return undefined as GetValueTraversalProperties<TValue>[TPropertyName];
1220+
},
1221+
}),
1222+
],
1223+
);
1224+
}
1225+
1226+
/**
1227+
* Select specific properties of the values in the traversal.
1228+
* Supports both graph elements and plain object values.
1229+
* @param propertyNames The names of the properties to select.
1230+
*/
1231+
public properties(): ValueTraversal<TSchema, GetValueTraversalProperties<TValue>>;
1232+
public properties<
1233+
const TPropertyNames extends readonly (keyof GetValueTraversalProperties<TValue>)[],
1234+
>(
1235+
...propertyNames: TPropertyNames
1236+
): ValueTraversal<TSchema, Pick<GetValueTraversalProperties<TValue>, TPropertyNames[number]>>;
1237+
public properties<
1238+
const TPropertyNames extends readonly (keyof GetValueTraversalProperties<TValue>)[],
1239+
>(...propertyNames: TPropertyNames): ValueTraversal<TSchema, any> {
1240+
return new ValueTraversal<
1241+
TSchema,
1242+
Pick<GetValueTraversalProperties<TValue>, TPropertyNames[number]>
1243+
>(this.graph, [
1244+
...this.steps,
1245+
new MapElementsStep<TValue>({
1246+
mapper: (value) => {
1247+
if (value instanceof Element) {
1248+
const storedProps = value[$StoredElement].properties;
1249+
if (propertyNames.length === 0) {
1250+
return storedProps as Pick<
1251+
GetValueTraversalProperties<TValue>,
1252+
TPropertyNames[number]
1253+
>;
1254+
}
1255+
const properties = {} as Pick<
1256+
GetValueTraversalProperties<TValue>,
1257+
TPropertyNames[number]
1258+
>;
1259+
for (const propertyName of propertyNames) {
1260+
properties[propertyName] = value.get(propertyName as never);
1261+
}
1262+
return properties;
1263+
}
1264+
if (typeof value === "object" && value !== null) {
1265+
if (propertyNames.length === 0) {
1266+
return value as Pick<GetValueTraversalProperties<TValue>, TPropertyNames[number]>;
1267+
}
1268+
const properties = {} as Pick<
1269+
GetValueTraversalProperties<TValue>,
1270+
TPropertyNames[number]
1271+
>;
1272+
for (const propertyName of propertyNames) {
1273+
properties[propertyName] = value[propertyName as keyof typeof value] as Pick<
1274+
GetValueTraversalProperties<TValue>,
1275+
TPropertyNames[number]
1276+
>[typeof propertyName];
1277+
}
1278+
return properties;
1279+
}
1280+
return {} as Pick<GetValueTraversalProperties<TValue>, TPropertyNames[number]>;
1281+
},
1282+
}),
1283+
]);
1284+
}
1285+
11521286
/**
11531287
* Order the values in the traversal.
11541288
*/
@@ -1900,6 +2034,46 @@ export class EdgeTraversal<const TSchema extends GraphSchema, const TPath> exten
19002034
return new EdgeTraversal<TSchema, TPath>(this.graph, [...this.steps, new DedupStep({})]);
19012035
}
19022036

2037+
/**
2038+
* Skip the first n edges in the traversal.
2039+
*/
2040+
public skip(n: number) {
2041+
return new EdgeTraversal<TSchema, TPath>(this.graph, [
2042+
...this.steps,
2043+
new RangeStep({ start: n, end: Infinity }),
2044+
]);
2045+
}
2046+
2047+
/**
2048+
* Take the first n edges in the traversal.
2049+
* @param n The number of edges to take.
2050+
*/
2051+
public limit(n: number) {
2052+
return new EdgeTraversal<TSchema, TPath>(this.graph, [
2053+
...this.steps,
2054+
new RangeStep({ start: 0, end: n }),
2055+
]);
2056+
}
2057+
2058+
/**
2059+
* Slice the edges in the traversal.
2060+
* @param start The index to start slicing
2061+
* @param end The index to end slicing
2062+
*/
2063+
public range(start: number, end: number) {
2064+
return new EdgeTraversal<TSchema, TPath>(this.graph, [
2065+
...this.steps,
2066+
new RangeStep({ start, end }),
2067+
]);
2068+
}
2069+
2070+
/**
2071+
* Count the number of edges in the traversal.
2072+
*/
2073+
public count() {
2074+
return new ValueTraversal<TSchema, number>(this.graph, [...this.steps, new CountStep({})]);
2075+
}
2076+
19032077
/**
19042078
* Select the labeled elements in the path
19052079
* @param pathLabels The labels to select.
@@ -1927,4 +2101,100 @@ export class EdgeTraversal<const TSchema extends GraphSchema, const TPath> exten
19272101
new ValuesStep({}),
19282102
]);
19292103
}
2104+
2105+
/**
2106+
* Select a single property of the edges in the traversal.
2107+
* @param propertyName The name of the property to select.
2108+
*/
2109+
public property<const TPropertyName extends keyof GetTraversalPathProperties<TPath>>(
2110+
propertyName: TPropertyName,
2111+
) {
2112+
return new ValueTraversal<TSchema, GetTraversalPathProperties<TPath>[TPropertyName]>(
2113+
this.graph,
2114+
[
2115+
...this.steps,
2116+
new MapElementsStep<any>({
2117+
mapper: (path) => path.value.get(propertyName),
2118+
}),
2119+
],
2120+
);
2121+
}
2122+
2123+
/**
2124+
* Select specific properties of the edges in the traversal.
2125+
* @param propertyNames The names of the properties to select.
2126+
*/
2127+
public properties(): ValueTraversal<TSchema, GetTraversalPathProperties<TPath>>;
2128+
public properties<
2129+
const TPropertyNames extends readonly (keyof GetTraversalPathProperties<TPath>)[],
2130+
>(
2131+
...propertyNames: TPropertyNames
2132+
): ValueTraversal<TSchema, Pick<GetTraversalPathProperties<TPath>, TPropertyNames[number]>>;
2133+
public properties<
2134+
const TPropertyNames extends readonly (keyof GetTraversalPathProperties<TPath>)[],
2135+
>(...propertyNames: TPropertyNames) {
2136+
return new ValueTraversal<
2137+
TSchema,
2138+
Pick<GetTraversalPathProperties<TPath>, TPropertyNames[number]>
2139+
>(this.graph, [
2140+
...this.steps,
2141+
new MapElementsStep<any>({
2142+
mapper: (path) => {
2143+
const value = path.value;
2144+
const storedProps = value[$StoredElement].properties;
2145+
if (propertyNames.length === 0) {
2146+
return storedProps;
2147+
}
2148+
const properties = {} as any;
2149+
for (const propertyName of propertyNames) {
2150+
properties[propertyName] = storedProps[propertyName as keyof typeof storedProps];
2151+
}
2152+
return properties;
2153+
},
2154+
}),
2155+
]);
2156+
}
2157+
2158+
/**
2159+
* Map each edge in the traversal to a new value.
2160+
* @param mapper A function that maps the path to a new value.
2161+
*/
2162+
public map<const TValue>(mapper: (path: TPath) => TValue) {
2163+
return new ValueTraversal<TSchema, TValue>(this.graph, [
2164+
...this.steps,
2165+
new MapElementsStep<TPath>({
2166+
mapper,
2167+
}),
2168+
]);
2169+
}
2170+
2171+
/**
2172+
* Order the edges in the traversal.
2173+
*/
2174+
public order() {
2175+
return new OrderEdgeTraversal<TSchema, TPath>(this.graph, [
2176+
...this.steps,
2177+
new OrderStep({ directions: [] }),
2178+
]);
2179+
}
2180+
}
2181+
2182+
export class OrderEdgeTraversal<
2183+
const TSchema extends GraphSchema,
2184+
const TPath,
2185+
> extends EdgeTraversal<TSchema, TPath> {
2186+
/**
2187+
* Order the edges in the traversal by a property.
2188+
* @param propertyName The name of the property to order by.
2189+
* @param direction The direction to order by.
2190+
*/
2191+
public by<const TPropertyName extends keyof GetTraversalPathProperties<TPath> & string>(
2192+
propertyName: TPropertyName,
2193+
direction: OrderDirection = "asc",
2194+
) {
2195+
return new OrderEdgeTraversal<TSchema, TPath>(
2196+
this.graph,
2197+
appendOrderDirection(this.steps, { key: propertyName, direction }),
2198+
);
2199+
}
19302200
}

0 commit comments

Comments
 (0)